detached 086a9fe

Add context-free CLI processing

commit
086a9fe2504703281cca08352012f4d03b3dd9ba
author
patriceckhart · · 2026-08-06 15:02 UTC
parent
7859d9c
signature
unsigned
refs
tag: v0.3.36
8 files · +98 -17
M README.md +3 -0
@@ -201,6 +201,7 @@ The first time you launch a newer zot binary, the TUI shows the GitHub release n
201201 zot # interactive tui
202202 zot "fix the failing test" # tui, pre-filled prompt
203203 zot -p "list all go files" # print final text, exit
204+cat README.md | zot -p "summarize this text" # combine stdin with the prompt
204205 zot -p --stats stats.json "task" # print final text and write generation stats
205206 zot --json "refactor main.go" # newline-delimited json events, exit
206207 zot --continue # resume the most recent session for this cwd
@@ -235,6 +236,7 @@ Print-mode stats contain `provider`, `model`, `prompt_tokens`, `reasoning_tokens
235236 | `-e`, `--ext <path>` | Load an extension from `<path>` for this run (repeatable; wins against installed extensions of the same name). |
236237 | `--no-ext` | Skip extension discovery for this run. `--ext` still works on top, so `--no-ext --ext ./x` runs only `x`. |
237238 | `--no-skill` | Disable all skills, including built-ins. No `skill` tool is registered and the system prompt has no skill manifest. |
239+| `--no-context-files`, `-nc` | Disable discovery and loading of global and project `AGENTS.md` files. |
238240 | `--no-yolo` | Confirm every tool call before it runs (interactive TUI only). A dialog shows the tool name and a one-line preview of its args, while edit calls show the proposed diff in the tool panel, with four choices: yes, yes-always-this-tool-this-session, yes-always-this-session, no. Press `/` while the dialog is focused to run a slash command first; closing its input or child dialog returns to the pending confirmation. Ignored with a stderr warning in print / json / rpc modes, where tools still run freely so scripts and automation keep working. |
239241
240242 ## Tools
@@ -251,6 +253,7 @@ When the sandbox is on (see `/jail`), all four tools refuse paths outside the se
251253 - **Interactive** (default): chat TUI with streaming output, spinner, cost meter, slash commands.
252254 - **Print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.
253255 - **Stream**: `zot --stream "prompt"` runs without the TUI and writes assistant text to stdout as it arrives. Tool activity goes to stderr.
256+- **Piped input**: print, stream, and JSON modes prepend piped stdin to the positional prompt, separated by a newline. For example, `cat README.md | zot -p "summarize this text"`.
254257 - **JSON**: `zot --json "prompt"` emits one JSON object per agent event to stdout, newline-delimited. The schema is documented in [docs/rpc.md](docs/rpc.md).
255258 - **RPC**: `zot rpc` runs as a long-lived child process; commands in on stdin, events and responses out on stdout, both as NDJSON. Designed for embedding zot in third-party apps written in any language. See [docs/rpc.md](docs/rpc.md) for the wire schema and `examples/rpc/{python,node,shell,go}` for working clients.
256259
@@ -201,6 +201,7 @@ The first time you launch a newer zot binary, the TUI shows the GitHub release n
201 zot # interactive tui201 zot # interactive tui
202 zot "fix the failing test" # tui, pre-filled prompt202 zot "fix the failing test" # tui, pre-filled prompt
203 zot -p "list all go files" # print final text, exit203 zot -p "list all go files" # print final text, exit
204+cat README.md | zot -p "summarize this text" # combine stdin with the prompt
204 zot -p --stats stats.json "task" # print final text and write generation stats205 zot -p --stats stats.json "task" # print final text and write generation stats
205 zot --json "refactor main.go" # newline-delimited json events, exit206 zot --json "refactor main.go" # newline-delimited json events, exit
206 zot --continue # resume the most recent session for this cwd207 zot --continue # resume the most recent session for this cwd
@@ -235,6 +236,7 @@ Print-mode stats contain `provider`, `model`, `prompt_tokens`, `reasoning_tokens
235 | `-e`, `--ext <path>` | Load an extension from `<path>` for this run (repeatable; wins against installed extensions of the same name). |236 | `-e`, `--ext <path>` | Load an extension from `<path>` for this run (repeatable; wins against installed extensions of the same name). |
236 | `--no-ext` | Skip extension discovery for this run. `--ext` still works on top, so `--no-ext --ext ./x` runs only `x`. |237 | `--no-ext` | Skip extension discovery for this run. `--ext` still works on top, so `--no-ext --ext ./x` runs only `x`. |
237 | `--no-skill` | Disable all skills, including built-ins. No `skill` tool is registered and the system prompt has no skill manifest. |238 | `--no-skill` | Disable all skills, including built-ins. No `skill` tool is registered and the system prompt has no skill manifest. |
239+| `--no-context-files`, `-nc` | Disable discovery and loading of global and project `AGENTS.md` files. |
238 | `--no-yolo` | Confirm every tool call before it runs (interactive TUI only). A dialog shows the tool name and a one-line preview of its args, while edit calls show the proposed diff in the tool panel, with four choices: yes, yes-always-this-tool-this-session, yes-always-this-session, no. Press `/` while the dialog is focused to run a slash command first; closing its input or child dialog returns to the pending confirmation. Ignored with a stderr warning in print / json / rpc modes, where tools still run freely so scripts and automation keep working. |240 | `--no-yolo` | Confirm every tool call before it runs (interactive TUI only). A dialog shows the tool name and a one-line preview of its args, while edit calls show the proposed diff in the tool panel, with four choices: yes, yes-always-this-tool-this-session, yes-always-this-session, no. Press `/` while the dialog is focused to run a slash command first; closing its input or child dialog returns to the pending confirmation. Ignored with a stderr warning in print / json / rpc modes, where tools still run freely so scripts and automation keep working. |
239 241
240 ## Tools242 ## Tools
@@ -251,6 +253,7 @@ When the sandbox is on (see `/jail`), all four tools refuse paths outside the se
251 - **Interactive** (default): chat TUI with streaming output, spinner, cost meter, slash commands.253 - **Interactive** (default): chat TUI with streaming output, spinner, cost meter, slash commands.
252 - **Print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.254 - **Print**: `zot -p "prompt"` runs the agent to completion and writes only the final assistant text to stdout.
253 - **Stream**: `zot --stream "prompt"` runs without the TUI and writes assistant text to stdout as it arrives. Tool activity goes to stderr.255 - **Stream**: `zot --stream "prompt"` runs without the TUI and writes assistant text to stdout as it arrives. Tool activity goes to stderr.
256+- **Piped input**: print, stream, and JSON modes prepend piped stdin to the positional prompt, separated by a newline. For example, `cat README.md | zot -p "summarize this text"`.
254 - **JSON**: `zot --json "prompt"` emits one JSON object per agent event to stdout, newline-delimited. The schema is documented in [docs/rpc.md](docs/rpc.md).257 - **JSON**: `zot --json "prompt"` emits one JSON object per agent event to stdout, newline-delimited. The schema is documented in [docs/rpc.md](docs/rpc.md).
255 - **RPC**: `zot rpc` runs as a long-lived child process; commands in on stdin, events and responses out on stdout, both as NDJSON. Designed for embedding zot in third-party apps written in any language. See [docs/rpc.md](docs/rpc.md) for the wire schema and `examples/rpc/{python,node,shell,go}` for working clients.258 - **RPC**: `zot rpc` runs as a long-lived child process; commands in on stdin, events and responses out on stdout, both as NDJSON. Designed for embedding zot in third-party apps written in any language. See [docs/rpc.md](docs/rpc.md) for the wire schema and `examples/rpc/{python,node,shell,go}` for working clients.
256 259
M docs/rpc.md +1 -1
@@ -23,7 +23,7 @@ You'll see one JSON object per line on stdout: a response acknowledging the prom
2323
2424 ## Flags
2525
26-`zot rpc` accepts the same flags as the other modes: `--provider`, `--model`, `--cwd`, `--api-key`, `--base-url`, `--system-prompt`, `--append-system-prompt`, `--reasoning`, `--max-steps`, `--no-tools`, `--tools`. Sessions are disabled by default in RPC mode — the embedding application owns persistence.
26+`zot rpc` accepts the same flags as the other modes: `--provider`, `--model`, `--cwd`, `--api-key`, `--base-url`, `--system-prompt`, `--append-system-prompt`, `--reasoning`, `--max-steps`, `--no-tools`, `--tools`, `--no-context-files`. Sessions are disabled by default in RPC mode; the embedding application owns persistence.
2727
2828 ## Auth
2929
@@ -23,7 +23,7 @@ You'll see one JSON object per line on stdout: a response acknowledging the prom
23 23
24 ## Flags24 ## Flags
25 25
26-`zot rpc` accepts the same flags as the other modes: `--provider`, `--model`, `--cwd`, `--api-key`, `--base-url`, `--system-prompt`, `--append-system-prompt`, `--reasoning`, `--max-steps`, `--no-tools`, `--tools`. Sessions are disabled by default in RPC mode — the embedding application owns persistence.26+`zot rpc` accepts the same flags as the other modes: `--provider`, `--model`, `--cwd`, `--api-key`, `--base-url`, `--system-prompt`, `--append-system-prompt`, `--reasoning`, `--max-steps`, `--no-tools`, `--tools`, `--no-context-files`. Sessions are disabled by default in RPC mode; the embedding application owns persistence.
27 27
28 ## Auth28 ## Auth
29 29
M packages/agent/args.go +7 -0
@@ -82,6 +82,10 @@ type Args struct {
8282 // skill discovery, including built-ins.
8383 WithSkills bool
8484
85+ // NoContextFiles disables discovery and loading of AGENTS.md files
86+ // from $ZOT_HOME and the cwd's ancestor directories.
87+ NoContextFiles bool
88+
8589 // InsecureTLS skips TLS verification for custom inference endpoints.
8690 InsecureTLS bool
8791
@@ -217,6 +221,8 @@ func ParseArgs(in []string) (Args, error) {
217221 a.NoExt = true
218222 case "--no-skill", "--no-skills":
219223 a.NoSkill = true
224+ case "--no-context-files", "-nc":
225+ a.NoContextFiles = true
220226 case "--with-skills", "--with-skill":
221227 // Deprecated no-op: user skills are loaded by default.
222228 a.WithSkills = true
@@ -438,6 +444,7 @@ func PrintHelp(version string) {
438444 row{"-y, --yes", "accept zot run consent without prompting"},
439445 row{"--no-ext", "skip extension discovery for this run"},
440446 row{"--no-skill", "skip all skill discovery for this run"},
447+ row{"--no-context-files, -nc", "skip AGENTS.md discovery for this run"},
441448 )
442449 section("misc",
443450 row{"--max-steps N", "agent loop iteration cap (default: unlimited)"},
@@ -82,6 +82,10 @@ type Args struct {
82 // skill discovery, including built-ins.82 // skill discovery, including built-ins.
83 WithSkills bool83 WithSkills bool
84 84
85+ // NoContextFiles disables discovery and loading of AGENTS.md files
86+ // from $ZOT_HOME and the cwd's ancestor directories.
87+ NoContextFiles bool
88+
85 // InsecureTLS skips TLS verification for custom inference endpoints.89 // InsecureTLS skips TLS verification for custom inference endpoints.
86 InsecureTLS bool90 InsecureTLS bool
87 91
@@ -217,6 +221,8 @@ func ParseArgs(in []string) (Args, error) {
217 a.NoExt = true221 a.NoExt = true
218 case "--no-skill", "--no-skills":222 case "--no-skill", "--no-skills":
219 a.NoSkill = true223 a.NoSkill = true
224+ case "--no-context-files", "-nc":
225+ a.NoContextFiles = true
220 case "--with-skills", "--with-skill":226 case "--with-skills", "--with-skill":
221 // Deprecated no-op: user skills are loaded by default.227 // Deprecated no-op: user skills are loaded by default.
222 a.WithSkills = true228 a.WithSkills = true
@@ -438,6 +444,7 @@ func PrintHelp(version string) {
438 row{"-y, --yes", "accept zot run consent without prompting"},444 row{"-y, --yes", "accept zot run consent without prompting"},
439 row{"--no-ext", "skip extension discovery for this run"},445 row{"--no-ext", "skip extension discovery for this run"},
440 row{"--no-skill", "skip all skill discovery for this run"},446 row{"--no-skill", "skip all skill discovery for this run"},
447+ row{"--no-context-files, -nc", "skip AGENTS.md discovery for this run"},
441 )448 )
442 section("misc",449 section("misc",
443 row{"--max-steps N", "agent loop iteration cap (default: unlimited)"},450 row{"--max-steps N", "agent loop iteration cap (default: unlimited)"},
M packages/agent/args_test.go +12 -0
@@ -47,6 +47,18 @@ func TestParseArgsStatsRequiresPrintMode(t *testing.T) {
4747 }
4848 }
4949
50+func TestParseArgsNoContextFiles(t *testing.T) {
51+ for _, flag := range []string{"--no-context-files", "-nc"} {
52+ args, err := ParseArgs([]string{flag})
53+ if err != nil {
54+ t.Fatalf("ParseArgs(%q): %v", flag, err)
55+ }
56+ if !args.NoContextFiles {
57+ t.Fatalf("ParseArgs(%q): NoContextFiles = false", flag)
58+ }
59+ }
60+}
61+
5062 func TestParseArgsStream(t *testing.T) {
5163 args, err := ParseArgs([]string{"--stream", "hi"})
5264 if err != nil {
@@ -47,6 +47,18 @@ func TestParseArgsStatsRequiresPrintMode(t *testing.T) {
47 }47 }
48 }48 }
49 49
50+func TestParseArgsNoContextFiles(t *testing.T) {
51+ for _, flag := range []string{"--no-context-files", "-nc"} {
52+ args, err := ParseArgs([]string{flag})
53+ if err != nil {
54+ t.Fatalf("ParseArgs(%q): %v", flag, err)
55+ }
56+ if !args.NoContextFiles {
57+ t.Fatalf("ParseArgs(%q): NoContextFiles = false", flag)
58+ }
59+ }
60+}
61+
50 func TestParseArgsStream(t *testing.T) {62 func TestParseArgsStream(t *testing.T) {
51 args, err := ParseArgs([]string{"--stream", "hi"})63 args, err := ParseArgs([]string{"--stream", "hi"})
52 if err != nil {64 if err != nil {
M packages/agent/build.go +4 -1
@@ -599,7 +599,10 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
599599
600600 summaries := toolSummaries(reg, args)
601601
602- contextFiles := loadAgentsContext(args.CWD, ZotHome())
602+ var contextFiles []ContextFile
603+ if !args.NoContextFiles {
604+ contextFiles = loadAgentsContext(args.CWD, ZotHome())
605+ }
603606 append_ := append([]string(nil), args.AppendSystemPrompt...)
604607 if agentsAddendum := formatAgentsContext(contextFiles); agentsAddendum != "" {
605608 append_ = append(append_, agentsAddendum)
@@ -599,7 +599,10 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
599 599
600 summaries := toolSummaries(reg, args)600 summaries := toolSummaries(reg, args)
601 601
602- contextFiles := loadAgentsContext(args.CWD, ZotHome())602+ var contextFiles []ContextFile
603+ if !args.NoContextFiles {
604+ contextFiles = loadAgentsContext(args.CWD, ZotHome())
605+ }
603 append_ := append([]string(nil), args.AppendSystemPrompt...)606 append_ := append([]string(nil), args.AppendSystemPrompt...)
604 if agentsAddendum := formatAgentsContext(contextFiles); agentsAddendum != "" {607 if agentsAddendum := formatAgentsContext(contextFiles); agentsAddendum != "" {
605 append_ = append(append_, agentsAddendum)608 append_ = append(append_, agentsAddendum)
M packages/agent/build_test.go +29 -0
@@ -69,6 +69,35 @@ func TestReadAgentsContextMissingFilesIsEmpty(t *testing.T) {
6969 }
7070 }
7171
72+func TestResolveNoContextFilesSkipsAgentsInstructions(t *testing.T) {
73+ zotHome := t.TempDir()
74+ cwd := t.TempDir()
75+ t.Setenv("ZOT_HOME", zotHome)
76+ t.Setenv("OPENAI_API_KEY", "test-key")
77+ if err := os.WriteFile(filepath.Join(zotHome, "AGENTS.md"), []byte("global rule"), 0o644); err != nil {
78+ t.Fatal(err)
79+ }
80+ if err := os.WriteFile(filepath.Join(cwd, "AGENTS.md"), []byte("project rule"), 0o644); err != nil {
81+ t.Fatal(err)
82+ }
83+
84+ r, err := Resolve(Args{
85+ Provider: "openai",
86+ Model: "gpt-5",
87+ CWD: cwd,
88+ NoContextFiles: true,
89+ }, false)
90+ if err != nil {
91+ t.Fatal(err)
92+ }
93+ if len(r.ContextFiles) != 0 {
94+ t.Fatalf("ContextFiles = %#v, want empty", r.ContextFiles)
95+ }
96+ if strings.Contains(r.SystemPrompt, "global rule") || strings.Contains(r.SystemPrompt, "project rule") {
97+ t.Fatalf("system prompt contains disabled context files:\n%s", r.SystemPrompt)
98+ }
99+}
100+
72101 // TestResolveFallsBackWhenConfiguredModelIsGone reproduces the
73102 // startup failure caught by the user's screenshot: the persisted
74103 // config.json points at a model id that's no longer in the active
@@ -69,6 +69,35 @@ func TestReadAgentsContextMissingFilesIsEmpty(t *testing.T) {
69 }69 }
70 }70 }
71 71
72+func TestResolveNoContextFilesSkipsAgentsInstructions(t *testing.T) {
73+ zotHome := t.TempDir()
74+ cwd := t.TempDir()
75+ t.Setenv("ZOT_HOME", zotHome)
76+ t.Setenv("OPENAI_API_KEY", "test-key")
77+ if err := os.WriteFile(filepath.Join(zotHome, "AGENTS.md"), []byte("global rule"), 0o644); err != nil {
78+ t.Fatal(err)
79+ }
80+ if err := os.WriteFile(filepath.Join(cwd, "AGENTS.md"), []byte("project rule"), 0o644); err != nil {
81+ t.Fatal(err)
82+ }
83+
84+ r, err := Resolve(Args{
85+ Provider: "openai",
86+ Model: "gpt-5",
87+ CWD: cwd,
88+ NoContextFiles: true,
89+ }, false)
90+ if err != nil {
91+ t.Fatal(err)
92+ }
93+ if len(r.ContextFiles) != 0 {
94+ t.Fatalf("ContextFiles = %#v, want empty", r.ContextFiles)
95+ }
96+ if strings.Contains(r.SystemPrompt, "global rule") || strings.Contains(r.SystemPrompt, "project rule") {
97+ t.Fatalf("system prompt contains disabled context files:\n%s", r.SystemPrompt)
98+ }
99+}
100+
72 // TestResolveFallsBackWhenConfiguredModelIsGone reproduces the101 // TestResolveFallsBackWhenConfiguredModelIsGone reproduces the
73 // startup failure caught by the user's screenshot: the persisted102 // startup failure caught by the user's screenshot: the persisted
74 // config.json points at a model id that's no longer in the active103 // config.json points at a model id that's no longer in the active
M packages/agent/cli.go +17 -15
@@ -389,11 +389,8 @@ func runPrintMode(ctx context.Context, args Args, version string) error {
389389 sess, _ := openOrCreateSession(args, r, ag, version)
390390 defer sess.Close()
391391
392- prompt := args.Prompt
393- if prompt == "" {
394- piped, _ := readAllStdin()
395- prompt = strings.TrimSpace(piped)
396- }
392+ piped, _ := readAllStdin()
393+ prompt := combinePromptInput(piped, args.Prompt)
397394 if prompt == "" {
398395 return fmt.Errorf("print mode requires a prompt (arg or stdin)")
399396 }
@@ -432,11 +429,8 @@ func runStreamMode(ctx context.Context, args Args, version string) error {
432429 sess, _ := openOrCreateSession(args, r, ag, version)
433430 defer sess.Close()
434431
435- prompt := args.Prompt
436- if prompt == "" {
437- piped, _ := readAllStdin()
438- prompt = strings.TrimSpace(piped)
439- }
432+ piped, _ := readAllStdin()
433+ prompt := combinePromptInput(piped, args.Prompt)
440434 if prompt == "" {
441435 return fmt.Errorf("stream mode requires a prompt (arg or stdin)")
442436 }
@@ -514,11 +508,8 @@ func runJSONMode(ctx context.Context, args Args, version string) error {
514508 sess, _ := openOrCreateSession(args, r, ag, version)
515509 defer sess.Close()
516510
517- prompt := args.Prompt
518- if prompt == "" {
519- piped, _ := readAllStdin()
520- prompt = strings.TrimSpace(piped)
521- }
511+ piped, _ := readAllStdin()
512+ prompt := combinePromptInput(piped, args.Prompt)
522513 if prompt == "" {
523514 return fmt.Errorf("json mode requires a prompt (arg or stdin)")
524515 }
@@ -1580,6 +1571,17 @@ func writeNewTranscriptLocked(ag *core.Agent, sess *core.Session, from int) {
15801571 _ = sess.AppendUsage(cum, cum)
15811572 }
15821573
1574+func combinePromptInput(stdin, positional string) string {
1575+ parts := make([]string, 0, 2)
1576+ if stdin = strings.TrimSpace(stdin); stdin != "" {
1577+ parts = append(parts, stdin)
1578+ }
1579+ if positional = strings.TrimSpace(positional); positional != "" {
1580+ parts = append(parts, positional)
1581+ }
1582+ return strings.Join(parts, "\n")
1583+}
1584+
15831585 func readAllStdin() (string, error) {
15841586 fi, err := os.Stdin.Stat()
15851587 if err != nil {
@@ -389,11 +389,8 @@ func runPrintMode(ctx context.Context, args Args, version string) error {
389 sess, _ := openOrCreateSession(args, r, ag, version)389 sess, _ := openOrCreateSession(args, r, ag, version)
390 defer sess.Close()390 defer sess.Close()
391 391
392- prompt := args.Prompt392+ piped, _ := readAllStdin()
393- if prompt == "" {393+ prompt := combinePromptInput(piped, args.Prompt)
394- piped, _ := readAllStdin()
395- prompt = strings.TrimSpace(piped)
396- }
397 if prompt == "" {394 if prompt == "" {
398 return fmt.Errorf("print mode requires a prompt (arg or stdin)")395 return fmt.Errorf("print mode requires a prompt (arg or stdin)")
399 }396 }
@@ -432,11 +429,8 @@ func runStreamMode(ctx context.Context, args Args, version string) error {
432 sess, _ := openOrCreateSession(args, r, ag, version)429 sess, _ := openOrCreateSession(args, r, ag, version)
433 defer sess.Close()430 defer sess.Close()
434 431
435- prompt := args.Prompt432+ piped, _ := readAllStdin()
436- if prompt == "" {433+ prompt := combinePromptInput(piped, args.Prompt)
437- piped, _ := readAllStdin()
438- prompt = strings.TrimSpace(piped)
439- }
440 if prompt == "" {434 if prompt == "" {
441 return fmt.Errorf("stream mode requires a prompt (arg or stdin)")435 return fmt.Errorf("stream mode requires a prompt (arg or stdin)")
442 }436 }
@@ -514,11 +508,8 @@ func runJSONMode(ctx context.Context, args Args, version string) error {
514 sess, _ := openOrCreateSession(args, r, ag, version)508 sess, _ := openOrCreateSession(args, r, ag, version)
515 defer sess.Close()509 defer sess.Close()
516 510
517- prompt := args.Prompt511+ piped, _ := readAllStdin()
518- if prompt == "" {512+ prompt := combinePromptInput(piped, args.Prompt)
519- piped, _ := readAllStdin()
520- prompt = strings.TrimSpace(piped)
521- }
522 if prompt == "" {513 if prompt == "" {
523 return fmt.Errorf("json mode requires a prompt (arg or stdin)")514 return fmt.Errorf("json mode requires a prompt (arg or stdin)")
524 }515 }
@@ -1580,6 +1571,17 @@ func writeNewTranscriptLocked(ag *core.Agent, sess *core.Session, from int) {
1580 _ = sess.AppendUsage(cum, cum)1571 _ = sess.AppendUsage(cum, cum)
1581 }1572 }
1582 1573
1574+func combinePromptInput(stdin, positional string) string {
1575+ parts := make([]string, 0, 2)
1576+ if stdin = strings.TrimSpace(stdin); stdin != "" {
1577+ parts = append(parts, stdin)
1578+ }
1579+ if positional = strings.TrimSpace(positional); positional != "" {
1580+ parts = append(parts, positional)
1581+ }
1582+ return strings.Join(parts, "\n")
1583+}
1584+
1583 func readAllStdin() (string, error) {1585 func readAllStdin() (string, error) {
1584 fi, err := os.Stdin.Stat()1586 fi, err := os.Stdin.Stat()
1585 if err != nil {1587 if err != nil {
A packages/agent/prompt_input_test.go +25 -0
@@ -0,0 +1,25 @@
1+package agent
2+
3+import "testing"
4+
5+func TestCombinePromptInput(t *testing.T) {
6+ tests := []struct {
7+ name string
8+ stdin string
9+ positional string
10+ want string
11+ }{
12+ {name: "stdin and instruction", stdin: "input text\n", positional: "summarize it", want: "input text\nsummarize it"},
13+ {name: "stdin only", stdin: " input text\n", want: "input text"},
14+ {name: "positional only", positional: " summarize it ", want: "summarize it"},
15+ {name: "empty", stdin: " \n", positional: " ", want: ""},
16+ }
17+
18+ for _, tc := range tests {
19+ t.Run(tc.name, func(t *testing.T) {
20+ if got := combinePromptInput(tc.stdin, tc.positional); got != tc.want {
21+ t.Fatalf("combinePromptInput(%q, %q) = %q, want %q", tc.stdin, tc.positional, got, tc.want)
22+ }
23+ })
24+ }
25+}
@@ -0,0 +1,25 @@
1+package agent
2+
3+import "testing"
4+
5+func TestCombinePromptInput(t *testing.T) {
6+ tests := []struct {
7+ name string
8+ stdin string
9+ positional string
10+ want string
11+ }{
12+ {name: "stdin and instruction", stdin: "input text\n", positional: "summarize it", want: "input text\nsummarize it"},
13+ {name: "stdin only", stdin: " input text\n", want: "input text"},
14+ {name: "positional only", positional: " summarize it ", want: "summarize it"},
15+ {name: "empty", stdin: " \n", positional: " ", want: ""},
16+ }
17+
18+ for _, tc := range tests {
19+ t.Run(tc.name, func(t *testing.T) {
20+ if got := combinePromptInput(tc.stdin, tc.positional); got != tc.want {
21+ t.Fatalf("combinePromptInput(%q, %q) = %q, want %q", tc.stdin, tc.positional, got, tc.want)
22+ }
23+ })
24+ }
25+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close