sigit-code 1.6.0__tar.gz → 1.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/AGENTS.md +46 -2
- sigit_code-1.6.2/.sigit/factory/config.toml +62 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/AGENTS.md +46 -2
- {sigit_code-1.6.0 → sigit_code-1.6.2}/CHANGELOG.md +77 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/CLAUDE.md +46 -2
- {sigit_code-1.6.0 → sigit_code-1.6.2}/Cargo.lock +2 -1
- {sigit_code-1.6.0 → sigit_code-1.6.2}/Cargo.toml +6 -1
- {sigit_code-1.6.0 → sigit_code-1.6.2}/PKG-INFO +1 -1
- {sigit_code-1.6.0 → sigit_code-1.6.2}/docs/headless.md +5 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/backend.rs +399 -19
- sigit_code-1.6.2/src/client_fs.rs +350 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/headless.rs +155 -5
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/main.rs +545 -78
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/provider.rs +56 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/tools.rs +327 -180
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/workspace.rs +1 -1
- sigit_code-1.6.2/tests/acp_client_fs.rs +549 -0
- sigit_code-1.6.2/tests/acp_images.rs +318 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/tests/acp_permissions.rs +93 -0
- sigit_code-1.6.2/tests/acp_stop_reasons.rs +439 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/tests/headless_mode.rs +109 -0
- sigit_code-1.6.2/tests/version_flag.rs +32 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/skills/agent-client-protocol/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/skills/ai-assisted-coding/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/skills/branding/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/skills/run-sigit/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/skills/run-sigit/driver.mjs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/skills/run-sigit/tui-smoke.sh +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/skills/sigit-code-release/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.agents/skills/tool-calling/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.cargo/config.toml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.claude/skills/agent-client-protocol/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.claude/skills/ai-assisted-coding/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.claude/skills/branding/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.claude/skills/run-sigit/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.claude/skills/run-sigit/driver.mjs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.claude/skills/run-sigit/tui-smoke.sh +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.claude/skills/sigit-code-release/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.claude/skills/tool-calling/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/ci.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-aur.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-crates.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-github.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-homebrew.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-npm.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-nuget.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-pypi.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-scoop.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.github/workflows/release-winget.yml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.gitignore +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/.nvmrc +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/LICENSE +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/README.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/docs/hooks.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/docs/mcp.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/examples/settings-with-hooks.toml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/examples/skills/README.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/examples/skills/commit-message/SKILL.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/README.md.tmpl +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/package-compat.json.tmpl +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/package-main.json.tmpl +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/package.json.tmpl +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/scripts/render-main-package.cjs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/scripts/render-platform-package.cjs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/sigit/.gitignore +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/sigit/README.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/sigit/package.json +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/sigit/src/index.ts +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/npm/sigit/tsconfig.json +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/nuget/.gitignore +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/nuget/sigit/Program.cs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/nuget/sigit/README.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/nuget/sigit/SiGit.Code.csproj +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/packaging/aur/PKGBUILD.in +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/packaging/nfpm.yaml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/packaging/windows/check-static-crt.ps1 +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/packaging/winget/getSigit.siGitCode.installer.yaml.in +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/packaging/winget/getSigit.siGitCode.locale.en-US.yaml.in +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/packaging/winget/getSigit.siGitCode.yaml.in +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/pypi/README.md +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/pypi/pyproject.toml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/pyproject.toml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/rust-toolchain.toml +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/account.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/browser_auth.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/chat.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/commands.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/credentials.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/frontmatter.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/hooks.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/inline_tool_calls.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/instructions.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/mcp.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/models.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/permissions.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/session_store.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/settings.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/setup.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/skills.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/src/subagents.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/tests/acp_endpoint_errors.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/tests/acp_multi_root.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/tests/acp_session_list.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/tests/acp_session_load.rs +0 -0
- {sigit_code-1.6.0 → sigit_code-1.6.2}/tests/acp_tool_stdin.rs +0 -0
|
@@ -127,10 +127,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
127
127
|
endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
|
|
128
128
|
the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
|
|
129
129
|
explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
|
|
130
|
+
`TurnResult::finish` carries the endpoint's `finish_reason` up to the loop, and
|
|
131
|
+
`stop_reason_for` in `main.rs` turns it into the ACP stop reason: the tool-round cap is
|
|
132
|
+
`max_turn_requests`, `length` is `max_tokens`, `content_filter` is `refusal`. ACP defines a
|
|
133
|
+
refusal as a turn the next prompt will not include, so `handle_prompt` restores the history
|
|
134
|
+
it snapshotted before the turn instead of only relabelling the response.
|
|
135
|
+
The round that reaches the cap (24, or `SIGIT_MAX_TOOL_ROUNDS`) is followed by one forced
|
|
136
|
+
text reply with no tools. The model cannot see that the tools are gone, so
|
|
137
|
+
`round_cap_note` is appended to that round's last tool result, and the turn always closes
|
|
138
|
+
with `round_cap_stop_message`. Without both, the thread stops on the model announcing a
|
|
139
|
+
step that never runs (issue #120).
|
|
130
140
|
Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
|
|
131
141
|
well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
|
|
132
142
|
and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
|
|
133
143
|
didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
|
|
144
|
+
Image attachments: ACP fixes `promptCapabilities.image` for the whole connection, while the
|
|
145
|
+
model can change on any turn, so the capability is always advertised and the decision is made
|
|
146
|
+
per prompt. `InferenceBackend::accepts_images` answers for the active model (on-device: no;
|
|
147
|
+
remote: `provider::model_accepts_images`, which reads the hard-coded `IMAGE_TIERS` for
|
|
148
|
+
`onde-*` ids and says yes for a user's own endpoint). `images_for_turn` in `main.rs` either
|
|
149
|
+
passes the images to `send_message_with_images` or drops them with a note to the user and a
|
|
150
|
+
bracketed note to the model. A message with images is stored in history as OpenAI content
|
|
151
|
+
parts and stays that way; `OpenAiBackend` strips the images per request (`without_images`)
|
|
152
|
+
when its model cannot read them, which is what makes a mid-thread switch to a text-only tier
|
|
153
|
+
safe. Read history text through `backend::message_text`, never `content.as_str()`.
|
|
154
|
+
`IMAGE_TIERS` mirrors onde-cloud's `IMAGE_CANDIDATES`; update both together.
|
|
134
155
|
- **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
|
|
135
156
|
wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
|
|
136
157
|
`~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
|
|
@@ -243,7 +264,11 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
243
264
|
overrides and the default mode from `[permissions]` in `settings.toml` (`allow`/`ask`/`deny`,
|
|
244
265
|
default `ask`; `SIGIT_PERMISSIONS` env overrides the default). On `ask`, the ACP path sends
|
|
245
266
|
`session/request_permission` (allow once / allow for session / deny) and the TUI pauses the
|
|
246
|
-
inference task on a y/a/n prompt.
|
|
267
|
+
inference task on a y/a/n prompt. On the ACP path the decision is taken *before* the call is
|
|
268
|
+
announced, because it sets the announced status: a call that will ask starts `pending`, the
|
|
269
|
+
permission request carries that call's own id, and an `in_progress` update follows approval.
|
|
270
|
+
That update also puts the card's title back, since the permission request overwrites it
|
|
271
|
+
with the full arguments. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
|
|
247
272
|
serialized by `SiGitAgent::turn_lock` so the dispatch loop can route the client's permission
|
|
248
273
|
answer mid-turn — don't move them back inline, and don't await client requests from inline
|
|
249
274
|
handlers (deadlock).
|
|
@@ -274,6 +299,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
274
299
|
every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
|
|
275
300
|
any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell. (The servers a
|
|
276
301
|
client names in `mcpServers` are a different thing and are per session; see `src/mcp.rs`.)
|
|
302
|
+
- **`src/client_fs.rs`** — file reads and writes through the ACP client. A client that
|
|
303
|
+
advertises `fs.readTextFile` / `fs.writeTextFile` in `initialize` serves `fs/read_text_file`
|
|
304
|
+
and `fs/write_text_file` from its buffers, so `read_file` sees unsaved changes and
|
|
305
|
+
`edit_file` lands in the open buffer instead of underneath it. `tools.rs` has no access to the
|
|
306
|
+
connection, so this is a seam like the one `mcp::call_tool` gives MCP: `handle_initialize`
|
|
307
|
+
registers a `ClientFileSystem` (`AcpClientFs` in `main.rs`, holding the connection), and
|
|
308
|
+
`execute_tool_impl` asks `route_for` before it dispatches `read_file`, `create_file`,
|
|
309
|
+
`edit_file` or `multi_edit`. Each of those tools is split into a parse step and a pure
|
|
310
|
+
render/apply step (`ReadFileCall`, `CreateFileCall`, `EditCall`) that the disk path and the
|
|
311
|
+
client path share, so the model gets the same result text either way. The two capabilities are
|
|
312
|
+
independent: a client that only reads still gets its edits written to disk. The disk is the
|
|
313
|
+
fallback throughout: nothing is registered in the TUI or headless modes, a path outside the
|
|
314
|
+
session's roots is not routed, and a request the client fails or leaves unanswered for 30
|
|
315
|
+
seconds is retried on disk with a warning in the log. The client is only ever asked from
|
|
316
|
+
inside a spawned prompt turn, never from `handle_initialize` itself (deadlock, same as
|
|
317
|
+
permission requests). Existence checks and `create_file`'s parent directories still use the
|
|
318
|
+
disk. `SIGIT_CLIENT_FS=off` turns the routing off. Covered by `tests/acp_client_fs.rs`.
|
|
277
319
|
- **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
|
|
278
320
|
`tokio::select!` to multiplex terminal events with streaming tokens.
|
|
279
321
|
- **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
|
|
@@ -337,7 +379,9 @@ verbosity with `RUST_LOG`.
|
|
|
337
379
|
|
|
338
380
|
`OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
|
|
339
381
|
default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
|
|
340
|
-
`SIGIT_MODEL`, `
|
|
382
|
+
`SIGIT_MODEL`, `SIGIT_MAX_TOOL_ROUNDS` (1 to 500, default 24; the tool-round cap of a
|
|
383
|
+
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `SIGIT_CLIENT_FS` (`off` keeps the file tools on disk even when the
|
|
384
|
+
ACP client offers `fs/read_text_file` / `fs/write_text_file`), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
|
|
341
385
|
smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
|
|
342
386
|
server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
|
|
343
387
|
mutating tools; the escape hatch for clients without permission-request support),
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[repo]
|
|
2
|
+
owner = "getsigit"
|
|
3
|
+
name = "sigit"
|
|
4
|
+
slug = "getsigit-sigit"
|
|
5
|
+
|
|
6
|
+
[agent]
|
|
7
|
+
login = "sigit-code-review"
|
|
8
|
+
display_name = "siGit Code Review"
|
|
9
|
+
|
|
10
|
+
[harvest]
|
|
11
|
+
state = "closed"
|
|
12
|
+
limit = 50
|
|
13
|
+
since = "2026-09-01"
|
|
14
|
+
cadence = "weekly"
|
|
15
|
+
day_of_week = "Mon"
|
|
16
|
+
runs_dir = "~/.agents/skills/sigit-code-review-outer-loop/runs"
|
|
17
|
+
report_format = "markdown"
|
|
18
|
+
compare_previous = true
|
|
19
|
+
|
|
20
|
+
[review.reviewer_skill]
|
|
21
|
+
name = "sigit-code-review-outer-loop"
|
|
22
|
+
version = "1.0.0"
|
|
23
|
+
|
|
24
|
+
[review.author_skill]
|
|
25
|
+
name = "sigit-code-review-feedbacks-response"
|
|
26
|
+
version = "1.0.0"
|
|
27
|
+
|
|
28
|
+
[review.repo_guidance]
|
|
29
|
+
branch_rules = "{fix,feature,release}/*"
|
|
30
|
+
ci_gate = "CI"
|
|
31
|
+
watch_paths = ["src/**"]
|
|
32
|
+
ignore_paths = ["**/*.md", "**/*.txt", "docs/**", "vendor/**", ".github/**", "*.lock"]
|
|
33
|
+
|
|
34
|
+
[[guardrails.known_issues]]
|
|
35
|
+
pattern = "all_zero_summary"
|
|
36
|
+
source = "getsigit-sigit"
|
|
37
|
+
description = "Zero findings on a large control-flow diff may be recall loss from an over-corrected prompt bar, not a clean review. Seen on stack #163 (900 additions, 0 findings)."
|
|
38
|
+
action = "flag_in_report"
|
|
39
|
+
|
|
40
|
+
[[guardrails.known_issues]]
|
|
41
|
+
pattern = "confident_false_positives"
|
|
42
|
+
source = "getsigit-sigit"
|
|
43
|
+
description = "Confident declarative findings whose premise is contradicted by the cited code. Seen on stack #160 (5 of 5 findings rejected, rejected_rate 1.0). The hedge ban replaced speculative questions with confident wrong claims."
|
|
44
|
+
action = "flag_in_report"
|
|
45
|
+
|
|
46
|
+
[[guardrails.known_issues]]
|
|
47
|
+
pattern = "approval_on_disagree"
|
|
48
|
+
source = "getsigit-sigit"
|
|
49
|
+
description = "Agent approves within 30-45s of an author disagreement without processing the correction. Compounds with confident false positives: wrong claim, author corrects, agent approves without re-reading."
|
|
50
|
+
action = "flag_in_report"
|
|
51
|
+
|
|
52
|
+
[[guardrails.known_issues]]
|
|
53
|
+
pattern = "repeat_findings_high"
|
|
54
|
+
source = "getsigit-sigit-si"
|
|
55
|
+
description = "Repeat findings above 30% indicate the agent is re-raising issues the author already addressed."
|
|
56
|
+
action = "flag_in_report"
|
|
57
|
+
|
|
58
|
+
[[guardrails.known_issues]]
|
|
59
|
+
pattern = "force_pushes_inflate_rounds"
|
|
60
|
+
source = "smbcloudXYZ-smbcloud"
|
|
61
|
+
description = "Force pushes cause one-for-one review round inflation; the agent re-reads unchanged diffs."
|
|
62
|
+
action = "note_in_report"
|
|
@@ -127,10 +127,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
127
127
|
endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
|
|
128
128
|
the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
|
|
129
129
|
explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
|
|
130
|
+
`TurnResult::finish` carries the endpoint's `finish_reason` up to the loop, and
|
|
131
|
+
`stop_reason_for` in `main.rs` turns it into the ACP stop reason: the tool-round cap is
|
|
132
|
+
`max_turn_requests`, `length` is `max_tokens`, `content_filter` is `refusal`. ACP defines a
|
|
133
|
+
refusal as a turn the next prompt will not include, so `handle_prompt` restores the history
|
|
134
|
+
it snapshotted before the turn instead of only relabelling the response.
|
|
135
|
+
The round that reaches the cap (24, or `SIGIT_MAX_TOOL_ROUNDS`) is followed by one forced
|
|
136
|
+
text reply with no tools. The model cannot see that the tools are gone, so
|
|
137
|
+
`round_cap_note` is appended to that round's last tool result, and the turn always closes
|
|
138
|
+
with `round_cap_stop_message`. Without both, the thread stops on the model announcing a
|
|
139
|
+
step that never runs (issue #120).
|
|
130
140
|
Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
|
|
131
141
|
well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
|
|
132
142
|
and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
|
|
133
143
|
didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
|
|
144
|
+
Image attachments: ACP fixes `promptCapabilities.image` for the whole connection, while the
|
|
145
|
+
model can change on any turn, so the capability is always advertised and the decision is made
|
|
146
|
+
per prompt. `InferenceBackend::accepts_images` answers for the active model (on-device: no;
|
|
147
|
+
remote: `provider::model_accepts_images`, which reads the hard-coded `IMAGE_TIERS` for
|
|
148
|
+
`onde-*` ids and says yes for a user's own endpoint). `images_for_turn` in `main.rs` either
|
|
149
|
+
passes the images to `send_message_with_images` or drops them with a note to the user and a
|
|
150
|
+
bracketed note to the model. A message with images is stored in history as OpenAI content
|
|
151
|
+
parts and stays that way; `OpenAiBackend` strips the images per request (`without_images`)
|
|
152
|
+
when its model cannot read them, which is what makes a mid-thread switch to a text-only tier
|
|
153
|
+
safe. Read history text through `backend::message_text`, never `content.as_str()`.
|
|
154
|
+
`IMAGE_TIERS` mirrors onde-cloud's `IMAGE_CANDIDATES`; update both together.
|
|
134
155
|
- **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
|
|
135
156
|
wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
|
|
136
157
|
`~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
|
|
@@ -243,7 +264,11 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
243
264
|
overrides and the default mode from `[permissions]` in `settings.toml` (`allow`/`ask`/`deny`,
|
|
244
265
|
default `ask`; `SIGIT_PERMISSIONS` env overrides the default). On `ask`, the ACP path sends
|
|
245
266
|
`session/request_permission` (allow once / allow for session / deny) and the TUI pauses the
|
|
246
|
-
inference task on a y/a/n prompt.
|
|
267
|
+
inference task on a y/a/n prompt. On the ACP path the decision is taken *before* the call is
|
|
268
|
+
announced, because it sets the announced status: a call that will ask starts `pending`, the
|
|
269
|
+
permission request carries that call's own id, and an `in_progress` update follows approval.
|
|
270
|
+
That update also puts the card's title back, since the permission request overwrites it
|
|
271
|
+
with the full arguments. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
|
|
247
272
|
serialized by `SiGitAgent::turn_lock` so the dispatch loop can route the client's permission
|
|
248
273
|
answer mid-turn — don't move them back inline, and don't await client requests from inline
|
|
249
274
|
handlers (deadlock).
|
|
@@ -274,6 +299,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
274
299
|
every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
|
|
275
300
|
any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell. (The servers a
|
|
276
301
|
client names in `mcpServers` are a different thing and are per session; see `src/mcp.rs`.)
|
|
302
|
+
- **`src/client_fs.rs`** — file reads and writes through the ACP client. A client that
|
|
303
|
+
advertises `fs.readTextFile` / `fs.writeTextFile` in `initialize` serves `fs/read_text_file`
|
|
304
|
+
and `fs/write_text_file` from its buffers, so `read_file` sees unsaved changes and
|
|
305
|
+
`edit_file` lands in the open buffer instead of underneath it. `tools.rs` has no access to the
|
|
306
|
+
connection, so this is a seam like the one `mcp::call_tool` gives MCP: `handle_initialize`
|
|
307
|
+
registers a `ClientFileSystem` (`AcpClientFs` in `main.rs`, holding the connection), and
|
|
308
|
+
`execute_tool_impl` asks `route_for` before it dispatches `read_file`, `create_file`,
|
|
309
|
+
`edit_file` or `multi_edit`. Each of those tools is split into a parse step and a pure
|
|
310
|
+
render/apply step (`ReadFileCall`, `CreateFileCall`, `EditCall`) that the disk path and the
|
|
311
|
+
client path share, so the model gets the same result text either way. The two capabilities are
|
|
312
|
+
independent: a client that only reads still gets its edits written to disk. The disk is the
|
|
313
|
+
fallback throughout: nothing is registered in the TUI or headless modes, a path outside the
|
|
314
|
+
session's roots is not routed, and a request the client fails or leaves unanswered for 30
|
|
315
|
+
seconds is retried on disk with a warning in the log. The client is only ever asked from
|
|
316
|
+
inside a spawned prompt turn, never from `handle_initialize` itself (deadlock, same as
|
|
317
|
+
permission requests). Existence checks and `create_file`'s parent directories still use the
|
|
318
|
+
disk. `SIGIT_CLIENT_FS=off` turns the routing off. Covered by `tests/acp_client_fs.rs`.
|
|
277
319
|
- **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
|
|
278
320
|
`tokio::select!` to multiplex terminal events with streaming tokens.
|
|
279
321
|
- **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
|
|
@@ -337,7 +379,9 @@ verbosity with `RUST_LOG`.
|
|
|
337
379
|
|
|
338
380
|
`OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
|
|
339
381
|
default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
|
|
340
|
-
`SIGIT_MODEL`, `
|
|
382
|
+
`SIGIT_MODEL`, `SIGIT_MAX_TOOL_ROUNDS` (1 to 500, default 24; the tool-round cap of a
|
|
383
|
+
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `SIGIT_CLIENT_FS` (`off` keeps the file tools on disk even when the
|
|
384
|
+
ACP client offers `fs/read_text_file` / `fs/write_text_file`), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
|
|
341
385
|
smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
|
|
342
386
|
server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
|
|
343
387
|
mutating tools; the escape hatch for clients without permission-request support),
|
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.2
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`sigit --version` prints the release.** `sigit --version` and `sigit -V`
|
|
8
|
+
print `sigit <version>` on stdout and exit 0. Before, the flag was not
|
|
9
|
+
recognized and siGit Code started a session, which left a script that asked
|
|
10
|
+
for the version waiting.
|
|
11
|
+
|
|
12
|
+
- **File tools read and write through the editor when it offers to.** An ACP
|
|
13
|
+
client that advertises `fs.readTextFile` or `fs.writeTextFile` serves those
|
|
14
|
+
requests from its open buffers. siGit Code always used the disk, so
|
|
15
|
+
`read_file` missed unsaved changes and `edit_file` wrote underneath a buffer
|
|
16
|
+
the user might have modified. `read_file`, `create_file`, `edit_file` and
|
|
17
|
+
`multi_edit` now go through the client for paths inside the session's roots.
|
|
18
|
+
The two capabilities are independent, and the disk is still the fallback:
|
|
19
|
+
for a path outside the roots, and for a request the client fails or leaves
|
|
20
|
+
unanswered for 30 seconds. `SIGIT_CLIENT_FS=off` keeps the file tools on
|
|
21
|
+
disk (#147).
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **A file attached from the editor is found when its path needs escaping.**
|
|
26
|
+
A `resource_link` was read by stripping `file://` and using the rest as the
|
|
27
|
+
path, so a space arrived as `%20`, a non-ASCII name failed, and a Windows
|
|
28
|
+
URI kept its leading slash. A `#` in a file name was also taken for the
|
|
29
|
+
line-range fragment. The URI is now parsed properly (#170).
|
|
30
|
+
|
|
31
|
+
## 1.6.1
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Images can be attached to a prompt in the editor.** siGit Code now
|
|
36
|
+
advertises `promptCapabilities.image`, so an ACP client offers image
|
|
37
|
+
attachments. The image goes to the model when the active model reads images.
|
|
38
|
+
The model picker marks those models with "reads images". The on-device
|
|
39
|
+
models and four cloud tiers (`nova`, `orbit`, `apex`, `flux`) are text-only:
|
|
40
|
+
with one of them active the image is left out, a note in the thread says so,
|
|
41
|
+
and the model is told an image was withheld. Switching a thread that already
|
|
42
|
+
holds images to a text-only model flags that once as well. Cloud tiers need
|
|
43
|
+
siGit Code Cloud to accept image parts, which is rolling out separately
|
|
44
|
+
(#134).
|
|
45
|
+
|
|
46
|
+
- **The tool-round cap of a headless run can be set.** `--max-tool-rounds <n>`
|
|
47
|
+
or `SIGIT_MAX_TOOL_ROUNDS` (1 to 500) replaces the built-in 24 for
|
|
48
|
+
`sigit run` and `sigit -p`. The flag wins over the variable.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **A prompt turn now reports why it stopped.** ACP defines five stop reasons
|
|
53
|
+
and siGit Code only ever sent `end_turn` and `cancelled`. A turn that runs
|
|
54
|
+
into the tool-round cap now ends with `max_turn_requests`, a reply cut off
|
|
55
|
+
at the token limit with `max_tokens`, and a reply the endpoint withheld
|
|
56
|
+
(`content_filter`) with `refusal`. A refused turn is also taken out of the
|
|
57
|
+
conversation, which is what the protocol tells the client to expect (#140).
|
|
58
|
+
|
|
59
|
+
- **A tool call that needs approval is one card, not two.** The permission
|
|
60
|
+
request carried an id of its own, so an ACP client showed an approval card
|
|
61
|
+
beside a call that already claimed to be running. The call is now announced
|
|
62
|
+
as `pending`, the permission request names that same call, and it moves to
|
|
63
|
+
`in_progress` once approved. Calls allowed by policy still start out
|
|
64
|
+
`in_progress` (#138).
|
|
65
|
+
|
|
66
|
+
- **A turn that runs out of tool rounds says so.** In an editor, a long task
|
|
67
|
+
could stop on a line like "Now commit:" with nothing after it. The turn had
|
|
68
|
+
used all 24 tool rounds, the model spent its last reply announcing the next
|
|
69
|
+
step, and the call behind it was dropped. siGit Code now tells the model
|
|
70
|
+
that its rounds are used up before that last reply, and ends the turn with a
|
|
71
|
+
message saying why it stopped and that "continue" picks it back up.
|
|
72
|
+
`SIGIT_MAX_TOOL_ROUNDS` now sets the cap for editor sessions too (#120).
|
|
73
|
+
|
|
74
|
+
- **A headless run no longer ends silently with no final message.** When the
|
|
75
|
+
last round came back empty, which is what happens when a run is cut off at
|
|
76
|
+
the tool-round cap mid-task, stdout was empty and the exit status was 0, so
|
|
77
|
+
a caller could not tell it from a run with nothing to report. siGit Code now
|
|
78
|
+
asks the model once for its final message before finishing.
|
|
79
|
+
|
|
3
80
|
## 1.6.0
|
|
4
81
|
|
|
5
82
|
### Added
|
|
@@ -127,10 +127,31 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
127
127
|
endpoint can also fail *after* the response is open, reporting it as a `data:` frame holding
|
|
128
128
|
the same envelope; that frame has no `choices`, so `consume_stream` has to check for it
|
|
129
129
|
explicitly or it parses as an empty chunk and the turn ends looking like an empty answer.
|
|
130
|
+
`TurnResult::finish` carries the endpoint's `finish_reason` up to the loop, and
|
|
131
|
+
`stop_reason_for` in `main.rs` turns it into the ACP stop reason: the tool-round cap is
|
|
132
|
+
`max_turn_requests`, `length` is `max_tokens`, `content_filter` is `refusal`. ACP defines a
|
|
133
|
+
refusal as a turn the next prompt will not include, so `handle_prompt` restores the history
|
|
134
|
+
it snapshotted before the turn instead of only relabelling the response.
|
|
135
|
+
The round that reaches the cap (24, or `SIGIT_MAX_TOOL_ROUNDS`) is followed by one forced
|
|
136
|
+
text reply with no tools. The model cannot see that the tools are gone, so
|
|
137
|
+
`round_cap_note` is appended to that round's last tool result, and the turn always closes
|
|
138
|
+
with `round_cap_stop_message`. Without both, the thread stops on the model announcing a
|
|
139
|
+
step that never runs (issue #120).
|
|
130
140
|
Some models write tool calls into content as text; `src/inline_tool_calls.rs` recovers the
|
|
131
141
|
well-formed ones. A block that doesn't parse (or never closes) is dropped from both the reply
|
|
132
142
|
and history, and `OpenAiBackend::complete` retries once with a note telling the model the call
|
|
133
143
|
didn't run. Leaving the raw block in history makes the model invent `<function_results>` later.
|
|
144
|
+
Image attachments: ACP fixes `promptCapabilities.image` for the whole connection, while the
|
|
145
|
+
model can change on any turn, so the capability is always advertised and the decision is made
|
|
146
|
+
per prompt. `InferenceBackend::accepts_images` answers for the active model (on-device: no;
|
|
147
|
+
remote: `provider::model_accepts_images`, which reads the hard-coded `IMAGE_TIERS` for
|
|
148
|
+
`onde-*` ids and says yes for a user's own endpoint). `images_for_turn` in `main.rs` either
|
|
149
|
+
passes the images to `send_message_with_images` or drops them with a note to the user and a
|
|
150
|
+
bracketed note to the model. A message with images is stored in history as OpenAI content
|
|
151
|
+
parts and stays that way; `OpenAiBackend` strips the images per request (`without_images`)
|
|
152
|
+
when its model cannot read them, which is what makes a mid-thread switch to a text-only tier
|
|
153
|
+
safe. Read history text through `backend::message_text`, never `content.as_str()`.
|
|
154
|
+
`IMAGE_TIERS` mirrors onde-cloud's `IMAGE_CANDIDATES`; update both together.
|
|
134
155
|
- **`src/provider.rs`** — decides *which* backend serves inference. Resolution order, first match
|
|
135
156
|
wins: (1) override via `OPENAI_BASE_URL`+`OPENAI_API_KEY` or active profile in
|
|
136
157
|
`~/.config/sigit/providers.toml`; (2) siGit Code Cloud when logged in; (3) on-device.
|
|
@@ -243,7 +264,11 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
243
264
|
overrides and the default mode from `[permissions]` in `settings.toml` (`allow`/`ask`/`deny`,
|
|
244
265
|
default `ask`; `SIGIT_PERMISSIONS` env overrides the default). On `ask`, the ACP path sends
|
|
245
266
|
`session/request_permission` (allow once / allow for session / deny) and the TUI pauses the
|
|
246
|
-
inference task on a y/a/n prompt.
|
|
267
|
+
inference task on a y/a/n prompt. On the ACP path the decision is taken *before* the call is
|
|
268
|
+
announced, because it sets the announced status: a call that will ask starts `pending`, the
|
|
269
|
+
permission request carries that call's own id, and an `in_progress` update follows approval.
|
|
270
|
+
That update also puts the card's title back, since the permission request overwrites it
|
|
271
|
+
with the full arguments. Note: ACP turn-affecting handlers run in `cx.spawn`ed tasks
|
|
247
272
|
serialized by `SiGitAgent::turn_lock` so the dispatch loop can route the client's permission
|
|
248
273
|
answer mid-turn — don't move them back inline, and don't await client requests from inline
|
|
249
274
|
handlers (deadlock).
|
|
@@ -274,6 +299,23 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
|
|
|
274
299
|
every root. MCP is deliberately not on that list — `mcp::init` runs once at startup, before
|
|
275
300
|
any session exists, so a second root's `.sigit/mcp.toml` has nobody to tell. (The servers a
|
|
276
301
|
client names in `mcpServers` are a different thing and are per session; see `src/mcp.rs`.)
|
|
302
|
+
- **`src/client_fs.rs`** — file reads and writes through the ACP client. A client that
|
|
303
|
+
advertises `fs.readTextFile` / `fs.writeTextFile` in `initialize` serves `fs/read_text_file`
|
|
304
|
+
and `fs/write_text_file` from its buffers, so `read_file` sees unsaved changes and
|
|
305
|
+
`edit_file` lands in the open buffer instead of underneath it. `tools.rs` has no access to the
|
|
306
|
+
connection, so this is a seam like the one `mcp::call_tool` gives MCP: `handle_initialize`
|
|
307
|
+
registers a `ClientFileSystem` (`AcpClientFs` in `main.rs`, holding the connection), and
|
|
308
|
+
`execute_tool_impl` asks `route_for` before it dispatches `read_file`, `create_file`,
|
|
309
|
+
`edit_file` or `multi_edit`. Each of those tools is split into a parse step and a pure
|
|
310
|
+
render/apply step (`ReadFileCall`, `CreateFileCall`, `EditCall`) that the disk path and the
|
|
311
|
+
client path share, so the model gets the same result text either way. The two capabilities are
|
|
312
|
+
independent: a client that only reads still gets its edits written to disk. The disk is the
|
|
313
|
+
fallback throughout: nothing is registered in the TUI or headless modes, a path outside the
|
|
314
|
+
session's roots is not routed, and a request the client fails or leaves unanswered for 30
|
|
315
|
+
seconds is retried on disk with a warning in the log. The client is only ever asked from
|
|
316
|
+
inside a spawned prompt turn, never from `handle_initialize` itself (deadlock, same as
|
|
317
|
+
permission requests). Existence checks and `create_file`'s parent directories still use the
|
|
318
|
+
disk. `SIGIT_CLIENT_FS=off` turns the routing off. Covered by `tests/acp_client_fs.rs`.
|
|
277
319
|
- **`src/chat.rs`** — the Unix-only ratatui TUI. Loading-spinner phase then chat; uses
|
|
278
320
|
`tokio::select!` to multiplex terminal events with streaming tokens.
|
|
279
321
|
- **`src/headless.rs`** — non-interactive `sigit run` execution for scripts, CI, and Factory
|
|
@@ -337,7 +379,9 @@ verbosity with `RUST_LOG`.
|
|
|
337
379
|
|
|
338
380
|
`OPENAI_BASE_URL` / `OPENAI_API_KEY` (provider override), `SIGIT_API_URL` (account API base,
|
|
339
381
|
default `https://sigit.si`), `SIGIT_CLOUD_URL`, `SIGIT_CONFIG_DIR` (default `~/.config/sigit`),
|
|
340
|
-
`SIGIT_MODEL`, `
|
|
382
|
+
`SIGIT_MODEL`, `SIGIT_MAX_TOOL_ROUNDS` (1 to 500, default 24; the tool-round cap of a
|
|
383
|
+
headless run or an ACP prompt turn), `SIGIT_MCP` (`off` disables MCP), `SIGIT_CLIENT_FS` (`off` keeps the file tools on disk even when the
|
|
384
|
+
ACP client offers `fs/read_text_file` / `fs/write_text_file`), `SIGIT_MCP_SMBCLOUD` (`off` drops the baked-in
|
|
341
385
|
smbCloud CLI server), `SIGIT_MCP_OFFICIAL` (`off` drops the baked-in
|
|
342
386
|
server), `SIGIT_PERMISSIONS` (`allow`/`ask`/`deny` — overrides the default permission mode for
|
|
343
387
|
mutating tools; the escape hatch for clients without permission-request support),
|
|
@@ -5778,7 +5778,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
|
5778
5778
|
|
|
5779
5779
|
[[package]]
|
|
5780
5780
|
name = "sigit"
|
|
5781
|
-
version = "1.6.
|
|
5781
|
+
version = "1.6.2"
|
|
5782
5782
|
dependencies = [
|
|
5783
5783
|
"agent-client-protocol",
|
|
5784
5784
|
"anyhow",
|
|
@@ -5801,6 +5801,7 @@ dependencies = [
|
|
|
5801
5801
|
"tokio-util",
|
|
5802
5802
|
"toml 0.8.23",
|
|
5803
5803
|
"tracing-subscriber",
|
|
5804
|
+
"url",
|
|
5804
5805
|
"uuid 1.26.0",
|
|
5805
5806
|
]
|
|
5806
5807
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "sigit"
|
|
3
|
-
version = "1.6.
|
|
3
|
+
version = "1.6.2"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
description = "siGit Code — ACP-compatible AI coding agent. Sí, git."
|
|
6
6
|
documentation = "https://github.com/getsigit/sigit"
|
|
@@ -60,6 +60,11 @@ base64 = "0.22"
|
|
|
60
60
|
rand = "0.9"
|
|
61
61
|
sha2 = "0.10"
|
|
62
62
|
|
|
63
|
+
# ACP `resource_link` blocks carry file URIs. Parsing them with `url` decodes
|
|
64
|
+
# percent escapes and maps Windows drive paths, which hand-stripping `file://`
|
|
65
|
+
# did not. Already in the graph through reqwest.
|
|
66
|
+
url = "2"
|
|
67
|
+
|
|
63
68
|
# Release binaries ship to five registries, and the NuGet package bundles all
|
|
64
69
|
# six targets in a single archive, so binary size is a distribution constraint
|
|
65
70
|
# rather than a nicety. Stripping symbols takes the macOS arm64 binary from
|
|
@@ -27,6 +27,11 @@ for interactive permission are denied in headless mode unless granted with
|
|
|
27
27
|
`--allow-tool <name>`. A repeatable `--deny-tool <name>` takes precedence over grants and
|
|
28
28
|
settings.
|
|
29
29
|
|
|
30
|
+
A run makes at most 24 rounds of tool calls. `--max-tool-rounds <n>`, or the
|
|
31
|
+
`SIGIT_MAX_TOOL_ROUNDS` environment variable, sets a different cap from 1 to 500; the flag wins.
|
|
32
|
+
When the cap is reached, or the model otherwise ends without saying anything, siGit Code asks it
|
|
33
|
+
once for a final message, so stdout is not left empty by a run that was cut off mid-task.
|
|
34
|
+
|
|
30
35
|
## JSONL output
|
|
31
36
|
|
|
32
37
|
Pass `--output jsonl` for a machine-readable stdout stream:
|