Allow CLI paths to come from config
This commit is contained in:
@@ -32,6 +32,11 @@ const (
|
||||
ExitValidationFailed = 2
|
||||
)
|
||||
|
||||
const (
|
||||
errPromptDirRequired = "prompt directory is required; provide --prompt-dir or config.yml prompt_dir"
|
||||
errProfileDirRequired = "profile directory is required; provide --profile-dir or config.yml profile_dir"
|
||||
)
|
||||
|
||||
type runConfig struct {
|
||||
configPath string
|
||||
|
||||
@@ -318,10 +323,10 @@ func parseServeArgs(args []string) (*serveConfig, error) {
|
||||
cfg.addr = settings.ServerAddr
|
||||
|
||||
if strings.TrimSpace(cfg.promptDir) == "" {
|
||||
return nil, errors.New("--prompt-dir is required")
|
||||
return nil, errors.New(errPromptDirRequired)
|
||||
}
|
||||
if strings.TrimSpace(cfg.profileDir) == "" {
|
||||
return nil, errors.New("--profile-dir is required")
|
||||
return nil, errors.New(errProfileDirRequired)
|
||||
}
|
||||
|
||||
cfg.promptDir = filepath.Clean(cfg.promptDir)
|
||||
@@ -369,10 +374,10 @@ func finalizeExecutionRequestConfig(fs *flag.FlagSet, cfg *runConfig) error {
|
||||
cfg.defaultRenderFormat = settings.DefaultRenderFormat
|
||||
|
||||
if strings.TrimSpace(cfg.promptDir) == "" {
|
||||
return errors.New("--prompt-dir is required")
|
||||
return errors.New(errPromptDirRequired)
|
||||
}
|
||||
if strings.TrimSpace(cfg.profileDir) == "" {
|
||||
return errors.New("--profile-dir is required")
|
||||
return errors.New(errProfileDirRequired)
|
||||
}
|
||||
if strings.TrimSpace(cfg.promptID) == "" {
|
||||
return errors.New("--prompt is required")
|
||||
@@ -592,7 +597,7 @@ func printSummary(stderr io.Writer, res *domain.RunResult) {
|
||||
|
||||
func printUsage(w io.Writer) {
|
||||
fmt.Fprintln(w, "usage: scriptorium <run|render|serve> ...")
|
||||
fmt.Fprintln(w, " run: scriptorium run [--config PATH] --prompt-dir DIR --profile-dir DIR --prompt ID --input name=path [--input ...] [--profile ID] [--llm-base-url URL] [--model NAME] [--api-key-env ENV] [--temperature N] [--max-tokens N] [--top-p N] [--var k=v] [--out path] [--timeout 10m]")
|
||||
fmt.Fprintln(w, " render: scriptorium render [--config PATH] --prompt-dir DIR --profile-dir DIR --prompt ID --input name=path [--input ...] [--profile ID] [--llm-base-url URL] [--model NAME] [--api-key-env ENV] [--temperature N] [--max-tokens N] [--top-p N] [--var k=v] [--format text|json] [--out path] [--timeout 10m]")
|
||||
fmt.Fprintf(w, " serve: scriptorium serve [--config PATH] --addr %s --prompt-dir DIR --profile-dir DIR [--schema-dir DIR]\n", defaults.HTTPAddrDefault)
|
||||
fmt.Fprintln(w, " run: scriptorium run [--config PATH] [--prompt-dir DIR] [--profile-dir DIR] --prompt ID --input name=path [--input ...] [--profile ID] [--llm-base-url URL] [--model NAME] [--api-key-env ENV] [--temperature N] [--max-tokens N] [--top-p N] [--var k=v] [--out path] [--timeout 10m]")
|
||||
fmt.Fprintln(w, " render: scriptorium render [--config PATH] [--prompt-dir DIR] [--profile-dir DIR] --prompt ID --input name=path [--input ...] [--profile ID] [--llm-base-url URL] [--model NAME] [--api-key-env ENV] [--temperature N] [--max-tokens N] [--top-p N] [--var k=v] [--format text|json] [--out path] [--timeout 10m]")
|
||||
fmt.Fprintf(w, " serve: scriptorium serve [--config PATH] [--addr %s] [--prompt-dir DIR] [--profile-dir DIR] [--schema-dir DIR]\n", defaults.HTTPAddrDefault)
|
||||
}
|
||||
|
||||
@@ -69,11 +69,17 @@ func TestParseRunArgsRequiredFlags(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("expected missing --prompt-dir error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "prompt directory is required") {
|
||||
t.Fatalf("expected clear prompt-dir guidance, got %v", err)
|
||||
}
|
||||
|
||||
_, err = parseRunArgs([]string{"--config", configPath, "--prompt-dir", "./prompts", "--prompt", "p", "--input", "a=b"})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing --profile-dir error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "profile directory is required") {
|
||||
t.Fatalf("expected clear profile-dir guidance, got %v", err)
|
||||
}
|
||||
|
||||
_, err = parseRunArgs([]string{"--config", configPath, "--prompt-dir", "./prompts", "--profile-dir", "./profiles", "--input", "a=b"})
|
||||
if err == nil {
|
||||
@@ -150,11 +156,17 @@ func TestParseServeArgsRequiredFlags(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("expected missing --prompt-dir error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "prompt directory is required") {
|
||||
t.Fatalf("expected clear prompt-dir guidance, got %v", err)
|
||||
}
|
||||
|
||||
_, err = parseServeArgs([]string{"--config", configPath, "--prompt-dir", "./prompts"})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing --profile-dir error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "profile directory is required") {
|
||||
t.Fatalf("expected clear profile-dir guidance, got %v", err)
|
||||
}
|
||||
|
||||
cfg, err := parseServeArgs([]string{"--config", configPath, "--prompt-dir", "./prompts", "--profile-dir", "./profiles"})
|
||||
if err != nil {
|
||||
@@ -413,6 +425,108 @@ server:
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseServeArgsWithConfigProvidesRequiredDirectoriesAndAddr(t *testing.T) {
|
||||
configPath := writeAppConfigFile(t, `
|
||||
prompt_dir: ./from-config/prompts
|
||||
profile_dir: ./from-config/profiles
|
||||
schema_dir: ./from-config/schemas
|
||||
server:
|
||||
addr: 127.0.0.1:9000
|
||||
`)
|
||||
|
||||
cfg, err := parseServeArgs([]string{
|
||||
"--config", configPath,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected valid args, got %v", err)
|
||||
}
|
||||
|
||||
if cfg.promptDir != filepath.Clean("./from-config/prompts") {
|
||||
t.Fatalf("expected prompt dir from config, got %q", cfg.promptDir)
|
||||
}
|
||||
if cfg.profileDir != filepath.Clean("./from-config/profiles") {
|
||||
t.Fatalf("expected profile dir from config, got %q", cfg.profileDir)
|
||||
}
|
||||
if cfg.schemaDir != filepath.Clean("./from-config/schemas") {
|
||||
t.Fatalf("expected schema dir from config, got %q", cfg.schemaDir)
|
||||
}
|
||||
if cfg.addr != "127.0.0.1:9000" {
|
||||
t.Fatalf("expected addr from config, got %q", cfg.addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRunArgsFailsClearlyWhenNoEffectivePromptDir(t *testing.T) {
|
||||
configPath := writeAppConfigFile(t, `
|
||||
profile_dir: ./profiles
|
||||
`)
|
||||
|
||||
_, err := parseRunArgs([]string{
|
||||
"--config", configPath,
|
||||
"--prompt", "p",
|
||||
"--input", "a=b",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing prompt_dir error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "prompt directory is required") || !strings.Contains(err.Error(), "config.yml prompt_dir") {
|
||||
t.Fatalf("expected clear prompt_dir guidance, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRunArgsFailsClearlyWhenNoEffectiveProfileDir(t *testing.T) {
|
||||
configPath := writeAppConfigFile(t, `
|
||||
prompt_dir: ./prompts
|
||||
`)
|
||||
|
||||
_, err := parseRunArgs([]string{
|
||||
"--config", configPath,
|
||||
"--prompt", "p",
|
||||
"--input", "a=b",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing profile_dir error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "profile directory is required") || !strings.Contains(err.Error(), "config.yml profile_dir") {
|
||||
t.Fatalf("expected clear profile_dir guidance, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRenderArgsFailsClearlyWhenNoEffectivePromptDir(t *testing.T) {
|
||||
configPath := writeAppConfigFile(t, `
|
||||
profile_dir: ./profiles
|
||||
`)
|
||||
|
||||
_, err := parseRenderArgs([]string{
|
||||
"--config", configPath,
|
||||
"--prompt", "p",
|
||||
"--input", "a=b",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing prompt_dir error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "prompt directory is required") || !strings.Contains(err.Error(), "config.yml prompt_dir") {
|
||||
t.Fatalf("expected clear prompt_dir guidance, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRenderArgsFailsClearlyWhenNoEffectiveProfileDir(t *testing.T) {
|
||||
configPath := writeAppConfigFile(t, `
|
||||
prompt_dir: ./prompts
|
||||
`)
|
||||
|
||||
_, err := parseRenderArgs([]string{
|
||||
"--config", configPath,
|
||||
"--prompt", "p",
|
||||
"--input", "a=b",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected missing profile_dir error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "profile directory is required") || !strings.Contains(err.Error(), "config.yml profile_dir") {
|
||||
t.Fatalf("expected clear profile_dir guidance, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetermineExitCode(t *testing.T) {
|
||||
if got := determineExitCode(errors.New("boom"), nil); got != ExitRuntimeError {
|
||||
t.Fatalf("expected runtime exit code, got %d", got)
|
||||
@@ -455,6 +569,46 @@ func TestRunCommandVarsOptional(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCommandSucceedsWithPromptAndProfileDirsFromConfig(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
promptDir := filepath.Join(tmp, "prompts")
|
||||
profileDir := filepath.Join(tmp, "profiles")
|
||||
if err := os.MkdirAll(promptDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(profileDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
inputPath := filepath.Join(tmp, "transcript.md")
|
||||
if err := os.WriteFile(inputPath, []byte("hello"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ts := newTestLLMServer("from-config-dirs", nil)
|
||||
defer ts.Close()
|
||||
|
||||
writePromptFile(t, promptDir, "prompt.default", "local-default")
|
||||
writeProfileFile(t, profileDir, "local-default", ts.URL+"/v1", "profile-model")
|
||||
configPath := writeAppConfigFile(t, fmt.Sprintf(`
|
||||
prompt_dir: %s
|
||||
profile_dir: %s
|
||||
`, promptDir, profileDir))
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := runCommand([]string{
|
||||
"--config", configPath,
|
||||
"--prompt", "prompt.default",
|
||||
"--input", "transcript=" + inputPath,
|
||||
}, &stdout, &stderr)
|
||||
if code != ExitOK {
|
||||
t.Fatalf("expected ExitOK, got %d stderr=%q", code, stderr.String())
|
||||
}
|
||||
if stdout.String() != "from-config-dirs" {
|
||||
t.Fatalf("unexpected stdout output: %q", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderCommandDefaultFormatTextIncludesPreparedDetailsAndNoSecrets(t *testing.T) {
|
||||
const envName = "SCRIPTORIUM_RENDER_TEST_API_KEY"
|
||||
const secret = "super-secret-render-key"
|
||||
@@ -527,6 +681,43 @@ func TestRenderCommandDefaultFormatTextIncludesPreparedDetailsAndNoSecrets(t *te
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderCommandSucceedsWithPromptAndProfileDirsFromConfig(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
promptDir := filepath.Join(tmp, "prompts")
|
||||
profileDir := filepath.Join(tmp, "profiles")
|
||||
if err := os.MkdirAll(promptDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(profileDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
inputPath := filepath.Join(tmp, "transcript.md")
|
||||
if err := os.WriteFile(inputPath, []byte("hello transcript"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
writePromptFile(t, promptDir, "prompt.render", "local-default")
|
||||
writeProfileFile(t, profileDir, "local-default", "http://127.0.0.1:1/v1", "profile-model")
|
||||
configPath := writeAppConfigFile(t, fmt.Sprintf(`
|
||||
prompt_dir: %s
|
||||
profile_dir: %s
|
||||
`, promptDir, profileDir))
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
code := renderCommand([]string{
|
||||
"--config", configPath,
|
||||
"--prompt", "prompt.render",
|
||||
"--input", "transcript=" + inputPath,
|
||||
}, &stdout, &stderr)
|
||||
if code != ExitOK {
|
||||
t.Fatalf("expected ExitOK, got %d stderr=%q", code, stderr.String())
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "prompt: prompt.render") {
|
||||
t.Fatalf("expected rendered output, got %q", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderCommandExplicitTextFormatWorks(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
promptDir := filepath.Join(tmp, "prompts")
|
||||
|
||||
Reference in New Issue
Block a user