chivgent 1.0.0 → 1.1.0

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