sakuracode 0.0.0-stage → 0.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 (117) hide show
  1. package/README.md +256 -2
  2. package/README.zh-CN.md +257 -0
  3. package/dist/LICENSE +21 -0
  4. package/dist/THIRD_PARTY_NOTICES.md +13 -0
  5. package/dist/code/bin/sakuracode.mjs +5 -0
  6. package/dist/code/src/agent-session.js +41 -0
  7. package/dist/code/src/ansi.js +220 -0
  8. package/dist/code/src/cli.js +211 -0
  9. package/dist/code/src/config.js +183 -0
  10. package/dist/code/src/exec-mode.js +84 -0
  11. package/dist/code/src/framework-testing.js +2 -0
  12. package/dist/code/src/framework.js +3 -0
  13. package/dist/code/src/idle-compaction.js +38 -0
  14. package/dist/code/src/images.js +18 -0
  15. package/dist/code/src/invocation.js +39 -0
  16. package/dist/code/src/markdown.js +383 -0
  17. package/dist/code/src/mentions.js +22 -0
  18. package/dist/code/src/permissions.js +31 -0
  19. package/dist/code/src/providers.js +56 -0
  20. package/dist/code/src/reports.js +81 -0
  21. package/dist/code/src/run-driver.js +76 -0
  22. package/dist/code/src/session-controller.js +405 -0
  23. package/dist/code/src/session-store.js +92 -0
  24. package/dist/code/src/shell.js +36 -0
  25. package/dist/code/src/slash-commands.js +83 -0
  26. package/dist/code/src/status-line.js +26 -0
  27. package/dist/code/src/toml.js +256 -0
  28. package/dist/code/src/transcript.js +288 -0
  29. package/dist/code/src/tui/app.js +267 -0
  30. package/dist/code/src/tui/keys.js +151 -0
  31. package/dist/code/src/tui/line-editor.js +324 -0
  32. package/dist/code/src/tui/live-region.js +71 -0
  33. package/dist/code/src/tui/screen.js +69 -0
  34. package/dist/code/src/tui/transcript-view.js +102 -0
  35. package/dist/licenses/codex-Apache-2.0.txt +201 -0
  36. package/dist/licenses/gitleaks-MIT.txt +21 -0
  37. package/dist/licenses/pi-mono-MIT.txt +21 -0
  38. package/dist/packages/agent/src/compose.js +91 -0
  39. package/dist/packages/agent/src/index.js +161 -0
  40. package/dist/packages/agent/src/restrict-tools.js +11 -0
  41. package/dist/packages/agent/src/system-prompt.js +49 -0
  42. package/dist/packages/agent/src/wake.js +71 -0
  43. package/dist/packages/env/src/confine.js +40 -0
  44. package/dist/packages/env/src/connect-ssh.js +242 -0
  45. package/dist/packages/env/src/index.js +4 -0
  46. package/dist/packages/env/src/local.js +206 -0
  47. package/dist/packages/env/src/remote-stream.js +143 -0
  48. package/dist/packages/env/src/remote-types.js +10 -0
  49. package/dist/packages/env/src/remote.js +183 -0
  50. package/dist/packages/evals/src/bfcl.js +71 -0
  51. package/dist/packages/evals/src/index.js +2 -0
  52. package/dist/packages/evals/src/png.js +30 -0
  53. package/dist/packages/evals/src/pool.js +22 -0
  54. package/dist/packages/evals/src/providers.js +23 -0
  55. package/dist/packages/kernel/src/channel.js +34 -0
  56. package/dist/packages/kernel/src/compaction.js +86 -0
  57. package/dist/packages/kernel/src/constants.js +5 -0
  58. package/dist/packages/kernel/src/goal.js +65 -0
  59. package/dist/packages/kernel/src/hooks.js +153 -0
  60. package/dist/packages/kernel/src/index.js +4 -0
  61. package/dist/packages/kernel/src/loop.js +556 -0
  62. package/dist/packages/kernel/src/prompts.js +99 -0
  63. package/dist/packages/kernel/src/system.js +49 -0
  64. package/dist/packages/kernel/src/types.js +23 -0
  65. package/dist/packages/kernel/src/usage.js +16 -0
  66. package/dist/packages/llm/src/chat/compat.js +16 -0
  67. package/dist/packages/llm/src/chat/errors.js +61 -0
  68. package/dist/packages/llm/src/chat/request.js +109 -0
  69. package/dist/packages/llm/src/chat/retry.js +57 -0
  70. package/dist/packages/llm/src/chat/stream.js +122 -0
  71. package/dist/packages/llm/src/index.js +120 -0
  72. package/dist/packages/plugins/agents/src/index.js +206 -0
  73. package/dist/packages/plugins/agents/src/view.js +60 -0
  74. package/dist/packages/plugins/context/src/index.js +67 -0
  75. package/dist/packages/plugins/limits/src/cost.js +3 -0
  76. package/dist/packages/plugins/limits/src/index.js +31 -0
  77. package/dist/packages/plugins/redaction/src/engine.js +110 -0
  78. package/dist/packages/plugins/redaction/src/index.js +69 -0
  79. package/dist/packages/plugins/redaction/src/restore.js +44 -0
  80. package/dist/packages/plugins/redaction/src/rules.gen.js +2059 -0
  81. package/dist/packages/plugins/resilience/src/index.js +86 -0
  82. package/dist/packages/plugins/review/src/chain.js +27 -0
  83. package/dist/packages/plugins/review/src/hard-rules.js +156 -0
  84. package/dist/packages/plugins/review/src/index.js +93 -0
  85. package/dist/packages/plugins/review/src/policy.js +65 -0
  86. package/dist/packages/plugins/review/src/reviewer.js +207 -0
  87. package/dist/packages/plugins/review/src/shell.js +124 -0
  88. package/dist/packages/plugins/tools/src/artifacts.js +61 -0
  89. package/dist/packages/plugins/tools/src/builtin/args.js +46 -0
  90. package/dist/packages/plugins/tools/src/builtin/bash.js +110 -0
  91. package/dist/packages/plugins/tools/src/builtin/edit.js +71 -0
  92. package/dist/packages/plugins/tools/src/builtin/image.js +84 -0
  93. package/dist/packages/plugins/tools/src/builtin/read.js +89 -0
  94. package/dist/packages/plugins/tools/src/builtin/write.js +35 -0
  95. package/dist/packages/plugins/tools/src/edit-match.js +159 -0
  96. package/dist/packages/plugins/tools/src/fold.js +65 -0
  97. package/dist/packages/plugins/tools/src/index.js +10 -0
  98. package/dist/packages/plugins/toolset/src/bm25.js +35 -0
  99. package/dist/packages/plugins/toolset/src/codemode.js +226 -0
  100. package/dist/packages/plugins/toolset/src/index.js +3 -0
  101. package/dist/packages/plugins/toolset/src/mcp.js +173 -0
  102. package/dist/packages/plugins/toolset/src/search.js +57 -0
  103. package/dist/packages/plugins/trace/src/index.js +29 -0
  104. package/dist/packages/plugins/trace/src/modules.js +28 -0
  105. package/dist/packages/session/src/file-log.js +144 -0
  106. package/dist/packages/session/src/fork.js +70 -0
  107. package/dist/packages/session/src/index.js +3 -0
  108. package/dist/packages/session/src/log-core.js +47 -0
  109. package/dist/packages/session/src/memory-log.js +7 -0
  110. package/dist/packages/session/src/projection.js +107 -0
  111. package/dist/packages/session/src/tree.js +35 -0
  112. package/dist/packages/testing/src/faux.js +69 -0
  113. package/dist/packages/testing/src/index.js +2 -0
  114. package/dist/packages/testing/src/rng.js +17 -0
  115. package/dist/sdk/index.js +10 -0
  116. package/dist/sdk/testing.js +2 -0
  117. package/package.json +54 -4
package/README.md CHANGED
@@ -1,3 +1,257 @@
1
- # Temporary Holding Version
1
+ # sakuracode
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [English](README.md) · [简体中文](README.zh-CN.md)
4
+
5
+ **sakuracode is an open-source terminal coding agent (a command-line AI coding assistant).** It reads and edits your code, runs shell commands and tests, and works toward a goal on its own, using any model endpoint that speaks Chat Completions (you choose it in a config file; DeepSeek and OpenCode Go are built in as examples). Every tool call is checked by an automatic review before it runs. It is built on the [Sakura agent framework](../README.md) (npm package `sakura-agent`) and is similar in use to OpenAI Codex CLI and Claude Code.
6
+
7
+ ```bash
8
+ npm install -g sakuracode
9
+ export MY_API_KEY=... # the key of the endpoint you set up in ~/.sakura/config.toml (see below)
10
+ sakuracode "explain this repository"
11
+ ```
12
+
13
+ * Interactive inline session: your terminal's scrollback is kept, the prompt and the status line stay on the last rows of the window. A big paste is shown as `[Pasted Content N chars]` and sent in full.
14
+ * Replies are rendered as **Markdown** (headings, lists, tables, fenced code, links). `exec` writes Markdown too.
15
+ * Slash commands, `!shell`, `@file` mentions, resume, fork, undo, `/compact`, long-running goals (`/goal`), MCP servers, image input.
16
+ * Steer, queue or cancel while the agent works.
17
+ * `exec` mode for scripts and CI, with a `--json` event stream; config file with profiles.
18
+ * Automatic review of every tool call (hard rules, then a reviewer model); every decision is recorded in the session file.
19
+ * Long sessions: the conversation is compacted when the context fills, and sessions can be resumed later.
20
+ * Not tied to a provider: any Chat Completions endpoint is set up in the config file; DeepSeek and OpenCode Go are built in for convenience.
21
+
22
+ ## Defaults
23
+
24
+ | | |
25
+ |---|---|
26
+ | Working directory | The agent starts in the directory where you launched the command and **may go anywhere**. Nothing is restricted to the directory and no outside-directory rule is enabled. (`-C DIR` only changes where it starts.) |
27
+ | Review | **On.** Every tool call goes through the framework's review plugin: hard rules first, then a reviewer model. A refused call goes back to the agent as a tool error. There is no approval prompt: the review decides on its own. |
28
+ | Redaction | **Off.** Turn it on with `--redact`, `redaction = true` in the config, or `/redaction on`. It hides secrets from the model provider; the session file on disk keeps the real values. |
29
+ | Output | Markdown. |
30
+
31
+ ### Is it safe to let it run commands?
32
+
33
+ It is **not sandboxed**: the agent runs with your user's permissions and may read and change files anywhere you can, and run any command. What stands between it and your machine is the automatic review (hard rules first, then a reviewer model), which checks writes, command expansions, credential access and catastrophic patterns, refuses what it judges harmful, and records every decision in the session file. A review is a judgement, not a wall: for untrusted work run sakuracode inside a container or virtual machine you provide. The conversation, including the content of files the agent reads, is sent to the model provider you chose (the reviewer is a second request to the same provider unless you configure another); with `--redact`, secrets are replaced by placeholders before a request leaves, while the session file on your disk keeps the real values.
34
+
35
+ ## Install and run
36
+
37
+ Requires Node 26 or newer (older versions print an npm warning).
38
+
39
+ ```bash
40
+ npm install -g sakuracode
41
+ ```
42
+
43
+ ### First start: tell it which model to use
44
+
45
+ sakuracode is not tied to one provider. It works with any endpoint that speaks Chat Completions (streaming and tool calls). You describe your endpoint once in `~/.sakura/config.toml`; the API key stays in an environment variable and is never stored by sakuracode.
46
+
47
+ ```bash
48
+ mkdir -p ~/.sakura
49
+ cat > ~/.sakura/config.toml <<'EOF'
50
+ provider = "myprovider"
51
+
52
+ [providers.myprovider]
53
+ base_url = "https://api.example.com/v1" # your endpoint
54
+ model = "your-model-name"
55
+ key_env = "MY_API_KEY" # the environment variable that holds the key
56
+ context_window = 128000 # the model's context window, in tokens
57
+ compat = "deepseek" # a compatibility preset: deepseek, moonshot, openai
58
+ # headers = { "x-team" = "a" } # extra request headers, if your endpoint wants them
59
+ # session_header = "x-session-id" # a header that gets the session id, if it wants one
60
+ EOF
61
+ export MY_API_KEY=...
62
+ cd your-project
63
+ sakuracode "explain this repository"
64
+ ```
65
+
66
+ `context_window` matters: the status line and automatic compaction count against it. `sakuracode providers` shows which providers are known and whether their key is set (never the key itself). It creates `~/.sakura/sessions/` on the first start.
67
+
68
+ **Example: OpenCode Go.** This is the configuration used while developing sakuracode; any other endpoint is set up the same way:
69
+
70
+ ```toml
71
+ provider = "opencode"
72
+
73
+ [providers.opencode]
74
+ base_url = "https://opencode.ai/zen/go/v1"
75
+ model = "deepseek-v4.1-flash"
76
+ key_env = "OPENCODE_API_KEY"
77
+ context_window = 1000000
78
+ compat = "deepseek"
79
+ images = true
80
+ session_header = "x-opencode-session" # OpenCode Go refuses a request without a session id
81
+ ```
82
+
83
+ For convenience, DeepSeek and OpenCode Go are also built in: with only `DEEPSEEK_API_KEY` or `OPENCODE_API_KEY` set and no config file, sakuracode uses their built-in settings (a 128,000-token window) and picks the first one whose key is set. `--provider NAME` picks a provider for a single run.
84
+
85
+ If something is missing, it says what to do:
86
+
87
+ | Situation | What you see | Exit code |
88
+ |---|---|---|
89
+ | No key set for any provider | `no API key found; set one of: ...` | 2 |
90
+ | The provider you chose has no key | `provider "myprovider" needs the environment variable MY_API_KEY` | 1 |
91
+ | Interactive command in a pipe or script | `the interactive session needs a terminal; use sakuracode exec for pipes and scripts` | 2 |
92
+
93
+ ### Commands
94
+
95
+ ```bash
96
+ sakuracode # interactive session
97
+ sakuracode "fix the failing test" # interactive, the prompt is sent at once
98
+ sakuracode --provider opencode # choose the provider
99
+ sakuracode -i error.png "what does this error mean?" # attach an image
100
+ sakuracode resume --last # continue the latest session (a picker without --last)
101
+ sakuracode fork --last # copy the latest session into a new one
102
+ sakuracode review --base main # review the changes against main, print the review
103
+ sakuracode exec "summarise this repo" # one run, Markdown on stdout
104
+ sakuracode exec --json "list the TODOs" | jq . # events as JSON lines
105
+ echo "explain this error" | sakuracode exec - # prompt from stdin
106
+ sakuracode sessions # list sessions
107
+ sakuracode providers # providers and whether their key is set (never the key)
108
+ ```
109
+
110
+ ### Options
111
+
112
+ | Option | Meaning |
113
+ |---|---|
114
+ | `--provider NAME` | `deepseek`, `opencode`, or a provider from the config file |
115
+ | `-p, --profile NAME` | settings profile from the config file |
116
+ | `-m, --model NAME` | model of the provider |
117
+ | `--permissions NAME` | `auto` (default), `read-only`, `strict`, `full-access` |
118
+ | `-i, --image FILE[,FILE]` | attach images to the first prompt (png, jpg, gif, webp, bmp); `@pic.png` in a prompt or a dropped file path does the same |
119
+ | `--redact` / `--no-redact` | redaction on / off (default off) |
120
+ | `--thinking LEVEL` | `off`, `low`, `medium`, `high` |
121
+ | `--max-tokens N` | output limit per model request |
122
+ | `-C, --cwd DIR` | directory to start in |
123
+ | `--resume REF` | continue a session (id, id prefix, `last`) |
124
+ | `--json` | `exec`: events as JSON lines (first line `session`, last line `result`) |
125
+ | `--raw` | `exec`: write the Markdown as it is even on a terminal |
126
+ | `-q, --quiet` | `exec`: no progress lines on stderr (warnings and errors are still shown) |
127
+ | `--output-last-message FILE` | `exec`: also write the final reply to FILE |
128
+ | `--no-color` | no colours |
129
+
130
+ `exec` exit codes: 0 done, 1 error, 2 usage error or paused by the review breaker, 130 cancelled. In `exec`, progress goes to stderr (one line per tool call) and the final reply goes to stdout; on a terminal it is rendered, in a pipe it is the raw Markdown.
131
+
132
+ ## In the session
133
+
134
+ | Key | While idle | While the agent works |
135
+ |---|---|---|
136
+ | Enter | send | **steer**: delivered before the agent's next model request |
137
+ | Tab | complete a slash command | **queue**: handled when the agent would stop |
138
+ | Esc | close the popup | **cancel** the run |
139
+ | Ctrl-C | clear the prompt; twice on an empty prompt leaves | cancel the run |
140
+ | Ctrl-D | leave (empty prompt) | |
141
+ | Shift-Enter, Alt-Enter, Ctrl-J, or `\` then Enter | new line in the prompt | |
142
+ | Up / Down | history, or move between lines | |
143
+ | Ctrl-A/E/K/U/W/Y, Alt-arrows | readline-style editing | |
144
+
145
+ Messages typed during a run show above the prompt as "steering" or "queued" until the agent takes them.
146
+
147
+ ### Slash commands
148
+
149
+ | Command | |
150
+ |---|---|
151
+ | `/help` | list the commands |
152
+ | `/status` | session, directory, model, permissions, context left, input (cached and new), cache hit rate, output |
153
+ | `/model [provider[/model]]` | show or switch provider and model (the agent is closed and recreated on the same session file) |
154
+ | `/permissions [auto\|read-only\|strict\|full-access]` | show or change what is reviewed |
155
+ | `/redaction [on\|off]` | hide secrets from the model |
156
+ | `/rename <name>` | give the session a name |
157
+ | `/compact` | summarise the conversation to free context |
158
+ | `/new`, `/clear` | start a new session (`/clear` also clears the screen) |
159
+ | `/resume [id\|last]` | continue an earlier session (a picker without argument) |
160
+ | `/fork` | copy this session into a new one and continue there |
161
+ | `/undo` | go back to before your last message and put it back in the prompt (Esc twice on an empty prompt does the same) |
162
+ | `/init` | have the agent write an `AGENTS.md` |
163
+ | `/review [focus]` | have the agent review the uncommitted changes |
164
+ | `/diff` | show `git diff` |
165
+ | `/goal [objective\|clear]` | show the goal; with text, set it and start working on it; `clear` ends it |
166
+ | `/mcp` | show MCP servers |
167
+ | `/exit`, `/quit` | leave |
168
+
169
+ `!command` runs a shell command yourself and shows the output (the agent does not see it). `@path` in a prompt names an existing file or directory to the agent.
170
+
171
+ ### Permission presets
172
+
173
+ | Preset | |
174
+ |---|---|
175
+ | `auto` (default) | review on |
176
+ | `read-only` | review on, and the agent only gets the `read` tool |
177
+ | `strict` | review on; three refusals in a row pause the run |
178
+ | `full-access` | review off |
179
+
180
+ None of them restricts the working directory: the review plugin runs with `directoryRules: false`, so a path outside the launch directory is not a reason to refuse. Writes, command expansions, credentials and catastrophic patterns are still checked by the hard rules and the reviewer model.
181
+
182
+ ## Config
183
+
184
+ `~/.sakura/config.toml` (set `SAKURA_HOME` to use another directory; sessions are stored in `sessions/` beside it). Precedence: defaults < file < profile < environment (`SAKURA_PROVIDER`, `SAKURA_MODEL`) < flags.
185
+
186
+ ```toml
187
+ provider = "opencode"
188
+ permissions = "auto"
189
+ redaction = false
190
+ thinking = "medium"
191
+ max_tokens = 32000
192
+ # idle auto-compaction: after cache_ttl_minutes without activity, a context of at least
193
+ # auto_compact_min_tokens (never below 1000) is compacted; a profile may set these too
194
+ cache_ttl_minutes = 5
195
+ auto_compact_min_tokens = 20000
196
+ auto_compact_idle = true
197
+
198
+ [profiles.cheap]
199
+ provider = "deepseek"
200
+ thinking = "low"
201
+
202
+ [providers.lab] # any Chat Completions endpoint
203
+ base_url = "https://example.com/v1"
204
+ model = "some-model"
205
+ key_env = "LAB_API_KEY"
206
+ context_window = 128000
207
+ compat = "deepseek" # a framework compat preset: deepseek, moonshot, openai
208
+ headers = { "x-team" = "a" }
209
+ session_header = "x-session-id" # header that gets the session id, if the endpoint wants one
210
+
211
+ [mcp.servers.files]
212
+ command = "npx"
213
+ args = ["-y", "some-mcp-server"]
214
+ ```
215
+
216
+ ### Providers
217
+
218
+ | Name | Endpoint | Model | Key |
219
+ |---|---|---|---|
220
+ | `deepseek` | `https://api.deepseek.com` | `deepseek-flash` | `DEEPSEEK_API_KEY` |
221
+ | `opencode` | `https://opencode.ai/zen/go/v1` | `deepseek-v4.1-flash` | `OPENCODE_API_KEY` |
222
+
223
+ Both use the framework's `compatPresets.deepseek`. OpenCode Go needs a session header on every request; the terminal sends `x-opencode-session: <session id>`. Without `-p`, the first provider whose key is set is used. Keys are read from the environment and never printed or stored.
224
+
225
+ ## Frequently asked questions
226
+
227
+ **Which models does it work with?** Any endpoint that speaks Chat Completions (streaming and tool calls), set up under `[providers.NAME]` in `~/.sakura/config.toml`. It has been tried with DeepSeek and OpenCode Go, which are also built in.
228
+
229
+ **Where are my sessions stored, and how do I continue one?** In `~/.sakura/sessions/` (set `SAKURA_HOME` to use another directory), one append-only file per session. `sakuracode resume --last` continues the latest one; `sakuracode sessions` lists them; `/fork` copies one.
230
+
231
+ **What happens when the conversation gets too long?** It is summarised automatically when the context fills (and after a few idle minutes), or when you type `/compact`; the recent part stays word for word. `/status` shows how full the context is and how much of the input came from the provider's cache.
232
+
233
+ **Can I use it in scripts and CI?** Yes: `sakuracode exec "task"` runs one prompt without a screen, writes the reply to stdout and progress to stderr, and exits with 0 (done), 1 (error), 2 (usage error or paused by the review) or 130 (cancelled). `--json` gives the events as JSON lines.
234
+
235
+ **Can I keep it working toward a goal on its own?** Yes: `/goal "make the tests pass"` sets a goal and starts working; the agent is checked against it before it may stop.
236
+
237
+ **How is it different from Codex CLI?** Codex CLI is OpenAI's product with sandbox modes and approval policies. sakuracode is the reference terminal of the open-source Sakura framework: it uses automatic review instead of a sandbox and approval prompts, works with any Chat Completions endpoint, and is built only on the public SDK, so everything it does you can also do in your own agents with `sakura-agent`.
238
+
239
+ ## Development
240
+
241
+ Run these commands from the repository root:
242
+
243
+ ```bash
244
+ npm ci
245
+ npm --prefix code start
246
+ npm --prefix code test # includes pseudo-terminal tests (needs python3)
247
+ npm --prefix code run typecheck
248
+ npm pack ./code # build the release tarball (npm publish ./code publishes it)
249
+ ```
250
+
251
+ The suite uses no network. To test a real provider, run `node --disable-warning=ExperimentalWarning code/test/live/interactive.ts PROVIDER WORKDIR [HOME]`.
252
+
253
+ Documents: [docs/design.md](docs/design.md) (modules and interfaces), [docs/test-report.md](docs/test-report.md) (what was run, including failures), [docs/framework-gaps.md](docs/framework-gaps.md) (what the framework lacks, with proposed changes).
254
+
255
+ ## Not done
256
+
257
+ Clipboard image paste, wake-up of the agent by finished background commands, sub-agent definitions, a `--full-auto` style flag (use `--permissions full-access`), themes, and a TOML array-of-tables config. See `docs/framework-gaps.md` for why some of them wait on the framework.
@@ -0,0 +1,257 @@
1
+ # sakuracode
2
+
3
+ [English](README.md) · [简体中文](README.zh-CN.md)
4
+
5
+ **sakuracode 是一个开源的终端编程 Agent(命令行 AI 编程助手)。** 它读写你的代码,运行 shell 命令和测试,能朝着一个目标自己一直做下去;支持任何说 Chat Completions 的模型接口(在配置文件里自己选;DeepSeek 和 OpenCode Go 作为示例内置)。每一次工具调用在执行前都会经过自动审核。它基于 [Sakura Agent 框架](../README.zh-CN.md)(npm 包 `sakura-agent`),用法上类似 OpenAI Codex CLI 和 Claude Code。
6
+
7
+ ```bash
8
+ npm install -g sakuracode
9
+ export MY_API_KEY=... # 你在 ~/.sakura/config.toml 里设置的接口的密钥(见下文)
10
+ sakuracode "解释一下这个仓库"
11
+ ```
12
+
13
+ * 交互式的内联会话:终端的回滚历史保留,提示符和状态栏停在窗口最后几行。大段粘贴显示成 `[Pasted Content N chars]`,但完整发送。
14
+ * 回复按 **Markdown** 渲染(标题、列表、表格、代码块、链接)。`exec` 也输出 Markdown。
15
+ * 斜杠命令、`!shell`、`@文件` 引用、续接、分叉、撤销、`/compact`、长时间目标(`/goal`)、MCP 服务、图片输入。
16
+ * Agent 工作时可以引导、排队或取消。
17
+ * 适合脚本和 CI 的 `exec` 模式,带 `--json` 事件流;有带多个方案(profile)的配置文件。
18
+ * 对每一次工具调用自动审核(先硬规则,再审核模型);每一次决定都记录在会话文件里。
19
+ * 长会话:上下文快满时自动压缩对话,会话之后可以续接。
20
+ * 不绑定服务商:任何 Chat Completions 接口都在配置文件里设置;DeepSeek 和 OpenCode Go 为了方便而内置。
21
+
22
+ ## 默认设置
23
+
24
+ | | |
25
+ |---|---|
26
+ | 工作目录 | Agent 从你启动命令的目录开始,并且**可以去任何地方**。没有任何东西被限制在这个目录里,也没有启用"目录之外"的规则。(`-C DIR` 只改变起点。) |
27
+ | 审核 | **开启。** 每一次工具调用都经过框架的审核插件:先硬规则,再审核模型。被拒绝的调用会作为工具错误回给 Agent。没有批准提示:审核自己做决定。 |
28
+ | 脱敏 | **关闭。** 用 `--redact`、配置里的 `redaction = true` 或 `/redaction on` 开启。它向模型服务商隐藏密钥;磁盘上的会话文件保留真实内容。 |
29
+ | 输出 | Markdown。 |
30
+
31
+ ### 让它运行命令安全吗?
32
+
33
+ 它**没有沙箱**:Agent 以你的用户权限运行,可以读写你能访问的任何位置的文件,运行任何命令。挡在它和你的机器之间的是自动审核(先硬规则,再审核模型):审核检查写入、命令展开、凭据访问和灾难性的命令模式,拒绝它判断有害的操作,并把每一次决定记录在会话文件里。审核是一种判断,不是一堵墙:对不可信的任务,请把 sakuracode 放在你自己提供的容器或虚拟机里运行。对话(包括 Agent 读到的文件内容)会发送给你选择的模型服务商(审核是向同一个服务商的第二个请求,除非你另外配置);使用 `--redact` 时,密钥在请求发出前被换成占位符,而你磁盘上的会话文件保留真实内容。
34
+
35
+ ## 安装和运行
36
+
37
+ 需要 Node 26 或更新版本(版本更低时 npm 会给出警告)。
38
+
39
+ ```bash
40
+ npm install -g sakuracode
41
+ ```
42
+
43
+ ### 第一次启动:告诉它用哪个模型
44
+
45
+ sakuracode 不绑定某一家服务商。任何支持 Chat Completions(流式和工具调用)的接口都能用。你只需要在 `~/.sakura/config.toml` 里描述一次你的接口;API 密钥放在环境变量里,sakuracode 从不保存它。
46
+
47
+ ```bash
48
+ mkdir -p ~/.sakura
49
+ cat > ~/.sakura/config.toml <<'EOF'
50
+ provider = "myprovider"
51
+
52
+ [providers.myprovider]
53
+ base_url = "https://api.example.com/v1" # 你的接口地址
54
+ model = "your-model-name"
55
+ key_env = "MY_API_KEY" # 存放密钥的环境变量名
56
+ context_window = 128000 # 模型的上下文窗口,单位 token
57
+ compat = "deepseek" # 兼容预设:deepseek、moonshot、openai
58
+ # headers = { "x-team" = "a" } # 额外的请求头(如果接口需要)
59
+ # session_header = "x-session-id" # 接收会话 id 的请求头(如果接口需要)
60
+ EOF
61
+ export MY_API_KEY=...
62
+ cd 你的项目
63
+ sakuracode "解释一下这个仓库"
64
+ ```
65
+
66
+ `context_window` 很重要:状态栏和自动压缩都以它为准。`sakuracode providers` 显示已知的服务商以及它们的密钥是否已设置(不会显示密钥本身)。第一次启动时会创建 `~/.sakura/sessions/`。
67
+
68
+ **示例:OpenCode Go。**这是开发 sakuracode 时用的配置;其他任何接口的设置方式都一样:
69
+
70
+ ```toml
71
+ provider = "opencode"
72
+
73
+ [providers.opencode]
74
+ base_url = "https://opencode.ai/zen/go/v1"
75
+ model = "deepseek-v4.1-flash"
76
+ key_env = "OPENCODE_API_KEY"
77
+ context_window = 1000000
78
+ compat = "deepseek"
79
+ images = true
80
+ session_header = "x-opencode-session" # OpenCode Go 会拒绝没有会话 id 的请求
81
+ ```
82
+
83
+ 为了方便,DeepSeek 和 OpenCode Go 也是内置的:只设置了 `DEEPSEEK_API_KEY` 或 `OPENCODE_API_KEY`、没有配置文件时,sakuracode 使用它们的内置设置(128,000 token 窗口),并选第一个设置了密钥的。`--provider 名字` 可以为单次运行指定服务商。
84
+
85
+ 缺什么,它会告诉你怎么做:
86
+
87
+ | 情况 | 你会看到 | 退出码 |
88
+ |---|---|---|
89
+ | 任何服务商都没有设置密钥 | `no API key found; set one of: ...` | 2 |
90
+ | 你选的服务商没有设置密钥 | `provider "myprovider" needs the environment variable MY_API_KEY` | 1 |
91
+ | 在管道或脚本里运行交互命令 | `the interactive session needs a terminal; use sakuracode exec for pipes and scripts` | 2 |
92
+
93
+ ### 命令
94
+
95
+ ```bash
96
+ sakuracode # 交互式会话
97
+ sakuracode "修好失败的测试" # 交互式,提示立刻发出
98
+ sakuracode --provider opencode # 选择服务商
99
+ sakuracode -i error.png "这个报错是什么意思?" # 附上图片
100
+ sakuracode resume --last # 续接最近的会话(不加 --last 会出现选择列表)
101
+ sakuracode fork --last # 把最近的会话复制成新会话
102
+ sakuracode review --base main # 对照 main 审查改动,并打印审查结果
103
+ sakuracode exec "总结这个仓库" # 运行一次,Markdown 输出到 stdout
104
+ sakuracode exec --json "列出 TODO" | jq . # 事件以 JSON 行输出
105
+ echo "解释这个报错" | sakuracode exec - # 从 stdin 读取提示
106
+ sakuracode sessions # 列出会话
107
+ sakuracode providers # 服务商以及它们的密钥是否已设置(不显示密钥)
108
+ ```
109
+
110
+ ### 选项
111
+
112
+ | 选项 | 含义 |
113
+ |---|---|
114
+ | `--provider NAME` | `deepseek`、`opencode`,或配置文件里的服务商 |
115
+ | `-p, --profile NAME` | 配置文件里的设置方案 |
116
+ | `-m, --model NAME` | 服务商的模型 |
117
+ | `--permissions NAME` | `auto`(默认)、`read-only`、`strict`、`full-access` |
118
+ | `-i, --image FILE[,FILE]` | 给第一条提示附上图片(png、jpg、gif、webp、bmp);提示里的 `@pic.png` 或拖入的文件路径效果相同 |
119
+ | `--redact` / `--no-redact` | 开启 / 关闭脱敏(默认关闭) |
120
+ | `--thinking LEVEL` | `off`、`low`、`medium`、`high` |
121
+ | `--max-tokens N` | 每个模型请求的输出上限 |
122
+ | `-C, --cwd DIR` | 起始目录 |
123
+ | `--resume REF` | 续接会话(id、id 前缀、`last`) |
124
+ | `--json` | `exec`:事件以 JSON 行输出(第一行是 `session`,最后一行是 `result`) |
125
+ | `--raw` | `exec`:即使在终端里也原样输出 Markdown |
126
+ | `-q, --quiet` | `exec`:stderr 不输出进度行(警告和错误仍会显示) |
127
+ | `--output-last-message FILE` | `exec`:同时把最后的回复写入 FILE |
128
+ | `--no-color` | 不用颜色 |
129
+
130
+ `exec` 的退出码:0 完成,1 出错,2 用法错误或被审核熔断器暂停,130 已取消。在 `exec` 里,进度输出到 stderr(每个工具调用一行),最后的回复输出到 stdout;在终端里是渲染后的效果,在管道里是原始的 Markdown。
131
+
132
+ ## 在会话里
133
+
134
+ | 按键 | 空闲时 | Agent 工作时 |
135
+ |---|---|---|
136
+ | Enter | 发送 | **引导**:在 Agent 的下一个模型请求之前送达 |
137
+ | Tab | 补全斜杠命令 | **排队**:Agent 要停下时处理 |
138
+ | Esc | 关闭弹出列表 | **取消**这次运行 |
139
+ | Ctrl-C | 清空提示;在空提示上按两次退出 | 取消这次运行 |
140
+ | Ctrl-D | 退出(提示为空时) | |
141
+ | Shift-Enter、Alt-Enter、Ctrl-J,或 `\` 再按 Enter | 提示里换行 | |
142
+ | 上 / 下 | 历史,或在行之间移动 | |
143
+ | Ctrl-A/E/K/U/W/Y、Alt-方向键 | readline 风格的编辑 | |
144
+
145
+ 运行中输入的消息显示在提示上方,标为"引导"或"排队",直到 Agent 接收。
146
+
147
+ ### 斜杠命令
148
+
149
+ | 命令 | |
150
+ |---|---|
151
+ | `/help` | 列出命令 |
152
+ | `/status` | 会话、目录、模型、权限、剩余上下文、输入(缓存的和新的)、缓存命中率、输出 |
153
+ | `/model [provider[/model]]` | 显示或切换服务商和模型(Agent 会关闭并在同一个会话文件上重新创建) |
154
+ | `/permissions [auto\|read-only\|strict\|full-access]` | 显示或修改被审核的范围 |
155
+ | `/redaction [on\|off]` | 向模型隐藏密钥 |
156
+ | `/rename <name>` | 给会话起名 |
157
+ | `/compact` | 总结对话以腾出上下文 |
158
+ | `/new`、`/clear` | 开始新会话(`/clear` 同时清屏) |
159
+ | `/resume [id\|last]` | 续接之前的会话(不带参数时出现选择列表) |
160
+ | `/fork` | 把这个会话复制成新会话,并在新会话里继续 |
161
+ | `/undo` | 回到你上一条消息之前,并把它放回提示里(在空提示上按两次 Esc 效果相同) |
162
+ | `/init` | 让 Agent 写一份 `AGENTS.md` |
163
+ | `/review [focus]` | 让 Agent 审查未提交的改动 |
164
+ | `/diff` | 显示 `git diff` |
165
+ | `/goal [objective\|clear]` | 显示目标;带文字时设定目标并开始工作;`clear` 结束目标 |
166
+ | `/mcp` | 显示 MCP 服务 |
167
+ | `/exit`、`/quit` | 退出 |
168
+
169
+ `!命令` 由你自己运行一条 shell 命令并显示输出(Agent 看不到)。提示里的 `@路径` 向 Agent 指明一个已有的文件或目录。
170
+
171
+ ### 权限预设
172
+
173
+ | 预设 | |
174
+ |---|---|
175
+ | `auto`(默认) | 审核开启 |
176
+ | `read-only` | 审核开启,并且 Agent 只有 `read` 工具 |
177
+ | `strict` | 审核开启;连续三次被拒绝就暂停这次运行 |
178
+ | `full-access` | 审核关闭 |
179
+
180
+ 它们都不限制工作目录:审核插件以 `directoryRules: false` 运行,所以启动目录之外的路径本身不是拒绝的理由。写入、命令展开、凭据和灾难性的模式仍然由硬规则和审核模型检查。
181
+
182
+ ## 配置
183
+
184
+ `~/.sakura/config.toml`(设置 `SAKURA_HOME` 可以改用别的目录;会话保存在它旁边的 `sessions/` 里)。优先级:默认值 < 文件 < 方案 < 环境变量(`SAKURA_PROVIDER`、`SAKURA_MODEL`)< 命令行选项。
185
+
186
+ ```toml
187
+ provider = "opencode"
188
+ permissions = "auto"
189
+ redaction = false
190
+ thinking = "medium"
191
+ max_tokens = 32000
192
+ # 空闲自动压缩:在 cache_ttl_minutes 分钟没有活动之后,至少 auto_compact_min_tokens
193
+ # (不会低于 1000)大小的上下文会被压缩;方案里也可以设置这些
194
+ cache_ttl_minutes = 5
195
+ auto_compact_min_tokens = 20000
196
+ auto_compact_idle = true
197
+
198
+ [profiles.cheap]
199
+ provider = "deepseek"
200
+ thinking = "low"
201
+
202
+ [providers.lab] # 任何 Chat Completions 接口
203
+ base_url = "https://example.com/v1"
204
+ model = "some-model"
205
+ key_env = "LAB_API_KEY"
206
+ context_window = 128000
207
+ compat = "deepseek" # 框架的兼容预设:deepseek、moonshot、openai
208
+ headers = { "x-team" = "a" }
209
+ session_header = "x-session-id" # 如果接口需要,把会话 id 放进这个请求头
210
+
211
+ [mcp.servers.files]
212
+ command = "npx"
213
+ args = ["-y", "some-mcp-server"]
214
+ ```
215
+
216
+ ### 服务商
217
+
218
+ | 名称 | 接口 | 模型 | 密钥 |
219
+ |---|---|---|---|
220
+ | `deepseek` | `https://api.deepseek.com` | `deepseek-flash` | `DEEPSEEK_API_KEY` |
221
+ | `opencode` | `https://opencode.ai/zen/go/v1` | `deepseek-v4.1-flash` | `OPENCODE_API_KEY` |
222
+
223
+ 两者都用框架的 `compatPresets.deepseek`。OpenCode Go 要求每个请求都带会话请求头;终端会发送 `x-opencode-session: <会话 id>`。不用 `-p` 时,使用第一个设置了密钥的服务商。密钥从环境变量读取,从不打印或保存。
224
+
225
+ ## 常见问题
226
+
227
+ **它支持哪些模型?** 任何支持 Chat Completions(流式和工具调用)的接口,都可以在 `~/.sakura/config.toml` 里用 `[providers.名字]` 设置。试用过 DeepSeek 和 OpenCode Go,它们也是内置的。
228
+
229
+ **会话存在哪里,怎么续接?** 在 `~/.sakura/sessions/`(设置 `SAKURA_HOME` 可以改用别的目录),每个会话一个只追加的文件。`sakuracode resume --last` 续接最近的一个;`sakuracode sessions` 列出它们;`/fork` 复制一个。
230
+
231
+ **对话太长了会怎样?** 上下文快满时(以及空闲几分钟之后)会自动总结,也可以输入 `/compact`;最近的部分原样保留。`/status` 显示上下文有多满,以及输入里有多少来自服务商的缓存。
232
+
233
+ **能用在脚本和 CI 里吗?** 能:`sakuracode exec "任务"` 不带界面运行一条提示,回复写到 stdout,进度写到 stderr,退出码是 0(完成)、1(出错)、2(用法错误或被审核暂停)或 130(已取消)。`--json` 把事件输出成 JSON 行。
234
+
235
+ **能让它自己朝一个目标一直做下去吗?** 能:`/goal "让测试通过"` 设定目标并开始工作;Agent 要停下之前会对照目标检查。
236
+
237
+ **它和 Codex CLI 有什么不同?** Codex CLI 是 OpenAI 的产品,有沙箱模式和批准策略。sakuracode 是开源 Sakura 框架的参考终端:它用自动审核代替沙箱和批准提示,支持任何 Chat Completions 接口,并且只基于公开的 SDK 构建,所以它做的一切,你都可以用 `sakura-agent` 在自己的 Agent 里做到。
238
+
239
+ ## 开发
240
+
241
+ 在仓库根目录运行这些命令:
242
+
243
+ ```bash
244
+ npm ci
245
+ npm --prefix code start
246
+ npm --prefix code test # 包括伪终端测试(需要 python3)
247
+ npm --prefix code run typecheck
248
+ npm pack ./code # 生成发布用的 tarball(npm publish ./code 发布它)
249
+ ```
250
+
251
+ 测试套件不使用网络。要用真实的服务商测试,运行 `node --disable-warning=ExperimentalWarning code/test/live/interactive.ts PROVIDER WORKDIR [HOME]`。
252
+
253
+ 文档(英文):[docs/design.md](docs/design.md)(模块和接口)、[docs/test-report.md](docs/test-report.md)(运行了什么,包括失败的)、[docs/framework-gaps.md](docs/framework-gaps.md)(框架缺什么,以及提出的改动)。
254
+
255
+ ## 还没做
256
+
257
+ 剪贴板图片粘贴、后台命令结束时唤醒 Agent、子 Agent 的定义、`--full-auto` 风格的选项(请用 `--permissions full-access`)、主题,以及 TOML 的表数组配置。其中一些要等框架支持,原因见 `docs/framework-gaps.md`。
package/dist/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025–2026 Vist233
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ # Third-party notices
2
+
3
+ Sakura borrows from a few other projects. Where code or text was copied or adapted, the source file says so in its header, and the licence text is kept in `licenses/`.
4
+
5
+ | Project | Licence | What Sakura took |
6
+ |---|---|---|
7
+ | [Pi](https://github.com/badlogic/pi-mono) (Mario Zechner) | MIT | The edit-matching code in `packages/plugins/tools/src/edit-match.ts`, and the wording of the `read`, `write`, `edit` and `bash` tool descriptions, adapted. The structure and some rules of the system prompt follow Pi's. |
8
+ | [gitleaks](https://github.com/gitleaks/gitleaks) (Zachary Rice) | MIT | The secret-detection rules in `packages/plugins/redaction/src/rules.gen.ts`, converted to TypeScript. |
9
+ | [OpenAI Codex](https://github.com/openai/codex) | Apache-2.0 | Ideas only: some task-execution principles in the system prompt, reworded. No code or text is copied. |
10
+
11
+ Dependency: [`quickjs-emscripten`](https://github.com/justjake/quickjs-emscripten) (MIT) runs the code-mode scripts. It is installed from npm, not copied.
12
+
13
+ Licence texts: `licenses/pi-mono-MIT.txt`, `licenses/gitleaks-MIT.txt`, `licenses/codex-Apache-2.0.txt`.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
+ import { main } from "../src/cli.js";
3
+ const code = await main(process.argv.slice(2), { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr, env: process.env, cwd: process.cwd() });
4
+ // Leave at once: a cancelled request or a paused terminal handle must not keep the process alive after the session has ended.
5
+ process.stdout.write("", () => process.exit(code));
@@ -0,0 +1,41 @@
1
+ // Creating and closing a framework agent for one choice of provider, model, permissions and session file.
2
+ // Switching the provider or the permissions means closing this and opening another on the same session file.
3
+ import { buildGuards } from "./permissions.js";
4
+ import { buildModelConfig } from "./providers.js";
5
+ import { corePlugins, createAgent, createChatClient } from "./framework.js";
6
+ const MARKDOWN_RULE = "Write your replies in Markdown: short paragraphs, lists where they help, and fenced code blocks with a language tag for code and commands.";
7
+ export async function openAgentSession(request) {
8
+ const modelConfig = request.clients ? undefined : buildModelConfig({ settings: request.settings, provider: request.provider, model: request.model, sessionId: request.sessionId, env: request.env });
9
+ const agentClient = request.clients?.agent ?? createClientFor(modelConfig, request.onRetry);
10
+ const reviewClient = request.clients?.reviewer ?? agentClient;
11
+ const guards = buildGuards({ permissions: request.permissions, redaction: request.redaction, reviewLlm: reviewClient });
12
+ const config = {
13
+ model: modelConfig ?? agentClient,
14
+ cwd: request.cwd,
15
+ sessionFile: request.sessionFile,
16
+ plugins: [...corePlugins(), ...guards.plugins],
17
+ rules: [MARKDOWN_RULE],
18
+ ...(guards.tools ? { tools: guards.tools } : {}),
19
+ ...(request.thinking ? { thinking: request.thinking } : {}),
20
+ ...(request.maxTokens ? { maxTokens: request.maxTokens } : {}),
21
+ ...(Object.keys(request.settings.mcpServers).length > 0 ? { mcp: { servers: request.settings.mcpServers } } : {}),
22
+ };
23
+ const agent = await createAgent(config);
24
+ return {
25
+ agent,
26
+ provider: request.provider,
27
+ model: modelConfig?.model ?? agentClient.model.id,
28
+ contextWindow: modelConfig?.contextWindow ?? agentClient.model.contextWindow,
29
+ thinking: request.thinking ?? (modelConfig?.compat?.thinkingFormat && modelConfig.compat.thinkingFormat !== "none" ? "medium" : undefined),
30
+ permissions: request.permissions,
31
+ redaction: request.redaction,
32
+ sessionId: request.sessionId,
33
+ close: () => agent.close(),
34
+ };
35
+ }
36
+ function createClientFor(model, onRetry) {
37
+ const retry = onRetry
38
+ ? { onRetry: ({ attempt, delayMs, error }) => onRetry(`The provider failed (${error.kind}): ${error.message.slice(0, 300)}. Trying again in ${(delayMs / 1000).toFixed(1)} s (retry ${attempt}).`) }
39
+ : undefined;
40
+ return createChatClient({ baseUrl: model.baseUrl, apiKey: model.apiKey, model: model.model, contextWindow: model.contextWindow, ...(model.compat ? { compat: model.compat } : {}), ...(model.headers ? { headers: model.headers } : {}), ...(retry ? { retry } : {}) });
41
+ }