chivgent 0.19.0 → 1.0.1

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.
Files changed (195) hide show
  1. package/README.md +136 -886
  2. package/README.zh-CN.md +71 -29
  3. package/dist/agent.d.ts +14 -1
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +195 -28
  6. package/dist/agent.js.map +1 -1
  7. package/dist/cli-options.d.ts +2 -0
  8. package/dist/cli-options.d.ts.map +1 -1
  9. package/dist/cli-options.js +12 -2
  10. package/dist/cli-options.js.map +1 -1
  11. package/dist/cli-runtime.d.ts +24 -0
  12. package/dist/cli-runtime.d.ts.map +1 -0
  13. package/dist/cli-runtime.js +64 -0
  14. package/dist/cli-runtime.js.map +1 -0
  15. package/dist/cli.js +29 -97
  16. package/dist/cli.js.map +1 -1
  17. package/dist/context/compaction.d.ts +5 -2
  18. package/dist/context/compaction.d.ts.map +1 -1
  19. package/dist/context/compaction.js +58 -32
  20. package/dist/context/compaction.js.map +1 -1
  21. package/dist/context/context-manager.d.ts +2 -0
  22. package/dist/context/context-manager.d.ts.map +1 -1
  23. package/dist/context/context-manager.js +4 -3
  24. package/dist/context/context-manager.js.map +1 -1
  25. package/dist/context/project-instructions.d.ts +17 -0
  26. package/dist/context/project-instructions.d.ts.map +1 -0
  27. package/dist/context/project-instructions.js +191 -0
  28. package/dist/context/project-instructions.js.map +1 -0
  29. package/dist/evals/baseline.d.ts +41 -0
  30. package/dist/evals/baseline.d.ts.map +1 -0
  31. package/dist/evals/baseline.js +121 -0
  32. package/dist/evals/baseline.js.map +1 -0
  33. package/dist/evals/cli.js +27 -1
  34. package/dist/evals/cli.js.map +1 -1
  35. package/dist/evals/parse-args.d.ts +2 -0
  36. package/dist/evals/parse-args.d.ts.map +1 -1
  37. package/dist/evals/parse-args.js +5 -0
  38. package/dist/evals/parse-args.js.map +1 -1
  39. package/dist/evals/report.d.ts +2 -0
  40. package/dist/evals/report.d.ts.map +1 -1
  41. package/dist/evals/report.js +12 -8
  42. package/dist/evals/report.js.map +1 -1
  43. package/dist/evals/runner.d.ts +3 -0
  44. package/dist/evals/runner.d.ts.map +1 -1
  45. package/dist/evals/runner.js +5 -1
  46. package/dist/evals/runner.js.map +1 -1
  47. package/dist/evals/task.d.ts.map +1 -1
  48. package/dist/evals/task.js +1 -0
  49. package/dist/evals/task.js.map +1 -1
  50. package/dist/evals/tools.d.ts +4 -1
  51. package/dist/evals/tools.d.ts.map +1 -1
  52. package/dist/evals/tools.js +11 -4
  53. package/dist/evals/tools.js.map +1 -1
  54. package/dist/events.d.ts +7 -1
  55. package/dist/events.d.ts.map +1 -1
  56. package/dist/events.js.map +1 -1
  57. package/dist/patch/parse.d.ts +26 -0
  58. package/dist/patch/parse.d.ts.map +1 -0
  59. package/dist/patch/parse.js +117 -0
  60. package/dist/patch/parse.js.map +1 -0
  61. package/dist/prompts.d.ts +2 -2
  62. package/dist/prompts.d.ts.map +1 -1
  63. package/dist/prompts.js +4 -3
  64. package/dist/prompts.js.map +1 -1
  65. package/dist/providers/deferred-client.d.ts +2 -1
  66. package/dist/providers/deferred-client.d.ts.map +1 -1
  67. package/dist/providers/deferred-client.js +4 -0
  68. package/dist/providers/deferred-client.js.map +1 -1
  69. package/dist/providers/interactive.d.ts +31 -0
  70. package/dist/providers/interactive.d.ts.map +1 -0
  71. package/dist/providers/interactive.js +184 -0
  72. package/dist/providers/interactive.js.map +1 -0
  73. package/dist/remote/repl.d.ts.map +1 -1
  74. package/dist/remote/repl.js +6 -5
  75. package/dist/remote/repl.js.map +1 -1
  76. package/dist/repl.d.ts +43 -4
  77. package/dist/repl.d.ts.map +1 -1
  78. package/dist/repl.js +513 -32
  79. package/dist/repl.js.map +1 -1
  80. package/dist/session-store.d.ts +6 -2
  81. package/dist/session-store.d.ts.map +1 -1
  82. package/dist/session-store.js +5 -1
  83. package/dist/session-store.js.map +1 -1
  84. package/dist/session.d.ts +1 -0
  85. package/dist/session.d.ts.map +1 -1
  86. package/dist/session.js +38 -12
  87. package/dist/session.js.map +1 -1
  88. package/dist/shell/approval.d.ts +15 -0
  89. package/dist/shell/approval.d.ts.map +1 -0
  90. package/dist/shell/approval.js +25 -0
  91. package/dist/shell/approval.js.map +1 -0
  92. package/dist/shell/docker.d.ts +18 -0
  93. package/dist/shell/docker.d.ts.map +1 -0
  94. package/dist/shell/docker.js +83 -0
  95. package/dist/shell/docker.js.map +1 -0
  96. package/dist/shell/local.d.ts +1 -1
  97. package/dist/shell/local.d.ts.map +1 -1
  98. package/dist/shell/local.js +1 -1
  99. package/dist/shell/local.js.map +1 -1
  100. package/dist/tools/apply-patch.d.ts +18 -0
  101. package/dist/tools/apply-patch.d.ts.map +1 -0
  102. package/dist/tools/apply-patch.js +53 -0
  103. package/dist/tools/apply-patch.js.map +1 -0
  104. package/dist/tools/bash.d.ts +4 -0
  105. package/dist/tools/bash.d.ts.map +1 -1
  106. package/dist/tools/bash.js +28 -3
  107. package/dist/tools/bash.js.map +1 -1
  108. package/dist/tools/parallel.d.ts +14 -0
  109. package/dist/tools/parallel.d.ts.map +1 -0
  110. package/dist/tools/parallel.js +20 -0
  111. package/dist/tools/parallel.js.map +1 -0
  112. package/dist/tools/tool.d.ts +7 -0
  113. package/dist/tools/tool.d.ts.map +1 -1
  114. package/dist/tools/tool.js +7 -1
  115. package/dist/tools/tool.js.map +1 -1
  116. package/dist/tools/update-plan.d.ts +57 -0
  117. package/dist/tools/update-plan.d.ts.map +1 -0
  118. package/dist/tools/update-plan.js +99 -0
  119. package/dist/tools/update-plan.js.map +1 -0
  120. package/dist/tui/activity.d.ts +17 -0
  121. package/dist/tui/activity.d.ts.map +1 -0
  122. package/dist/tui/activity.js +188 -0
  123. package/dist/tui/activity.js.map +1 -0
  124. package/dist/tui/diff.d.ts +14 -0
  125. package/dist/tui/diff.d.ts.map +1 -0
  126. package/dist/tui/diff.js +57 -0
  127. package/dist/tui/diff.js.map +1 -0
  128. package/dist/tui/editor-input.d.ts +53 -0
  129. package/dist/tui/editor-input.d.ts.map +1 -0
  130. package/dist/tui/editor-input.js +353 -0
  131. package/dist/tui/editor-input.js.map +1 -0
  132. package/dist/tui/editor.d.ts +72 -0
  133. package/dist/tui/editor.d.ts.map +1 -0
  134. package/dist/tui/editor.js +321 -0
  135. package/dist/tui/editor.js.map +1 -0
  136. package/dist/tui/git-diff.d.ts +7 -0
  137. package/dist/tui/git-diff.d.ts.map +1 -0
  138. package/dist/tui/git-diff.js +48 -0
  139. package/dist/tui/git-diff.js.map +1 -0
  140. package/dist/tui/input.d.ts +20 -0
  141. package/dist/tui/input.d.ts.map +1 -1
  142. package/dist/tui/input.js +175 -13
  143. package/dist/tui/input.js.map +1 -1
  144. package/dist/tui/live.d.ts +8 -0
  145. package/dist/tui/live.d.ts.map +1 -1
  146. package/dist/tui/live.js +37 -5
  147. package/dist/tui/live.js.map +1 -1
  148. package/dist/tui/markdown.d.ts +7 -0
  149. package/dist/tui/markdown.d.ts.map +1 -0
  150. package/dist/tui/markdown.js +55 -0
  151. package/dist/tui/markdown.js.map +1 -0
  152. package/dist/tui/menu.d.ts +26 -0
  153. package/dist/tui/menu.d.ts.map +1 -0
  154. package/dist/tui/menu.js +75 -0
  155. package/dist/tui/menu.js.map +1 -0
  156. package/dist/tui/review.d.ts +27 -0
  157. package/dist/tui/review.d.ts.map +1 -0
  158. package/dist/tui/review.js +114 -0
  159. package/dist/tui/review.js.map +1 -0
  160. package/dist/tui/slash.d.ts +11 -0
  161. package/dist/tui/slash.d.ts.map +1 -0
  162. package/dist/tui/slash.js +13 -0
  163. package/dist/tui/slash.js.map +1 -0
  164. package/dist/tui/state.d.ts +5 -0
  165. package/dist/tui/state.d.ts.map +1 -1
  166. package/dist/tui/state.js +10 -1
  167. package/dist/tui/state.js.map +1 -1
  168. package/dist/tui/syntax.d.ts +6 -0
  169. package/dist/tui/syntax.d.ts.map +1 -0
  170. package/dist/tui/syntax.js +48 -0
  171. package/dist/tui/syntax.js.map +1 -0
  172. package/dist/tui/text.d.ts +2 -0
  173. package/dist/tui/text.d.ts.map +1 -1
  174. package/dist/tui/text.js +19 -0
  175. package/dist/tui/text.js.map +1 -1
  176. package/dist/tui/view.d.ts +1 -1
  177. package/dist/tui/view.d.ts.map +1 -1
  178. package/dist/tui/view.js +77 -31
  179. package/dist/tui/view.js.map +1 -1
  180. package/dist/tui/welcome.d.ts +7 -1
  181. package/dist/tui/welcome.d.ts.map +1 -1
  182. package/dist/tui/welcome.js +40 -23
  183. package/dist/tui/welcome.js.map +1 -1
  184. package/dist/workspace/patch.d.ts +11 -0
  185. package/dist/workspace/patch.d.ts.map +1 -0
  186. package/dist/workspace/patch.js +124 -0
  187. package/dist/workspace/patch.js.map +1 -0
  188. package/dist/workspace/types.d.ts +7 -0
  189. package/dist/workspace/types.d.ts.map +1 -1
  190. package/dist/workspace/types.js.map +1 -1
  191. package/dist/workspace.d.ts +2 -1
  192. package/dist/workspace.d.ts.map +1 -1
  193. package/dist/workspace.js +5 -0
  194. package/dist/workspace.js.map +1 -1
  195. package/package.json +2 -2
package/README.md CHANGED
@@ -1,939 +1,189 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/chivgent-banner-dark.svg">
4
+ <img src="docs/assets/chivgent-banner-light.svg" alt="chivgent: terminal coding agent. Read-only by default, Docker shell, OpenAI-compatible." width="100%">
5
+ </picture>
6
+ </p>
7
+
1
8
  # chivgent
2
9
 
3
10
  [English](README.md) | [简体中文](README.zh-CN.md)
4
11
 
5
- > A small, readable coding-agent CLI for learning how an agent harness actually
6
- > works.
7
-
8
- ![Node.js](https://img.shields.io/badge/Node.js-%3E%3D22-339933?logo=nodedotjs&logoColor=white)
9
- ![TypeScript](https://img.shields.io/badge/TypeScript-ESM-3178C6?logo=typescript&logoColor=white)
12
+ [![npm](https://img.shields.io/npm/v/chivgent)](https://www.npmjs.com/package/chivgent)
10
13
  [![CI](https://github.com/chivopic/chivgent/actions/workflows/ci.yml/badge.svg)](https://github.com/chivopic/chivgent/actions/workflows/ci.yml)
11
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
12
- ![Status](https://img.shields.io/badge/status-MVP-orange)
13
-
14
- `chivgent` connects a Provider-independent agent loop to LLM APIs, tools, and a
15
- workspace boundary. The current MVP can discover files, search source text, and
16
- read bounded file ranges before answering with OpenAI, DeepSeek, or any
17
- compatible Chat Completions endpoint.
18
-
19
- The project is intentionally compact: it is designed to make the mechanics of
20
- tool calling, conversation state, Provider adapters, and loop termination easy
21
- to study before adding production-harness complexity.
22
-
23
- ## Features
24
-
25
- - A real multi-turn agent loop: model -> tool call -> tool result -> model.
26
- - Provider-independent runtime messages and tool contracts.
27
- - A Provider registry: adding a Provider is a declaration, not a CLI change.
28
- - API keys resolved from `--api-key`, the environment, then an optional file.
29
- - OpenAI support through the Responses API.
30
- - DeepSeek support through a reusable OpenAI-compatible Chat Completions client.
31
- - Custom OpenAI-compatible endpoints through environment-only configuration.
32
- - An interactive session with slash commands, or a single-shot question.
33
- - Sessions that persist as JSON lines and can be resumed in a later process.
34
- - A context manager that summarises old turns to stay inside the window.
35
- - A `--json` event stream for scripting and other front ends.
36
- - Streamed answers rendered from a typed runtime event stream.
37
- - Interruptible runs: Ctrl+C ends the current run without losing the transcript.
38
- - Per-attempt Provider timeouts and bounded exponential-backoff retries.
39
- - Deterministic project discovery through `list_files` and literal `search_text`.
40
- - Ranged `read_file` output with continuation hints and bounded tool results.
41
- - Read-only by default; `--allow-writes` adds `write_file` and `edit_file`.
42
- - An opt-in `bash` tool behind `--allow-shell`, with streamed output.
43
- - Token usage from the Provider, per turn, per run, per eval task.
44
- - An eval suite that measures whether the agent completes tasks, not just runs.
45
- - Remote sessions: one process holds a session, others attach over a local socket.
46
- - Several clients can watch one session; any of them can interrupt the run.
47
- - Extensions that add tools, slash commands, event handlers and prompt text.
48
- - Project extensions gated by a per-directory trust decision, taken before any
49
- module is imported and refused outright when there is no terminal to ask.
50
- - Commands run in their own process group, so cancelling kills the whole tree.
51
- - Command output is truncated from the tail; the full text goes to a temp file.
52
- - Exact-match `edit_file` that refuses missing or ambiguous edits.
53
- - Edits preserve the file's own byte order mark and CRLF line endings.
54
- - Atomic writes: a crash mid-write leaves the original file intact.
55
- - Safe workspace access with traversal and symlink-escape protection.
56
- - Root `.gitignore`, generated-directory, and sensitive-path filtering.
57
- - Tool argument validation, explicit tool errors, and a bounded turn limit.
58
- - A packaged Node.js CLI with no framework dependency.
59
- - Unit tests that do not spend API credits.
14
+ ![Node.js >=22](https://img.shields.io/badge/Node.js-%3E%3D22-339933?logo=nodedotjs&logoColor=white)
60
15
 
61
- ## Quick start
16
+ chivgent is a terminal coding agent you can read end to end. It asks an LLM
17
+ (OpenAI, DeepSeek, or any OpenAI-compatible endpoint) about the project in your
18
+ current directory, using read-only file tools by default. Writes and shell
19
+ commands are separate opt-in flags, and shell commands run in an offline Docker
20
+ container with per-command approval.
21
+
22
+ It is small on purpose: a TypeScript CLI with four runtime dependencies, where
23
+ the agent loop, Provider adapters, context compaction, and tool sandboxing are
24
+ each a few hundred lines you can study.
62
25
 
63
- ### Requirements
26
+ **Use it if** you want a coding agent whose every mechanism you can inspect and
27
+ change, or a reference implementation for building your own.
28
+ **Look elsewhere if** you want the most capable agent for daily work, a hosted
29
+ product, a fine-grained permission system, or native Windows shell support.
64
30
 
65
- - Node.js 22 or newer
66
- - npm
67
- - An API key for OpenAI, DeepSeek, or a compatible Provider
31
+ ## Quick start
68
32
 
69
- ### Install from npm
33
+ You need Node.js 22 or newer and an API key for one Provider.
70
34
 
71
35
  ```bash
72
36
  npm install -g chivgent
73
37
  chivgent --version
74
38
  ```
75
39
 
76
- Update an npm global installation with:
77
-
78
- ```bash
79
- chivgent update --check # Check without installing
80
- chivgent update # Install the newer version, then restart chivgent
81
- ```
82
-
83
- For older releases without this command, run `npm install -g chivgent@latest`
84
- once. The updater checks only when requested. Source checkouts, local dependencies
85
- and temporary `npx` runs receive instructions for their installation type.
86
- See [updating chivgent](docs/updating.md) for details and troubleshooting.
87
-
88
- To install the current source checkout instead:
89
-
90
- ```bash
91
- git clone https://github.com/chivopic/chivgent.git
92
- cd chivgent
93
- npm install
94
- npm run build
95
- npm install -g .
96
- ```
97
-
98
- ### Ask about a project
99
-
100
- Run `chivgent` from the project you want it to inspect.
101
-
102
- With OpenAI:
40
+ This prints the installed version, such as `1.0.0`. Then, from the project you want to ask
41
+ about:
103
42
 
104
43
  ```bash
105
44
  export OPENAI_API_KEY="your-api-key"
106
- chivgent "What does src/agent.ts do?"
45
+ chivgent "What does this project do, and where is its entry point?"
107
46
  ```
108
47
 
109
- With DeepSeek:
48
+ The answer streams to stdout. Tool activity (`list_files`, `search_text`,
49
+ `read_file`) is shown on stderr. Nothing in the project is changed.
50
+
51
+ Prefer DeepSeek or another Provider? Swap the key and add `--provider`:
110
52
 
111
53
  ```bash
112
54
  export DEEPSEEK_API_KEY="your-api-key"
113
55
  chivgent --provider deepseek "Explain the architecture in src/"
114
56
  ```
115
57
 
116
- With any OpenAI-compatible Chat Completions endpoint:
117
-
118
- ```bash
119
- export OPENAI_API_KEY="your-provider-api-key"
120
- export OPENAI_BASE_URL="https://api.vendor.example/v1"
121
- export OPENAI_MODEL="vendor-model"
122
-
123
- chivgent --provider openai-compatible "Explain the architecture in src/"
124
- ```
125
-
126
- Start an interactive session by running `chivgent` with no question:
127
-
128
- ```bash
129
- chivgent
130
- › What does src/agent.ts do?
131
- › Where is that loop tested?
132
- › /exit
133
- ```
134
-
135
- The conversation is kept across prompts, so follow-up questions do not repeat
136
- the earlier context. Resume it later with `chivgent --continue` (or
137
- `chivgent --resume <id>`; `chivgent --sessions` lists what is recorded).
138
-
139
- Answers stream to stdout as the model produces them, so stdout stays pipeable.
140
- Tool activity, retries, and run status go to stderr; Provider failures produce a
141
- non-zero exit code. Use `--no-stream` for one final write, and `--quiet` to hide
142
- tool activity.
143
-
144
- ## CLI reference
145
-
146
- ```text
147
- chivgent [options] "question" Answer one question and exit
148
- chivgent [options] Start an interactive session
149
-
150
- Options:
151
- --provider NAME openai, deepseek, openai-compatible, openrouter, groq, xai,
152
- moonshot (default: openai)
153
- --model MODEL Provider model override
154
- --max-turns N Tool-calling turn limit (default: 8, 16 with --allow-writes
155
- or --allow-shell)
156
- --no-stream Wait for the full answer instead of streaming tokens
157
- -q, --quiet Hide tool activity on stderr
158
- --json Write the run as JSON lines instead of rendered text
159
- -c, --continue Resume the most recent session for this workspace
160
- --resume ID Resume a specific session
161
- --api-key KEY API key for this run; prefer an environment variable
162
- --sessions List recorded sessions and exit
163
- --allow-writes Let the agent create and change files (default: read-only)
164
- --allow-shell Let the agent run shell commands. This implies write access:
165
- a shell is not bound by the workspace. Unix only.
166
- --serve Expose this session on a local socket and keep running
167
- --connect TARGET Attach to a served session, by id or socket path
168
- --servers List the servers still answering, then exit
169
- --no-extensions Do not load any extension, and do not ask about trust
170
- --extensions List loaded extensions and what they register, then exit
171
- --forget-trust Forget the trust decision covering this workspace, then exit
172
- --context-window N Token budget for the context (default: 128000)
173
- --no-compaction Send the whole transcript instead of summarising old turns
174
- --no-session Do not record this run
175
- -h, --help Show help
176
- -v, --version Show version
177
- ```
178
-
179
- In an interactive session, `/help` lists the slash commands: `/session`,
180
- `/tools`, `/clear`, `/login`, and `/exit`. Ctrl+C stops the answer in progress without
181
- leaving the session; Ctrl+D leaves it.
58
+ Run `chivgent` with no question for an interactive session. If no key is set,
59
+ the session still starts: type `/provider`, pick one, and enter its key when
60
+ asked. Keys are saved owner-only in `~/.chivgent/auth.json`.
182
61
 
183
- Exit codes: `0` answered, `1` configuration or Provider failure, `2` turn limit
184
- reached, `130` interrupted with Ctrl+C.
62
+ **Try it without a key or network:** from a source checkout, `npm run demo:tui`
63
+ runs the TUI against a stub Provider and does not touch the workspace.
185
64
 
186
- ### Signing in
65
+ ## What it can do, and what it needs from you
187
66
 
188
- The quickest way in is to start chivgent and let it ask:
67
+ | Capability | Flag | What it can touch |
68
+ | --- | --- | --- |
69
+ | Discover, search, and read files | default | The current directory only. `.gitignore`d, generated, and credential paths (such as `.env`, `id_rsa`) are skipped. |
70
+ | Create and edit files (`write_file`, `edit_file`, `apply_patch`) | `--allow-writes` | Files inside the workspace. Edits are exact-match and atomic. |
71
+ | Run shell commands (`bash`) | `--allow-shell` | A fresh `node:22-alpine` Docker container with no network, the workspace mounted at `/workspace`, and no API keys. Each command needs a `y` to run. Docker is required; there is no fallback to the host. |
72
+ | Load project extensions | trust prompt on first run | Anything you can: extensions are in-process JavaScript. |
189
73
 
190
- ```text
191
- $ chivgent
192
- chivgent 0.13.0 · openai · gpt-5.6
193
- session 2026-09-07T...
74
+ Costs: chivgent calls the Provider you choose with your key, so you pay that
75
+ Provider's normal token prices. It reports token counts per turn and per
76
+ session (`/session`) but never converts them to money.
194
77
 
195
- No API key for openai yet.
196
- Run /login to store one, or leave and set an environment variable.
197
- Type /help for commands, Ctrl+D to leave.
198
- › /login
199
- Paste an API key for openai. It is not echoed.
200
- It will be stored in ~/.chivgent/auth.json, readable only by you.
201
- API key:
202
- Stored the key for openai.
203
- ›
204
- ```
205
-
206
- An interactive session starts even without a key, so the fix is reachable from
207
- inside chivgent rather than being something to go and arrange first. `/login`
208
- stores the key for the Provider this session is using — start with
209
- `--provider deepseek` to store one for another — and puts it to use straight
210
- away, so the prompt you type next already works. Running `/login` again replaces
211
- a key that turned out to be wrong.
212
-
213
- The key is not echoed as you type, and `auth.json` is written owner-only. It is
214
- not verified against the Provider when stored; the first prompt after it is what
215
- proves it works.
216
-
217
- A non-interactive run still fails fast when no key is configured: a pipeline has
218
- nobody to ask.
219
-
220
- ### Provider configuration
221
-
222
- | Provider | API key | Model environment variable | Default model | API style |
223
- | --- | --- | --- | --- | --- |
224
- | OpenAI | `OPENAI_API_KEY` | `OPENAI_MODEL` | `gpt-5.6` | Responses API |
225
- | DeepSeek | `DEEPSEEK_API_KEY` | `DEEPSEEK_MODEL` | `deepseek-v4-flash` | OpenAI-compatible Chat Completions |
226
- | Custom compatible | `OPENAI_API_KEY` | `OPENAI_MODEL` | Required | OpenAI-compatible Chat Completions |
227
- | OpenRouter | `OPENROUTER_API_KEY` | `OPENROUTER_MODEL` | Required | OpenAI-compatible Chat Completions |
228
- | Groq | `GROQ_API_KEY` | `GROQ_MODEL` | Required | OpenAI-compatible Chat Completions |
229
- | xAI | `XAI_API_KEY` | `XAI_MODEL` | Required | OpenAI-compatible Chat Completions |
230
- | Moonshot | `MOONSHOT_API_KEY` | `MOONSHOT_MODEL` | Required | OpenAI-compatible Chat Completions |
231
-
232
- An explicit `--model` value takes precedence over the Provider-specific model
233
- environment variable. Custom compatible Providers also require
234
- `OPENAI_BASE_URL`. Sessions are written under `CHIVGENT_HOME` (default
235
- `~/.chivgent`).
236
-
237
- Providers are declared in a registry rather than branched on in the CLI, so
238
- `--help` and `--provider` validation are generated from the same list that
239
- creates the client.
240
-
241
- #### API key resolution
242
-
243
- Keys are resolved in this order, first match wins:
244
-
245
- 1. `--api-key` for a single run
246
- 2. the Provider's environment variable
247
- 3. `<CHIVGENT_HOME>/auth.json`
248
-
249
- An environment variable deliberately beats the stored file, matching the
250
- convention other CLIs use, so a stored key can be overridden for one run
251
- without editing anything.
252
-
253
- `auth.json` is optional and holds literal keys only:
254
-
255
- ```json
256
- {
257
- "openai": { "type": "api_key", "key": "sk-..." },
258
- "deepseek": "sk-..."
259
- }
260
- ```
261
-
262
- Neither `$VAR` expansion nor `!command` substitution is supported: letting a
263
- config file spawn a process is a large attack surface for a small convenience.
264
- chivgent warns when the file is readable by other users; keep it at `chmod 600`.
265
-
266
- ```bash
267
- chivgent --provider openai --model gpt-5.6 "Explain package.json"
268
- chivgent --provider deepseek --model deepseek-v4-pro "Explain package.json"
269
- chivgent --provider openai-compatible --model vendor-model "Explain package.json"
270
- ```
271
-
272
- ## Architecture
273
-
274
- ```text
275
- +-> OpenAI Responses API
276
- User -> CLI -> Agent -> LLMClient |
277
- | +-> OpenAI-compatible Chat -> DeepSeek / custom
278
- |
279
- +-> Tool Registry -> list_files / search_text / read_file -> Workspace
280
- | write_file / edit_file (--allow-writes)
281
- | bash (--allow-shell) -> ShellOperations
282
- ```
283
-
284
- The Agent runtime owns its own messages. Provider-specific schemas are converted
285
- only at the `LLMClient` boundary:
286
-
287
- ```text
288
- Agent Message[] -> Provider adapter -> Provider request
289
- <- Provider response
290
- AssistantMessage <- normalized result
291
- ```
292
-
293
- This prevents the Agent, tools, and CLI from depending on one vendor's message
294
- format.
295
-
296
- ### Context management
297
-
298
- A session records everything that happened. The context manager decides what
299
- is worth sending for one request. Keeping those apart is what lets a long
300
- session stay inside a fixed context window.
301
-
302
- ```text
303
- Full transcript ──────────────→ Session store (what happened)
304
- │
305
- ↓
306
- ContextManager
307
- │ token estimate vs. contextWindow - reserveTokens
308
- ↓
309
- summary + recent messages ────→ Provider (what the model sees)
310
- ```
311
-
312
- When the estimate exceeds the budget, older messages are summarised into a
313
- single message and recent turns are kept verbatim. Three details matter:
314
-
315
- - **File lists are derived from tool calls, not from the summary.** A summary
316
- can forget or invent a path. For a coding agent, "which files did I read and
317
- change" is the part that most needs to survive intact, so it is collected
318
- from the `read_file`, `write_file`, and `edit_file` calls themselves.
319
- - **Tool calls are never separated from their results.** A split point that
320
- would orphan a tool result moves forward past the whole group.
321
- - **Compaction discards the Provider continuation.** A Provider that chains
322
- history server-side replays its own copy and ignores the messages sent with
323
- it, so keeping the continuation would send back the history just removed.
324
-
325
- Token counts are estimated from character length rather than with a real
326
- tokenizer, which would be model-specific and a large dependency for a number
327
- that only decides *when* to compact. The reserve budget absorbs the error.
328
-
329
- A single tool result larger than the whole budget cannot be compacted away;
330
- bound tool output instead. Compaction is disabled with `--no-compaction`.
331
-
332
- ### Runtime events
333
-
334
- The Agent Loop reports what it is doing through a typed event stream instead of
335
- printing anything itself. One run emits:
336
-
337
- ```text
338
- agent_start
339
- turn_start -> message_start -> message_update* -> message_end
340
- tool_execution_start -> tool_execution_end (once per tool call)
341
- turn_end
342
- ...
343
- agent_end (completed | max_turns | aborted | error)
344
- ```
78
+ ## Providers
345
79
 
346
- `message_update` carries deltas only, never a cumulative snapshot, so the stream
347
- stays linear in the length of the answer. Events are structured-cloneable and
348
- each listener receives a copy, so a renderer can never mutate the transcript.
349
- The CLI renderer in `src/render.ts` is one consumer; a log file, a JSON stream,
350
- or a TUI are others.
80
+ | Provider | `--provider` | API key variable | Default model |
81
+ | --- | --- | --- | --- |
82
+ | OpenAI (Responses API) | `openai` (default) | `OPENAI_API_KEY` | `gpt-5.6` |
83
+ | DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek-v4-flash` |
84
+ | Any OpenAI-compatible endpoint | `openai-compatible` | `OPENAI_API_KEY` + `OPENAI_BASE_URL` | set `OPENAI_MODEL` |
85
+ | OpenRouter | `openrouter` | `OPENROUTER_API_KEY` | set `OPENROUTER_MODEL` |
86
+ | Groq | `groq` | `GROQ_API_KEY` | set `GROQ_MODEL` |
87
+ | xAI | `xai` | `XAI_API_KEY` | set `XAI_MODEL` |
88
+ | Moonshot | `moonshot` | `MOONSHOT_API_KEY` | set `MOONSHOT_MODEL` |
351
89
 
352
- `LLMClient.stream` is optional. When a Provider does not implement it, the Agent
353
- falls back to `complete` and the same events are emitted without deltas.
90
+ `--model` overrides the model variable. Keys resolve in this order: `--api-key`,
91
+ then the environment variable, then `~/.chivgent/auth.json` (literal keys only;
92
+ no `$VAR` expansion or command substitution). A custom `OPENAI_BASE_URL`
93
+ receives your key and prompts, so point it only at endpoints you trust.
354
94
 
355
- ### OpenAI-compatible Providers
95
+ Compatible endpoints must implement `POST /chat/completions` with function tool
96
+ calling. Changing only the base URL does not guarantee full compatibility; see
97
+ [architecture](docs/architecture.md#openai-compatible-providers).
356
98
 
357
- Compatible Providers reuse the official `openai` npm package by changing
358
- `baseURL`, credentials, and model. CLI users do not need to edit code:
99
+ ## Everyday use
359
100
 
360
101
  ```bash
361
- export OPENAI_API_KEY="your-provider-api-key"
362
- export OPENAI_BASE_URL="https://api.vendor.example/v1"
363
- export OPENAI_MODEL="vendor-model"
102
+ chivgent # interactive session
103
+ chivgent --continue # resume the latest session here
104
+ chivgent --allow-writes "Rename sayHi to greet everywhere"
105
+ chivgent --allow-shell "Run the tests and explain the failure"
106
+ chivgent --json "List the CLI flags" > run.jsonl # machine-readable events
107
+ ```
108
+
109
+ - **Interactive sessions** keep history across prompts and are saved as JSON
110
+ lines under `~/.chivgent/sessions` (`--no-session` to skip). `/help` lists
111
+ slash commands such as `/provider`, `/model`, `/login`, `/session`, `/diff`,
112
+ and `/exit`. Ctrl+C stops a running answer; at an idle prompt it exits.
113
+ - **`--tui`** adds a live status region and a multi-line editor with history,
114
+ an inline `/` command menu, and `/gitdiff` review. See the
115
+ [TUI guide](docs/tui-v5.md).
116
+ - **Long sessions** stay inside the context window: older turns are summarised,
117
+ while the list of files read and changed is kept from the tool calls
118
+ themselves. See [architecture](docs/architecture.md#context-management).
119
+ - **Exit codes:** `0` answered, `1` configuration or Provider failure, `2` turn
120
+ limit reached, `130` interrupted.
121
+ - **Updating:** `chivgent update --check`, then `chivgent update`. See
122
+ [updating](docs/updating.md).
123
+
124
+ `chivgent --help` prints every flag.
364
125
 
365
- chivgent --provider openai-compatible "What does src/agent.ts do?"
366
- ```
367
-
368
- `OPENAI_BASE_URL` must point to the Provider's OpenAI-compatible API root. The
369
- Provider must implement `POST /chat/completions` and function tool calling.
370
-
371
- When adding a named Provider in source code, use the same adapter:
372
-
373
- ```ts
374
- const client = new OpenAICompatibleChatClient({
375
- apiKey: process.env.VENDOR_API_KEY!,
376
- baseURL: "https://api.vendor.example/v1",
377
- model: "vendor-model",
378
- continuationTag: "vendor-chat",
379
- });
380
- ```
126
+ ## Security model
381
127
 
382
- `DeepSeekChatClient` is a small configuration wrapper around this shared client.
383
- The compatibility layer also preserves optional Provider-only fields such as
384
- DeepSeek's `reasoning_content` inside opaque continuation state.
385
-
386
- Changing only `baseURL` is not a promise of complete compatibility. Providers
387
- can differ in model names, authentication, tool-schema support, strict mode,
388
- reasoning fields, streaming events, and error behavior. Keep those differences
389
- inside thin Provider adapters rather than leaking them into the Agent loop.
390
-
391
- ## Project structure
392
-
393
- ```text
394
- src/
395
- cli.ts CLI entry point and process boundary
396
- cli-options.ts Argument and Provider configuration
397
- auth/
398
- credentials.ts Credential contract and resolution order
399
- runtime-credentials.ts --api-key override
400
- env-credentials.ts Environment variable lookup
401
- file-credentials.ts The auth.json store, read and write
402
- agent.ts Agent loop and run state
403
- events.ts Runtime event model
404
- render.ts Terminal renderer for runtime events
405
- llm.ts Provider-independent LLM contract
406
- retry.ts Provider timeout and retry decorator
407
- messages.ts Runtime message model
408
- session.ts Conversation state and event fan-out
409
- session-store.ts JSONL session log and resume support
410
- repl.ts Interactive prompt and slash commands
411
- context/
412
- context-manager.ts Builds the messages for one request
413
- compaction.ts Summarises old history and tracks files
414
- token-estimator.ts Character-based token approximation
415
- workspace.ts Workspace configuration and the read-only default
416
- workspace/
417
- types.ts Limits, errors, and the Workspace contract
418
- paths.ts Path normalisation and escape protection
419
- ignore.ts .gitignore and generated-directory filtering
420
- text.ts UTF-8 decoding, line splitting, previews
421
- read.ts Ranged reads
422
- list.ts Directory walking
423
- search.ts Literal text search
424
- write.ts Atomic whole-file writes and exact edits
425
- providers/
426
- registry.ts Provider registry
427
- usage.ts Reading and adding up Provider token counts
428
- deferred-client.ts A client whose Provider can arrive later
429
- definitions.ts Built-in Provider declarations
430
- client.ts Credential resolution into an LLM client
431
- openai.ts OpenAI Responses adapter
432
- openai-compatible-chat.ts Shared Chat Completions adapter
433
- deepseek.ts DeepSeek configuration wrapper
434
- tools/
435
- tool.ts Tool contract
436
- output.ts Shared 64 KiB tool-output boundary
437
- list-files.ts Deterministic project-tree discovery
438
- search-text.ts Bounded literal source search
439
- read-file.ts Ranged text-file reader
440
- write-file.ts Whole-file create and replace
441
- edit-file.ts Exact unique-match edit
442
- bash.ts Shell command execution
443
- prompts.ts The instructions the agent runs under
444
- evals/
445
- task.ts Task definitions and their validation
446
- fixture.ts A fresh workspace copy per attempt
447
- graders.ts Deterministic graders
448
- runner.ts Runs a task N times and aggregates
449
- report.ts Table and JSON output
450
- parse-args.ts Eval CLI arguments
451
- cli.ts npm run eval
452
- remote/
453
- protocol.ts Message shapes and version negotiation
454
- framing.ts JSON Lines framing with a bounded buffer
455
- server.ts Serves one session on a Unix socket
456
- client.ts Attaches to a served session
457
- repl.ts The interactive loop for an attached client
458
- socket-path.ts Socket paths, staleness, and discovery
459
- extensions/
460
- trust.ts Per-directory trust store
461
- decide-trust.ts The prompt, and the refusal when nobody can answer
462
- discover.ts Where extensions live and which files count
463
- api.ts What an extension may register
464
- registry.ts Collects registrations, rejects conflicts
465
- loader.ts Imports modules and contains their failures
466
- shell/
467
- types.ts Execution contract and shell error types
468
- config.ts Shell resolution and the Windows guard
469
- local.ts Local spawn backend
470
- process.ts Process-group kill and exit handling
471
- output.ts Bounded streaming output accumulator
472
- truncate.ts Tail truncation for command output
473
- sanitize.ts Control-character filtering
474
- tests/ Provider, loop, and workspace tests
475
- evals/ Eval tasks and their fixtures
476
- docs/ Architecture and learning notes
477
- ```
128
+ chivgent states its boundaries rather than pretending to enforce fine-grained
129
+ ones. Capabilities are coarse, session-level switches.
130
+
131
+ - File tools reject paths outside the workspace, including `..` traversal and
132
+ symlink escapes, and deny common credential and private-key paths. Tool output
133
+ is capped at 64 KiB.
134
+ - `--allow-shell` runs each command in a new offline container (dropped
135
+ capabilities, read-only root, non-root user, resource limits) and asks before
136
+ each one. Headless and served sessions deny commands unless you also pass
137
+ `--approve-all-shell`. Inside the container, commands can still change or
138
+ delete anything in the mounted project, and Docker is not proof against
139
+ kernel or VM escapes. Details: [shell sandbox](docs/shell-sandbox.md).
140
+ - Writes and trusted extensions are **not** inside the container. Trusting a
141
+ project's extensions means letting its authors run code as you, so chivgent
142
+ asks once per directory and never loads them when there is no terminal to ask.
143
+ - `auth.json`, session logs, and `trust.json` are written owner-only but in
144
+ plain text. Session logs contain prompts, answers, and file excerpts.
145
+ - `--serve` exposes a session on a Unix socket in an owner-only directory.
146
+ Anyone who can open the socket gets every capability the server started with.
147
+ There is no TCP listener; use SSH forwarding for another machine.
148
+
149
+ Work on committed code, and use a disposable copy for repositories you don't
150
+ trust.
151
+
152
+ ## Going deeper
153
+
154
+ - [Architecture](docs/architecture.md): agent loop, runtime events, context
155
+ compaction, Provider adapters, the TUI live region, and the source layout.
156
+ - [Evals and token usage](docs/evals.md): ten deterministic task suites that
157
+ measure pass rates over repeated attempts, separate from the free, offline
158
+ unit tests.
159
+ - [Extensions and remote sessions](docs/extensions-and-remote.md): adding
160
+ tools and slash commands, project trust, and attaching several terminals to
161
+ one session.
162
+ - [Patch format](docs/apply-patch.md), [scoped `AGENTS.md`](docs/scoped-agents-md.md),
163
+ [parallel read tools](docs/parallel-read-tools.md),
164
+ [plan tracking](docs/agent-plan-progress.md).
165
+ - Design notes for each build stage, from
166
+ [stage 1](docs/stage-1-minimal-agent.md) to [stage 14](docs/stage-14-tui.md),
167
+ and the [release process](docs/releasing.md).
478
168
 
479
169
  ## Development
480
170
 
481
171
  ```bash
172
+ git clone https://github.com/chivopic/chivgent.git
173
+ cd chivgent
482
174
  npm install
483
175
  npm run check
484
176
  npm test
485
177
  npm run build
486
178
  ```
487
179
 
488
- Run the complete release gate, including an npm tarball dry run:
489
-
490
- ```bash
491
- npm run release:check
492
- ```
493
-
494
- Build a locally installable tarball:
495
-
496
- ```bash
497
- npm pack
498
- npm install -g ./chivgent-0.6.0.tgz
499
- ```
500
-
501
- Tests use scripted or mocked LLM clients. A real API smoke test is deliberately
502
- manual so the default test suite never consumes credits.
503
-
504
- ## Evals
505
-
506
- The 300-odd unit tests check that the code runs the way it was written. They say
507
- nothing about whether this is a good agent. `npm run eval` answers that:
508
-
509
- ```bash
510
- npm run eval # every task in evals/
511
- npm run eval -- --task rename-symbol --attempts 10
512
- npm run eval -- --allow-writes --allow-shell --json report.json
513
- DEEPSEEK_API_KEY=sk-... npm run eval -- --provider deepseek
514
- ```
515
-
516
- `--api-key` works too, but prefer the environment variable here: npm echoes the
517
- fully expanded command before running it, so a key on the command line lands in
518
- the clear in any captured log.
519
-
520
- ```text
521
- task pass turns tokens tools p50
522
- find-auth-logic 4/5 2.0 3.4k list_files,search_text,... 6.1s
523
- rename-symbol 3/5 5.4 12.1k read_file,edit_file 9.8s
524
-
525
- overall 7/10 (70%) 58.3k tokens
526
-
527
- rename-symbol attempt 2,5: not-used-tool name="write_file" — called write_file 1 time(s)
528
- ```
529
-
530
- The `tokens` column is what the Provider reported, so a change can be judged on
531
- both axes at once: a prompt that lifts the pass rate from 70% to 75% while
532
- tripling the tokens is usually a bad trade, and without the second number that
533
- looks like a pure win.
534
-
535
- **An eval is not a test.** A model is nondeterministic, so one run of a task is a
536
- coin flip: passing and failing are properties of the model-and-harness
537
- combination, not of that run. The unit is therefore a pass rate over N attempts,
538
- and evals never gate CI — a merge gate that costs money and goes red six times in
539
- ten trains everyone to ignore CI. `npm test` stays free, offline and
540
- deterministic.
541
-
542
- A task is a directory under `evals/`: a `task.json` and a `fixture/` tree copied
543
- into a fresh temporary workspace for every attempt.
544
-
545
- ```jsonc
546
- {
547
- "prompt": "Rename the function sayHi to greet everywhere, including callers.",
548
- "capabilities": ["writes"],
549
- "attempts": 5,
550
- "graders": [
551
- { "type": "file-contains", "path": "src/greet.ts", "text": "export function greet(" },
552
- { "type": "file-excludes", "path": "src/greet.ts", "text": "sayHi" },
553
- { "type": "used-tool", "name": "edit_file" },
554
- { "type": "not-used-tool", "name": "write_file" }
555
- ]
556
- }
557
- ```
558
-
559
- Graders are deterministic — file contents, tool calls, answer patterns, turn
560
- counts. There is no LLM judge: a judge is itself a nondeterministic instrument,
561
- and measuring a nondeterministic subject with one compounds the error until "the
562
- agent got worse" cannot be told apart from "the judge felt different today".
563
-
564
- Those last two graders are the point. Rewriting the whole file also produces the
565
- right contents, and an eval that scores results alone would give it full marks.
566
- For a coding agent, tool misuse is the more common and the more interesting
567
- failure, so tasks assert on how the work was done.
568
-
569
- A grader naming a tool the task never grants is refused when the task loads.
570
- `not-used-tool: write_file` on a read-only task cannot fail — it asserts the
571
- model did not call something it was never given — and its mirror image cannot
572
- pass. Both are task-definition mistakes, and both are caught before the run
573
- spends anything. One such grader shipped for two releases before this check
574
- existed.
575
-
576
- Three graders exist because a set of tool names cannot answer the question they
577
- answer. `tool-never-failed` catches a guessed path, which shows up as a failed
578
- read that a deduplicated name list hides — it is sound only where a failed call
579
- means the model was wrong, so it fits `read_file` and not `bash`, which reports
580
- any non-zero exit as an error and is *supposed* to fail on a task that runs a
581
- failing test. `max-tool-calls` budgets calls rather than turns, because a model
582
- may issue any number of calls in one turn: "did it follow the imports or read
583
- the whole project" is a call count. `file-unchanged` compares against the
584
- fixture, so "don't touch what you weren't asked to touch" becomes a verdict
585
- rather than an intention — and it needs no second copy of the file in
586
- `task.json` to drift out of date. The JSON report carries `toolCalls`, every
587
- call in order with whether it succeeded, alongside the `toolsUsed` set the table
588
- prints.
589
-
590
- The nine tasks split into what the agent found and what it changed. Four exist
591
- because the first real baseline passed 29 of 30 attempts and therefore measured
592
- nothing: `needle-in-many-files` (40 files with uninformative names, so listing
593
- gives no signal), `trace-the-default` (a value overridden twice, where stopping
594
- at the first hop yields a plausible wrong number), `decoy-config` (two
595
- near-identical modules, one of them dead), and `wrong-test` (a failing test
596
- whose expectation contradicts the contract documented in the source — bending
597
- the source to fit it is the trap).
598
-
599
- A task that scores 0/N or N/N across different models is not measuring anything
600
- and should be made harder, fixed, or dropped. Passing is not evidence of quality
601
- when everything passes.
602
-
603
- ## Token usage
604
-
605
- Providers report what each call cost, and chivgent now keeps it: per turn on
606
- `message_end`, per run on `agent_end` and the run result, per attempt in the
607
- eval report, and cumulatively in `/session`.
608
-
609
- ```text
610
- › /session
611
- id: 2026-09-07T...
612
- workspace: /home/me/project
613
- prompts: 4
614
- messages: 18
615
- tokens: 38.2k total, 35.9k in, 2.3k out, 12.0k cached
616
- ```
617
-
618
- **Tokens only, never money.** Converting to a currency needs a model-to-price
619
- table, and that table goes stale silently when a Provider changes its rates —
620
- producing a figure that looks precise and is wrong, which is worse than no
621
- figure because nobody questions a number with a decimal point in it. Token
622
- counts are what the Provider itself reported and do not expire.
623
-
624
- A total is marked incomplete when some call reported nothing, rather than
625
- counting it as zero: a missing figure stays visibly missing instead of quietly
626
- understating the total. Compaction's own summarising call is included, since it
627
- trades a call now against smaller inputs later and that trade cannot be judged
628
- with half of it hidden.
629
-
630
- Streamed Chat Completions only report usage when asked, so chivgent sends
631
- `stream_options: { include_usage: true }`. A self-hosted OpenAI-compatible
632
- endpoint that predates that field may reject the request; `--no-stream` is the
633
- fallback.
634
-
635
- ## The live region
636
-
637
- `--tui` draws a status region above the prompt while a run is in progress: the
638
- turn, whether the model is thinking or running tools, which tools are running
639
- with what they are aimed at and their newest output, elapsed time, and the
640
- running token count.
641
-
642
- ```text
643
- Let me look at how sessions are stored.
644
- read_file src/session.ts (2s)
645
- search_text (1s) src/store.ts:41: export class SessionStore
646
- running tools · turn 2/8 · 11s · 4.2k tokens · ctrl+c to stop
647
- ```
648
-
649
- It is the first consumer of the event stream that runs backwards. Every other
650
- one — streamed output, the session log, extensions, the remote client — is
651
- append-only: an event arrives, it is printed, it is forgotten. This one has to
652
- rebuild *what is happening now* from the same stream and keep it right as more
653
- arrives, which splits into three pieces:
654
-
655
- ```text
656
- reduce(state, event, now) -> ViewState pure
657
- view(state, {width, now}) -> string[] pure
658
- paint(previous, next) the only part that touches a terminal
659
- ```
660
-
661
- Both pure pieces are tested without a terminal, which is where the real
662
- guarantees live. One asymmetry in the event vocabulary is what makes the state
663
- model load-bearing: `message_update` carries a **delta**, `tool_execution_update`
664
- carries a truncated **snapshot**. Appending a snapshot stacks output that was
665
- never produced — the window has already had its front dropped — and replacing on
666
- a delta leaves the last token and loses the answer. An append-only renderer
667
- never has to know the difference, because it just prints whatever arrives. Two
668
- tests fail when either is written the wrong way round.
669
-
670
- It does not take the alternate screen buffer. That would cost the scrollback,
671
- and getting it back means reimplementing scrolling, search and selection —
672
- thousands of lines that teach terminal graphics rather than agent architecture.
673
- A finished turn is flushed to the terminal's own scrollback and dropped from
674
- the state, so a long session never grows it. Input stays with readline for the
675
- same reason: a line editor is not the interesting problem here.
676
-
677
- Painting rewrites only the lines whose content changed, so a ticking timer
678
- costs one line rather than a repaint. A resize invalidates that baseline — the
679
- terminal has reflowed what is on screen — and is the one moment a full repaint
680
- is correct rather than lazy.
681
-
682
- `--tui` needs a terminal on stdin and stderr and an interactive session; it
683
- refuses rather than falling back silently, since a switch that quietly does
684
- nothing makes a mistyped command look like it worked. It is not the default
685
- yet. The welcome panel shows the Provider, model, workspace and session. Use Tab
686
- to complete slash commands (including extension commands), and Up/Down for input
687
- history. During a run ordinary typing is ignored; Ctrl+C cancels immediately.
688
- At an idle prompt Ctrl+C clears the draft; Ctrl+D on an empty line exits cleanly.
689
- Input is single-line: only the first submitted line in an input batch is accepted.
690
- Wait for the prompt before sending another question.
691
- After cancellation the prompt is ready again and partial text stays in scrollback.
692
- A completion line records the outcome, elapsed time and reported token usage.
693
-
694
- Chinese, combining characters and joined emoji are clipped by terminal columns;
695
- short terminals keep the status visible. Resizing repaints the existing region.
696
- All prompts and UI output go to stderr; stdout receives final answers only, so
697
- `chivgent --tui > answers.txt` keeps the terminal interactive while saving answers.
698
-
699
- ```sh
700
- npm run build
701
- node dist/cli.js --tui
702
- npm run demo:tui # Offline interactive demo; no API key or network required
703
- ```
704
-
705
- The demo uses a stub Provider and simulated tool progress; it does not modify the
706
- workspace. See [the TUI guide](docs/tui.md) for controls and verification.
707
-
708
- ## Remote sessions
709
-
710
- A session normally lives and dies with the terminal that started it. `--serve`
711
- keeps it in one process and lets others attach:
712
-
713
- ```bash
714
- # One terminal
715
- chivgent --serve --allow-writes
716
- # chivgent 0.12.0 serving session 2026-09-06T...
717
- # socket: ~/.chivgent/sockets/2026-09-06T....sock
718
- # capabilities: --allow-writes
719
-
720
- # Another terminal
721
- chivgent --servers # list what is running
722
- chivgent --connect <id> # attach interactively
723
- chivgent --connect <id> "question" # ask once and leave
724
- ```
725
-
726
- A client holds no state: it sends prompts and renders the event stream the
727
- server broadcasts. Several clients can attach at once — the extra ones watch the
728
- same run as it happens, and any of them can interrupt it, because the run
729
- belongs to the session rather than to whoever started it. A prompt sent while
730
- another is running is refused rather than queued, and a client that disconnects
731
- mid-run does not cancel it.
732
-
733
- Attaching does not replay history; the session log already holds that. The
734
- protocol is JSON Lines over a Unix socket, one object per line, the same shape
735
- `--json` writes.
736
-
737
- **Whoever can reach the socket has everything the server was started with.** If
738
- it was started with `--allow-shell`, anyone who can connect can make the model
739
- run commands, which is why the server prints its capabilities on startup: the
740
- person attaching cannot see the flags you typed. The boundary is filesystem
741
- permissions — sockets live in an owner-only directory under `CHIVGENT_HOME` —
742
- and there is no TCP listener. For another machine, forward the socket over SSH
743
- so that authentication is SSH's job.
744
-
745
- ## Extensions
746
-
747
- An extension is an ES module that default-exports a function. It is called once
748
- at startup with an API that can add a tool, add a slash command, subscribe to
749
- runtime events, and append to the system prompt.
750
-
751
- ```js
752
- // .chivgent/extensions/word-count.js
753
- export default function (api) {
754
- api.registerTool({
755
- name: "word_count",
756
- description: "Count the words in a workspace file.",
757
- inputSchema: {
758
- type: "object",
759
- properties: { path: { type: "string" } },
760
- required: ["path"],
761
- additionalProperties: false,
762
- },
763
- async execute(args, context) {
764
- const file = await context.workspace.readTextFile(args.path);
765
- return {
766
- content: `${file.content.split(/\s+/).filter(Boolean).length} words`,
767
- isError: false,
768
- };
769
- },
770
- });
771
-
772
- api.contributeSystemPrompt("Use word_count instead of reading a file to count words.");
773
- }
774
- ```
775
-
776
- Extensions are loaded from two places:
777
-
778
- | Location | Loaded |
779
- | --- | --- |
780
- | `<CHIVGENT_HOME>/extensions/` | Always: it is your own machine's configuration |
781
- | `<workspace>/.chivgent/extensions/` | Only after you trust the project |
782
-
783
- Both accept `name.js` and `name/index.js`; nesting stops there. Plain JavaScript
784
- only — compiling TypeScript yourself is cheaper than making chivgent carry a
785
- TypeScript loader. A name already taken by a built-in tool or command is refused,
786
- so an extension cannot shadow `read_file` or `/clear`, and a broken extension is
787
- reported and skipped rather than taking chivgent down with it.
788
-
789
- `chivgent --extensions` lists what loaded and what each one registered.
790
-
791
- ### Project trust
792
-
793
- The first time you run chivgent in a project that ships extensions, it describes
794
- what they are and asks once. The answer is stored in `<CHIVGENT_HOME>/trust.json`
795
- against the resolved directory path, and matched by nearest ancestor, so trusting
796
- `~/work` covers every repository under it. `--forget-trust` removes the decision
797
- covering the current workspace.
798
-
799
- **Trusting a project means letting its authors run code as you.** An extension
800
- executes inside the chivgent process: it is not bound by the workspace, it does
801
- not need `--allow-shell` to run a command, and it can read your environment
802
- including the API key. That is why the question is asked before anything is
803
- imported, and why the answer is no whenever there is nobody to ask — a CI job or
804
- a piped run never loads a checkout's extensions on its own.
805
-
806
- ## Security model
807
-
808
- - API keys come from `--api-key`, an environment variable, or an optional
809
- `auth.json`, in that order, and must never be committed.
810
- - `auth.json` stores keys in plain text. It is opt-in for that reason, and
811
- chivgent warns when its permissions let other users read it.
812
- - `/login` writes it owner-only and does not echo what you type, but the key is
813
- still stored in plain text; it is a convenience, not a secret manager.
814
- - The auth file accepts literal keys only; it cannot expand environment
815
- variables or run shell commands.
816
- - A custom `OPENAI_BASE_URL` receives the configured API key and prompts; use
817
- only endpoints you trust.
818
- - Workspace tools are read-only unless `--allow-writes` is passed; `write_file`
819
- and `edit_file` are not registered at all without it.
820
- - The `bash` tool needs `--allow-shell`, which is separate from `--allow-writes`
821
- and never implied by it. Granting it grants far more: a shell can change or
822
- delete anything the user running chivgent can, inside the workspace or not,
823
- and none of the workspace limits below apply to it.
824
- - Commands are spawned in their own process group and killed as a group, so
825
- cancelling a run does not leave descendants behind.
826
- - Commands inherit chivgent's environment, which includes the API key it is
827
- using. A model with `--allow-shell` can read that key and anything else the
828
- environment holds. Run it with an environment that carries only what the task
829
- needs.
830
- - Truncated command output is written to a temp file so the model can read the
831
- rest. The file is owner-only, but it is not deleted when the run ends: it can
832
- hold anything the command printed. Clear the temp directory after sensitive
833
- work.
834
- - Session logs record command output as well as file excerpts once
835
- `--allow-shell` is on.
836
- - Project extensions are loaded only after an explicit, recorded trust decision,
837
- and never without a terminal to ask at. An extension runs in-process with your
838
- permissions and is bound by none of the workspace limits above, so trusting a
839
- project is the same order of authority as `--allow-shell`, reached by cloning
840
- a repository rather than by typing a flag.
841
- - User extensions under `<CHIVGENT_HOME>/extensions/` are always loaded; that
842
- directory is your own configuration.
843
- - Extensions cannot take the name of a built-in tool or command.
844
- - `trust.json`, like the session log and the auth file, is written owner-only.
845
- - A served session is reachable by anyone who can open its socket, and they get
846
- every capability the server was started with. Sockets live in an owner-only
847
- directory; the directory is the real gate, because a socket file exists
848
- briefly with default permissions before it can be restricted.
849
- - chivgent never listens on TCP. Use SSH forwarding to reach another machine.
850
- - Writes resolve the deepest existing ancestor and reject a symlink at any
851
- segment, so a planted link cannot redirect a write out of the workspace.
852
- - Writes are staged in a sibling temp file and renamed into place, so an
853
- interrupted write cannot truncate an existing file.
854
- - `edit_file` refuses an edit whose `old_text` is missing or matches more than
855
- once, so an imprecise edit fails instead of changing the wrong line.
856
- - Paths must remain inside the current workspace.
857
- - Real-path checks block `..` traversal and symlink escapes.
858
- - File size and binary-content checks limit unsafe reads.
859
- - Discovery respects the root `.gitignore` and fixed generated-directory ignores.
860
- - Common credential and private-key paths are denied across all workspace tools.
861
- - Tool results are limited to 64 KiB; reads, scans, depth, and result counts are bounded.
862
- - Tool inputs are untrusted and validated before execution.
863
- - The agent stops after a bounded number of model turns.
864
- - Session logs under `~/.chivgent/sessions` contain prompts, answers, and tool
865
- results, including file excerpts. Use `--no-session` in sensitive workspaces,
866
- and treat the log directory like the project it describes.
867
- - Session ids are validated before they become file paths.
868
-
869
- This is an educational MVP, not a hardened sandbox. There is no permission
870
- system: capabilities are coarse, session-level switches, and neither
871
- `--allow-writes` nor `--allow-shell` asks for per-action confirmation. That is
872
- deliberate. Once a shell exists, a command allowlist is bypassed by a single
873
- `sh -c`, and a prompt on every action only trains people to approve without
874
- reading, so chivgent states the boundary instead of pretending to enforce one.
875
-
876
- Use these switches on work you have committed. If you need a real boundary,
877
- put the whole process in a container and give that container only what the task
878
- needs. Review the code and threat model before pointing chivgent at a sensitive
879
- project.
880
-
881
- ## Roadmap
882
-
883
- - [x] Minimal tool-calling agent loop
884
- - [x] Safe `read_file` tool
885
- - [x] OpenAI and DeepSeek Providers
886
- - [x] Reusable OpenAI-compatible Chat Completions adapter
887
- - [x] Custom OpenAI-compatible CLI Provider
888
- - [x] Project discovery tools: `list_files`, `search_text`, and ranged `read_file`
889
- - [x] Streaming output and runtime events
890
- - [x] Persistent multi-turn sessions
891
- - [x] Context-window management and compaction
892
- - [x] Opt-in `write_file` and `edit_file` behind `--allow-writes`
893
- - [x] Provider registry and credential resolution chain
894
- - [x] `bash` tool with streaming output, behind `--allow-shell`
895
- - [x] Extensions and project trust
896
- - [x] Remote sessions over a local socket
897
- - [x] An eval suite with deterministic graders and pass rates
898
- - [x] Token usage from the Provider, aggregated per run and per eval
899
- - [x] A live status region driven by the event stream (`--tui`)
900
-
901
- Per-command confirmation prompts and command allowlists are deliberately not
902
- planned. Once a shell tool exists, `bash` can do anything `write_file` can and
903
- more, so an allowlist is bypassed by a single `sh -c` and a per-command prompt
904
- only trains people to approve without reading. Capabilities are coarse,
905
- session-level switches; the real boundary is a container.
906
-
907
- ## Documentation
908
-
909
- - [Stage 1: Minimal Agent design](docs/stage-1-minimal-agent.md)
910
- - [DeepSeek Provider design](docs/deepseek-provider.md)
911
- - [Stage 2: Project Discovery implementation design](docs/stage-2-project-discovery.md)
912
- - [Stage 3: Runtime Events and Streaming design](docs/stage-3-runtime-events.md)
913
- - [Stage 4: Sessions and interactive mode design](docs/stage-4-sessions.md)
914
- - [Stage 5: Write tools and the workspace split](docs/stage-5-write-tools.md)
915
- - [Stage 6: Provider registry and credential chain](docs/stage-6-provider-registry.md)
916
- - [Stage 7: Context budget and compaction](docs/stage-7-context-management.md)
917
- - [Stage 8: Shell tool and streaming subprocesses](docs/stage-8-shell-tool.md)
918
- - [Stage 9: Extensions and project trust](docs/stage-9-extensions.md)
919
- - [Stage 10: Remote sessions](docs/stage-10-remote-sessions.md)
920
- - [Stage 11: Evals](docs/stage-11-evals.md)
921
- - [Stage 12: Token usage](docs/stage-12-usage.md)
922
- - [Release process](docs/releasing.md)
923
-
924
- ## Contributing
925
-
926
- Issues and focused pull requests are welcome. Before submitting a change, run:
927
-
928
- ```bash
929
- npm run check
930
- npm test
931
- npm run build
932
- ```
180
+ Tests use scripted LLM clients and never spend API credits.
181
+ `npm run release:check` runs the full gate, including an `npm pack` dry run.
182
+ To try the checkout as a global command: `npm install -g .`
933
183
 
934
- Please keep Provider-specific types inside `src/providers/` and keep the core
935
- Agent runtime independent from vendor SDK schemas.
184
+ Issues and focused pull requests are welcome. Keep Provider-specific types in
185
+ `src/providers/` so the agent runtime stays independent of vendor SDKs.
936
186
 
937
187
  ## License
938
188
 
939
- Licensed under the [MIT License](LICENSE).
189
+ [MIT](LICENSE)