262 lines
9.9 KiB
Go
262 lines
9.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
|
)
|
|
|
|
const processNotImplementedMessage = "process command is not implemented yet"
|
|
|
|
// Run executes the Audita CLI with the provided arguments and streams.
|
|
func Run(args []string, stdout, stderr io.Writer) int {
|
|
if len(args) == 0 {
|
|
writeRootUsage(stdout)
|
|
return 0
|
|
}
|
|
|
|
if isHelpCommand(args) {
|
|
writeRootUsage(stdout)
|
|
return 0
|
|
}
|
|
|
|
if args[0] == "process" {
|
|
return runProcess(args[1:], stdout, stderr)
|
|
}
|
|
|
|
fmt.Fprintf(stderr, "audita: unknown command %q\n\n", args[0])
|
|
writeRootUsage(stderr)
|
|
return 2
|
|
}
|
|
|
|
func runProcess(args []string, stdout, stderr io.Writer) int {
|
|
if isHelpCommand(args) || hasHelpFlag(args) {
|
|
writeProcessUsage(stdout)
|
|
return 0
|
|
}
|
|
|
|
cfg, err := config.LoadFromEnv()
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "audita process: invalid environment configuration: %v\n", err)
|
|
return 2
|
|
}
|
|
|
|
fs := flag.NewFlagSet("process", flag.ContinueOnError)
|
|
fs.SetOutput(stderr)
|
|
|
|
glossaryPath := fs.String("glossary", "", "Path to glossary YAML file")
|
|
outputPath := fs.String("output", "", "Path to corrected transcript JSON output file")
|
|
reportJSONPath := fs.String("report-json", "", "Path to machine-readable report JSON output file")
|
|
modules := fs.String("modules", strings.Join(cfg.Modules, ","), "Comma-separated module sequence override")
|
|
llmAPIKey := fs.String("llm-api-key", cfg.PrimaryLLM.APIKey, "Primary LLM API key")
|
|
validationLLMAPIKey := fs.String("validation-llm-api-key", cfg.ValidationLLM.APIKey, "Validation LLM API key")
|
|
model := fs.String("model", cfg.PrimaryLLM.Model, "Primary LLM model name")
|
|
validationModel := fs.String("validation-model", cfg.ValidationLLM.Model, "Validation LLM model name")
|
|
baseURL := fs.String("base-url", cfg.PrimaryLLM.BaseURL, "Primary OpenAI-compatible base URL")
|
|
validationBaseURL := fs.String("validation-base-url", cfg.ValidationLLM.BaseURL, "Validation OpenAI-compatible base URL")
|
|
llmTimeoutSeconds := fs.Int("llm-timeout-seconds", cfg.PrimaryLLM.TimeoutSeconds, "Primary LLM timeout in seconds")
|
|
validationTimeoutSecondsDefault := cfg.PrimaryLLM.TimeoutSeconds
|
|
if cfg.ValidationLLM.TimeoutSeconds != nil {
|
|
validationTimeoutSecondsDefault = *cfg.ValidationLLM.TimeoutSeconds
|
|
}
|
|
validationLLMTimeoutSeconds := fs.Int("validation-llm-timeout-seconds", validationTimeoutSecondsDefault, "Validation LLM timeout in seconds")
|
|
maxRetries := fs.Int("max-retries", cfg.PrimaryLLM.MaxRetries, "Maximum structured-output retries")
|
|
validationMaxRetriesDefault := cfg.PrimaryLLM.MaxRetries
|
|
if cfg.ValidationLLM.MaxRetries != nil {
|
|
validationMaxRetriesDefault = *cfg.ValidationLLM.MaxRetries
|
|
}
|
|
validationMaxRetries := fs.Int("validation-max-retries", validationMaxRetriesDefault, "Validation structured-output retries")
|
|
validationMaxPromptTokens := fs.Int("validation-max-prompt-tokens", cfg.ValidationMaxPromptTokens, "Validation max prompt tokens")
|
|
maxSectionTokens := fs.Int("max-section-tokens", cfg.MaxSectionTokens, "Max section tokens")
|
|
minSectionTokens := fs.Int("min-section-tokens", cfg.MinSectionTokens, "Min section tokens")
|
|
targetSectionsDefault := 0
|
|
if cfg.TargetSections != nil {
|
|
targetSectionsDefault = *cfg.TargetSections
|
|
}
|
|
targetSections := fs.Int("target-sections", targetSectionsDefault, "Target number of transcript sections")
|
|
glossaryConfidenceThreshold := fs.Float64("glossary-confidence-threshold", cfg.Thresholds.Glossary, "Glossary confidence threshold")
|
|
grammarConfidenceThreshold := fs.Float64("grammar-confidence-threshold", cfg.Thresholds.Grammar, "Grammar confidence threshold")
|
|
homophonesConfidenceThreshold := fs.Float64("homophones-confidence-threshold", cfg.Thresholds.Homophones, "Homophones confidence threshold")
|
|
spokenWordConfidenceThreshold := fs.Float64("spoken-word-confidence-threshold", cfg.Thresholds.SpokenWord, "Spoken-word confidence threshold")
|
|
normalizeMaxSegmentGap := fs.Float64("normalize-max-segment-gap", cfg.Normalization.MaxSegmentGap, "Maximum same-speaker merge gap")
|
|
normalizeEllipsisGap := fs.Float64("normalize-ellipsis-gap", cfg.Normalization.EllipsisGap, "Gap threshold for ellipsis insertion")
|
|
normalizeMaxSegmentDuration := fs.Float64("normalize-max-segment-duration", cfg.Normalization.MaxSegmentDuration, "Maximum merged segment duration")
|
|
normalizeMaxSegmentTokens := fs.Int("normalize-max-segment-tokens", cfg.Normalization.MaxSegmentTokens, "Maximum merged segment token estimate")
|
|
workDir := fs.String("work-dir", cfg.WorkDir, "Per-run work directory")
|
|
workDirRetention := fs.String("work-dir-retention", string(cfg.WorkDirRetention), "Work-dir retention policy: auto|always|never")
|
|
|
|
_ = glossaryPath
|
|
_ = outputPath
|
|
_ = reportJSONPath
|
|
_ = llmAPIKey
|
|
_ = validationLLMAPIKey
|
|
_ = model
|
|
_ = validationModel
|
|
_ = baseURL
|
|
_ = validationBaseURL
|
|
_ = llmTimeoutSeconds
|
|
_ = validationLLMTimeoutSeconds
|
|
_ = maxRetries
|
|
_ = validationMaxRetries
|
|
_ = validationMaxPromptTokens
|
|
_ = maxSectionTokens
|
|
_ = minSectionTokens
|
|
_ = targetSections
|
|
_ = glossaryConfidenceThreshold
|
|
_ = grammarConfidenceThreshold
|
|
_ = homophonesConfidenceThreshold
|
|
_ = spokenWordConfidenceThreshold
|
|
_ = normalizeMaxSegmentGap
|
|
_ = normalizeEllipsisGap
|
|
_ = normalizeMaxSegmentDuration
|
|
_ = normalizeMaxSegmentTokens
|
|
_ = workDir
|
|
_ = workDirRetention
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
if errors.Is(err, flag.ErrHelp) {
|
|
writeProcessUsage(stdout)
|
|
return 0
|
|
}
|
|
return 2
|
|
}
|
|
|
|
overrides := config.CLIOverrides{}
|
|
fs.Visit(func(f *flag.Flag) {
|
|
switch f.Name {
|
|
case "modules":
|
|
overrides.ModulesCSV = modules
|
|
case "llm-api-key":
|
|
overrides.PrimaryLLMAPIKey = llmAPIKey
|
|
case "validation-llm-api-key":
|
|
overrides.ValidationLLMAPIKey = validationLLMAPIKey
|
|
case "model":
|
|
overrides.PrimaryModel = model
|
|
case "validation-model":
|
|
overrides.ValidationModel = validationModel
|
|
case "base-url":
|
|
overrides.PrimaryBaseURL = baseURL
|
|
case "validation-base-url":
|
|
overrides.ValidationBaseURL = validationBaseURL
|
|
case "llm-timeout-seconds":
|
|
overrides.PrimaryLLMTimeoutSeconds = llmTimeoutSeconds
|
|
case "validation-llm-timeout-seconds":
|
|
overrides.ValidationLLMTimeoutSeconds = validationLLMTimeoutSeconds
|
|
case "max-retries":
|
|
overrides.MaxRetries = maxRetries
|
|
case "validation-max-retries":
|
|
overrides.ValidationMaxRetries = validationMaxRetries
|
|
case "validation-max-prompt-tokens":
|
|
overrides.ValidationMaxPromptTokens = validationMaxPromptTokens
|
|
case "max-section-tokens":
|
|
overrides.MaxSectionTokens = maxSectionTokens
|
|
case "min-section-tokens":
|
|
overrides.MinSectionTokens = minSectionTokens
|
|
case "target-sections":
|
|
overrides.TargetSections = targetSections
|
|
case "glossary-confidence-threshold":
|
|
overrides.GlossaryConfidenceThreshold = glossaryConfidenceThreshold
|
|
case "grammar-confidence-threshold":
|
|
overrides.GrammarConfidenceThreshold = grammarConfidenceThreshold
|
|
case "homophones-confidence-threshold":
|
|
overrides.HomophonesConfidenceThreshold = homophonesConfidenceThreshold
|
|
case "spoken-word-confidence-threshold":
|
|
overrides.SpokenWordConfidenceThreshold = spokenWordConfidenceThreshold
|
|
case "normalize-max-segment-gap":
|
|
overrides.NormalizeMaxSegmentGap = normalizeMaxSegmentGap
|
|
case "normalize-ellipsis-gap":
|
|
overrides.NormalizeEllipsisGap = normalizeEllipsisGap
|
|
case "normalize-max-segment-duration":
|
|
overrides.NormalizeMaxSegmentDuration = normalizeMaxSegmentDuration
|
|
case "normalize-max-segment-tokens":
|
|
overrides.NormalizeMaxSegmentTokens = normalizeMaxSegmentTokens
|
|
case "work-dir":
|
|
overrides.WorkDir = workDir
|
|
case "work-dir-retention":
|
|
overrides.WorkDirRetention = workDirRetention
|
|
}
|
|
})
|
|
|
|
if err := cfg.ApplyCLIOverrides(overrides); err != nil {
|
|
fmt.Fprintf(stderr, "audita process: invalid CLI configuration: %v\n", err)
|
|
return 2
|
|
}
|
|
|
|
remaining := fs.Args()
|
|
if len(remaining) != 1 {
|
|
fmt.Fprintln(stderr, "audita process: expected exactly 1 transcript JSON path argument")
|
|
fmt.Fprintln(stderr)
|
|
writeProcessUsage(stderr)
|
|
return 2
|
|
}
|
|
|
|
fmt.Fprintf(stderr, "audita process: %s\n", processNotImplementedMessage)
|
|
return 1
|
|
}
|
|
|
|
func isHelpCommand(args []string) bool {
|
|
if len(args) == 0 {
|
|
return false
|
|
}
|
|
if len(args) == 1 {
|
|
switch args[0] {
|
|
case "help", "-h", "--help":
|
|
return true
|
|
}
|
|
}
|
|
if len(args) == 2 && args[0] == "help" {
|
|
switch args[1] {
|
|
case "process":
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hasHelpFlag(args []string) bool {
|
|
for _, arg := range args {
|
|
if arg == "-h" || arg == "--help" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func writeRootUsage(w io.Writer) {
|
|
fmt.Fprintln(w, "Audita is a transcript polishing CLI.")
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintln(w, "Usage:")
|
|
fmt.Fprintln(w, " audita <command> [options]")
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintln(w, "Commands:")
|
|
fmt.Fprintln(w, " process Process a transcript JSON file")
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintln(w, "Example:")
|
|
fmt.Fprintln(w, " audita process transcript.json --glossary glossary.yaml --output corrected.json")
|
|
}
|
|
|
|
func writeProcessUsage(w io.Writer) {
|
|
fmt.Fprintln(w, "Process a transcript JSON file.")
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintln(w, "Usage:")
|
|
fmt.Fprintln(w, " audita process <transcript.json> [flags]")
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintln(w, "Flags:")
|
|
fmt.Fprintln(w, " --glossary <path> Path to glossary YAML file")
|
|
fmt.Fprintln(w, " --output <path> Path to corrected transcript JSON output file")
|
|
fmt.Fprintln(w, " --report-json <path> Path to machine-readable report JSON output file")
|
|
fmt.Fprintln(w, " --modules <list> Comma-separated module sequence override")
|
|
fmt.Fprintln(w, " --llm-api-key <key> Primary LLM API key")
|
|
fmt.Fprintln(w, " --model <name> Primary LLM model")
|
|
fmt.Fprintln(w, " --base-url <url> Primary OpenAI-compatible API base URL")
|
|
fmt.Fprintln(w, " --work-dir <path> Per-run work directory")
|
|
fmt.Fprintln(w, " --work-dir-retention <mode> auto|always|never")
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintln(w, "Example:")
|
|
fmt.Fprintln(w, " audita process transcript.json --glossary glossary.yaml --output corrected.json")
|
|
}
|