tandem-mode 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +257 -233
  2. package/dist/agent/clipboard.js +37 -37
  3. package/dist/agent/clipboard.js.map +1 -1
  4. package/dist/agent/loop.js +52 -16
  5. package/dist/agent/loop.js.map +1 -1
  6. package/dist/agent/session.js +2 -2
  7. package/dist/agent/session.js.map +1 -1
  8. package/dist/agent/tools/edit.js +1 -1
  9. package/dist/agent/tools/edit.js.map +1 -1
  10. package/dist/agent/tools/listDir.js +49 -6
  11. package/dist/agent/tools/listDir.js.map +1 -1
  12. package/dist/agent/tools/safeCommand.js +70 -0
  13. package/dist/agent/tools/safeCommand.js.map +1 -0
  14. package/dist/agent/tools/search.js +5 -6
  15. package/dist/agent/tools/search.js.map +1 -1
  16. package/dist/agent/tools/searchFallback.js +162 -0
  17. package/dist/agent/tools/searchFallback.js.map +1 -0
  18. package/dist/agent/tools/shell.js +3 -13
  19. package/dist/agent/tools/shell.js.map +1 -1
  20. package/dist/agent/tools/viewImage.js +2 -2
  21. package/dist/agent/tools/viewImage.js.map +1 -1
  22. package/dist/agent/tools/webSearch.js +2 -2
  23. package/dist/agent/tools/webSearch.js.map +1 -1
  24. package/dist/agent/usage.js +2 -2
  25. package/dist/agent/usage.js.map +1 -1
  26. package/dist/benchmark/runner.js +8 -8
  27. package/dist/benchmark/runner.js.map +1 -1
  28. package/dist/cli.js +118 -19
  29. package/dist/cli.js.map +1 -1
  30. package/dist/config/credentials.js +37 -37
  31. package/dist/config/credentials.js.map +1 -1
  32. package/dist/config/env.js +1 -1
  33. package/dist/config/schema.js +1 -0
  34. package/dist/config/schema.js.map +1 -1
  35. package/dist/config/store.js +1 -1
  36. package/dist/config/store.js.map +1 -1
  37. package/dist/deepseek/client.js +7 -2
  38. package/dist/deepseek/client.js.map +1 -1
  39. package/dist/deepseek/errors.js +101 -0
  40. package/dist/deepseek/errors.js.map +1 -0
  41. package/dist/deepseek/webSearch.js +3 -3
  42. package/dist/deepseek/webSearch.js.map +1 -1
  43. package/dist/i18n/en.js +30 -19
  44. package/dist/i18n/en.js.map +1 -1
  45. package/dist/i18n/sr.js +30 -19
  46. package/dist/i18n/sr.js.map +1 -1
  47. package/dist/orchestrator/planner.js +14 -14
  48. package/dist/orchestrator/repoMap.js +1 -1
  49. package/dist/orchestrator/reviewer.js +14 -14
  50. package/dist/orchestrator/reviewer.js.map +1 -1
  51. package/dist/repl/commands.js +1 -0
  52. package/dist/repl/commands.js.map +1 -1
  53. package/dist/ui/status.js +37 -0
  54. package/dist/ui/status.js.map +1 -0
  55. package/dist/ui/toolFormat.js +95 -0
  56. package/dist/ui/toolFormat.js.map +1 -0
  57. package/package.json +1 -1
package/README.md CHANGED
@@ -1,233 +1,257 @@
1
- # Tandem Mode
2
-
3
- [![npm version](https://img.shields.io/npm/v/tandem-mode.svg)](https://www.npmjs.com/package/tandem-mode)
4
- [![license](https://img.shields.io/npm/l/tandem-mode.svg)](./LICENSE)
5
- [![node](https://img.shields.io/node/v/tandem-mode.svg)](./package.json)
6
- [![good first issues](https://img.shields.io/github/issues/zdrave08/Tandem-Mode/good%20first%20issue)](https://github.com/zdrave08/Tandem-Mode/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
7
-
8
- **English** | [Srpski](./README.sr.md)
9
-
10
- A terminal coding agent built specifically for the DeepSeek API — not a generic
11
- LLM harness with DeepSeek bolted on. Interactive REPL, planner/worker/reviewer
12
- orchestration, cache-aware prompt architecture, and first-class support for
13
- Windows, macOS, and Linux.
14
-
15
- > **Independent open-source project. Not affiliated with or endorsed by
16
- > DeepSeek.**
17
-
18
- ## Status
19
-
20
- M0–M4 of the [development plan](./deepseek-cli-development-plan.md) are done.
21
- Published on npm, usable today for real coding tasks against a local repo.
22
-
23
- First benchmark run is in — see [full results](./docs/benchmark-results.md).
24
- Headline: all three configurations (Pro-only, Flash-only, orchestration)
25
- fixed 4/4 injected bugs correctly in a real WooCommerce plugin, with a clean
26
- `php -l` and zero unnecessary file changes. But for this class of task
27
- (single-file, well-scoped fixes), orchestration cost ~9.5× more and took
28
- ~19× longer than Pro-only for the same correctness — the opposite of what
29
- you'd want orchestration for. Read as: orchestration's overhead needs a
30
- bigger, more decomposable task to pay for itself, which this first run didn't
31
- test. Reported as-is, not adjusted to fit expectations.
32
-
33
- ## Features
34
-
35
- - **Agentic loop** — `deepseek-v4-pro`/`deepseek-v4-flash` with tool calling:
36
- `read_file`, `list_dir`, `search`, `edit`, `git_diff`, `shell`, `view_image`,
37
- `web_search`.
38
- - **Two entry points** — interactive REPL for normal work, single-shot mode
39
- (`tandem "task" --yes`) for scripting and CI.
40
- - **Orchestration** (`/plan`) — a planner produces a JSON task list, workers
41
- execute each task with a fresh minimal context, a reviewer checks the git
42
- diff against the original task and either approves or requests corrections
43
- (capped by `max_review_loops`).
44
- - **Cache-aware prompts** — the worker's system prompt + repo map + plan stay
45
- byte-identical across calls in one orchestration run, so DeepSeek's context
46
- cache actually hits. Measured live: cache-hit tokens grew call over call
47
- within a single `/plan` run.
48
- - **Vision** — `view_image` lets the agent look at a screenshot you point it
49
- to; `/paste` grabs an image straight from the clipboard (Windows, macOS,
50
- and Linux via X11/`xclip` or Wayland/`wl-clipboard`).
51
- - **Web search** — isolated calls to DeepSeek's Responses API when the agent
52
- needs something outside the repo (an error message, a library's docs).
53
- - **Session persistence** — every session is a JSONL file; `/resume` continues
54
- the latest (or a named) one, `/fork` branches off without touching the
55
- original.
56
- - **Safety** — destructive actions (file edits, shell commands not on a small
57
- read-only allowlist) always ask for confirmation; `y`/`n`/`a` where `a`
58
- remembers the choice for that tool for the rest of the session. `--yes`
59
- bypasses this for CI but always prints what it auto-approved.
60
- - **Cost control** — usage is read only from the API's own `usage` field,
61
- never estimated. A budget (`/budget`, `--budget`) hard-stops the loop before
62
- the next API call if exceeded, without losing the session. `/usage` also
63
- shows what the session would have cost off-peak, and the REPL warns you at
64
- startup if you're currently in a peak-pricing window.
65
- - **ESC to interrupt** — cancels the in-flight call (and any running shell
66
- command) cleanly, keeping whatever was generated so far.
67
- - **Cross-platform** — the shell tool runs PowerShell natively on Windows and
68
- `sh` on macOS/Linux, file edits preserve the original CRLF/LF line ending
69
- style, paths are handled with `path.resolve` throughout.
70
-
71
- ## Quick start
72
-
73
- ```bash
74
- npm install -g tandem-mode
75
- tandem # interactive REPL
76
- tandem "explain what this repo does" --yes # one-shot
77
- ```
78
-
79
- Or from source (useful for contributing, or to track `main` instead of the
80
- latest npm release):
81
-
82
- ```bash
83
- git clone https://github.com/zdrave08/Tandem-Mode.git
84
- cd Tandem-Mode
85
- pnpm install
86
- pnpm dev # interactive REPL
87
- pnpm dev "explain what this repo does" --yes # one-shot
88
- ```
89
-
90
- On first run you'll be asked for a DeepSeek API key (get one at
91
- [platform.deepseek.com](https://platform.deepseek.com)) and a default
92
- model/reasoning effort. The key is stored in your OS's own secure credential
93
- store — never in a config file or log:
94
-
95
- - **Windows** — DPAPI-encrypted (tied to your OS user account) in
96
- `%LOCALAPPDATA%\TandemMode\credentials.dat`.
97
- - **macOS** — the login Keychain, via the built-in `security` CLI.
98
- - **Linux** — the Secret Service (gnome-keyring, KWallet, or similar), via
99
- `secret-tool` (install `libsecret-tools` if it's missing).
100
-
101
- `DEEPSEEK_API_KEY` works everywhere and always takes priority, for CI use.
102
-
103
- ## Usage
104
-
105
- ```bash
106
- tandem # interactive REPL
107
- tandem "fix the off-by-one in foo.ts"
108
- tandem --plan "add dark mode to the settings page"
109
- tandem "task" --model deepseek-v4-flash --effort low --budget 0.50 --yes
110
- ```
111
-
112
- | Flag | Effect |
113
- |---|---|
114
- | `--resume` / `-r` | Continue the latest session in this directory |
115
- | `--yes` / `-y` | Auto-approve destructive actions (prints what it approved) |
116
- | `--plan` | Run planner/worker/reviewer orchestration instead of a single turn |
117
- | `--model` | `deepseek-v4-pro` or `deepseek-v4-flash` |
118
- | `--effort` | `low`, `high`, or `max` reasoning effort |
119
- | `--budget` | Hard USD cap for the run |
120
- | `--max-review-loops` | Cap on orchestration correction cycles (default 3) |
121
-
122
- ### REPL commands
123
-
124
- | Command | Effect |
125
- |---|---|
126
- | `/plan <task>` | Planner/worker/reviewer orchestration |
127
- | `/model [name]` | Show or switch model |
128
- | `/thinking [on\|off]` | Toggle thinking mode |
129
- | `/effort [low\|high\|max]` | Show or set reasoning effort |
130
- | `/status` | Current session/settings |
131
- | `/diff [--staged] [path]` | Working-tree diff, no API call |
132
- | `/usage` | Tokens, cost, off-peak projection |
133
- | `/budget [amount]` | Show or set the session budget |
134
- | `/new` | Start a fresh session |
135
- | `/save-profile <name>` | Save model, effort, budget and review loops as a named profile |
136
- | `/profile [name]` | Load a saved profile, or list them |
137
- | `/compact` | Summarize the history into a new session to free up context; the old session stays on disk |
138
- | `/fork` | New session, carrying over the current history |
139
- | `/resume [id]` | Continue the latest (or a named) session |
140
- | `/paste [message]` | Send a clipboard image to the agent |
141
- | `/clear` | Clear the screen |
142
- | `/help` | This list |
143
- | `/exit` | Quit |
144
-
145
- Press **ESC** at any time during a turn to interrupt it.
146
-
147
- ## Architecture
148
-
149
- ```
150
- tandem (no args) ─┬─> interactive REPL ─┬─> single turn: agent loop (tools)
151
- │ └─> /plan: planner → worker(s) → reviewer
152
- tandem "task" ──┘ (single-shot, same agent loop, for scripts/CI)
153
- ```
154
-
155
- - `src/deepseek/` — thin client for DeepSeek's OpenAI-compatible API
156
- (streaming + non-streaming chat completions, vision, web search via
157
- Responses API). See [`docs/api-notes.en.md`](./docs/api-notes.en.md)
158
- (Serbian original: [`docs/api-notes.md`](./docs/api-notes.md)) for the
159
- API behavior this is built against, verified live rather than assumed.
160
- - `src/agent/` — the tool-calling loop, tool implementations, session
161
- persistence, usage/cost tracking.
162
- - `src/orchestrator/` — planner/worker/reviewer, repo-map generation, the
163
- cache-aware stable-prefix construction.
164
- - `src/repl/` — slash commands, runtime config, the first-run wizard.
165
- - `src/cli.tsx` — Ink-based terminal UI and the REPL/single-shot entry points.
166
-
167
- ## Configuration
168
-
169
- Layered: **session** (in-REPL commands) > **project** (`.tandem/config.json`
170
- in the current directory) > **global** (`~/.tandem/config.json`).
171
-
172
- ```json
173
- {
174
- "defaultModel": "deepseek-v4-pro",
175
- "defaultReasoningEffort": "high",
176
- "budgetUsd": 5,
177
- "maxReviewLoops": 3,
178
- "verifyCommand": "pnpm typecheck && pnpm test",
179
- "notifyCommand": "curl -s -d \"Tandem: $TANDEM_OUTCOME\" https://ntfy.sh/my-topic",
180
- "language": "en"
181
- }
182
- ```
183
-
184
- `verifyCommand` runs before each `/plan` review round and its result goes to
185
- the reviewer. `language` is the UI language (`en` or `sr`, default `en`); the
186
- `--lang` flag and the `TANDEM_LANG` environment variable override it, in that
187
- order. The first-run wizard asks for it.
188
-
189
- `notifyCommand` runs when a `/plan` run ends: reviewer approved, `max_review_loops`
190
- reached, or an error (not when you interrupt it with ESC). The details come in as
191
- environment variables, never spliced into the command text: `TANDEM_OUTCOME`
192
- (`approved`, `max_review_loops` or `error`), `TANDEM_TASK`, `TANDEM_NOTES` (the
193
- reviewer's notes, or the error message), `TANDEM_REVIEW_LOOPS` and `TANDEM_COST_USD`.
194
- The command runs in your shell (PowerShell on Windows, `sh` elsewhere) with a 15 s
195
- limit, and a failure only prints a warning. Any shell command works, so Slack,
196
- ntfy, a desktop notification or a webhook are all just examples:
197
-
198
- ```json
199
- { "notifyCommand": "curl -s -d \"Tandem: $TANDEM_OUTCOME\" https://ntfy.sh/my-topic" }
200
- ```
201
-
202
- On Windows (PowerShell) the variables are written `$env:TANDEM_OUTCOME`.
203
-
204
- Like `verifyCommand`, `notifyCommand` runs without asking, so only put it in a
205
- project's `.tandem/config.json` if you trust that project.
206
-
207
- **Profiles:** `/save-profile <name>` stores the current model, reasoning effort,
208
- budget and review loops in `~/.tandem/profiles/<name>.json`, and `/profile <name>`
209
- loads them back into the running session (only the fields present in the file are
210
- applied). `/profile` alone lists the saved ones. Names use `a-z`, `0-9`, `-` and `_`.
211
- Profiles never include `verifyCommand` or the API key.
212
-
213
- The API key is never stored in these files — it lives in the OS credential
214
- store (or `DEEPSEEK_API_KEY` env var, which takes priority, for CI use).
215
-
216
- ## Development
217
-
218
- See [CONTRIBUTING.md](./CONTRIBUTING.md).
219
-
220
- ```bash
221
- pnpm install
222
- pnpm typecheck
223
- pnpm dev "some task"
224
- ```
225
-
226
- ## Disclaimer
227
-
228
- This is an independent, community-run project. It is not affiliated with,
229
- endorsed by, or sponsored by DeepSeek.
230
-
231
- ## License
232
-
233
- [MIT](./LICENSE)
1
+ # Tandem Mode
2
+
3
+ [![npm version](https://img.shields.io/npm/v/tandem-mode.svg)](https://www.npmjs.com/package/tandem-mode)
4
+ [![license](https://img.shields.io/npm/l/tandem-mode.svg)](./LICENSE)
5
+ [![node](https://img.shields.io/node/v/tandem-mode.svg)](./package.json)
6
+ [![good first issues](https://img.shields.io/github/issues/zdrave08/Tandem-Mode/good%20first%20issue)](https://github.com/zdrave08/Tandem-Mode/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
7
+
8
+ **English** | [Srpski](./README.sr.md)
9
+
10
+ A terminal coding agent built specifically for the DeepSeek API — not a generic
11
+ LLM harness with DeepSeek bolted on. Interactive REPL, planner/worker/reviewer
12
+ orchestration, cache-aware prompt architecture, and first-class support for
13
+ Windows, macOS, and Linux.
14
+
15
+ > **Independent open-source project. Not affiliated with or endorsed by
16
+ > DeepSeek.**
17
+
18
+ ## Status
19
+
20
+ M0–M4 of the [development plan](./deepseek-cli-development-plan.md) are done.
21
+ Published on npm, usable today for real coding tasks against a local repo.
22
+
23
+ First benchmark run is in — see [full results](./docs/benchmark-results.md).
24
+ Headline: all three configurations (Pro-only, Flash-only, orchestration)
25
+ fixed 4/4 injected bugs correctly in a real WooCommerce plugin, with a clean
26
+ `php -l` and zero unnecessary file changes. But for this class of task
27
+ (single-file, well-scoped fixes), orchestration cost ~9.5× more and took
28
+ ~19× longer than Pro-only for the same correctness — the opposite of what
29
+ you'd want orchestration for. Read as: orchestration's overhead needs a
30
+ bigger, more decomposable task to pay for itself, which this first run didn't
31
+ test. Reported as-is, not adjusted to fit expectations.
32
+
33
+ ## Features
34
+
35
+ - **Agentic loop** — `deepseek-v4-pro`/`deepseek-v4-flash` with tool calling:
36
+ `read_file`, `list_dir`, `search`, `edit`, `git_diff`, `shell`, `view_image`,
37
+ `web_search`. `list_dir` can return a nested tree (`depth` up to 3) in one call,
38
+ and `search` uses ripgrep when it is installed and a built-in search when not,
39
+ so nothing extra has to be installed.
40
+ - **Resilient API calls** — connection drops, timeouts, `429` and `5xx` are
41
+ retried up to 3 times with backoff (the status line shows the retry); other
42
+ errors surface at once, with the underlying cause (e.g. `ECONNRESET`) and a
43
+ note that the session is saved.
44
+ - **Two entry points** — interactive REPL for normal work, single-shot mode
45
+ (`tandem "task" --yes`) for scripting and CI.
46
+ - **Orchestration** (`/plan`) — a planner produces a JSON task list, workers
47
+ execute each task with a fresh minimal context, a reviewer checks the git
48
+ diff against the original task and either approves or requests corrections
49
+ (capped by `max_review_loops`).
50
+ - **Cache-aware prompts** — the worker's system prompt + repo map + plan stay
51
+ byte-identical across calls in one orchestration run, so DeepSeek's context
52
+ cache actually hits. Measured live: cache-hit tokens grew call over call
53
+ within a single `/plan` run.
54
+ - **Vision** — `view_image` lets the agent look at a screenshot you point it
55
+ to; `/paste` grabs an image straight from the clipboard (Windows, macOS,
56
+ and Linux via X11/`xclip` or Wayland/`wl-clipboard`).
57
+ - **Web search** — isolated calls to DeepSeek's Responses API when the agent
58
+ needs something outside the repo (an error message, a library's docs).
59
+ - **Session persistence** — every session is a JSONL file; `/resume` continues
60
+ the latest (or a named) one, `/fork` branches off without touching the
61
+ original.
62
+ - **Safety** — destructive actions (file edits, shell commands not on a small
63
+ read-only allowlist) always ask for confirmation. A shell command skips the
64
+ prompt only if every part of it (`;`, `&&`, `|`) is read-only, and never with a
65
+ redirection, backticks, `$(...)` or a `{ }` block. `y`/`n`/`a` where `a`
66
+ remembers the choice for that tool for the rest of the session. `--yes`
67
+ bypasses this for CI but always prints what it auto-approved.
68
+ - **Cost control** — usage is read only from the API's own `usage` field,
69
+ never estimated. A budget (`/budget`, `--budget`) hard-stops the loop before
70
+ the next API call if exceeded, without losing the session. `/usage` also
71
+ shows what the session would have cost off-peak, and the REPL warns you at
72
+ startup if you're currently in a peak-pricing window.
73
+ - **Status line** — the last line of every turn shows model, mode (chat/plan),
74
+ cost (and budget), and the current call out of the iteration limit; it turns
75
+ yellow, then red, as you approach the budget or the limit.
76
+ - **Readable tool output** — `edit` shows a red/green diff, `read_file`, `search`
77
+ and `list_dir` collapse to a one-line summary (`120 lines read`, `no matches`).
78
+ - **ESC to interrupt** — cancels the in-flight call (and any running shell
79
+ command) cleanly, keeping whatever was generated so far.
80
+ - **Cross-platform** — the shell tool runs PowerShell natively on Windows and
81
+ `sh` on macOS/Linux, file edits preserve the original CRLF/LF line ending
82
+ style, paths are handled with `path.resolve` throughout.
83
+
84
+ ## Quick start
85
+
86
+ ```bash
87
+ npm install -g tandem-mode
88
+ tandem # interactive REPL
89
+ tandem "explain what this repo does" --yes # one-shot
90
+ ```
91
+
92
+ Or from source (useful for contributing, or to track `main` instead of the
93
+ latest npm release):
94
+
95
+ ```bash
96
+ git clone https://github.com/zdrave08/Tandem-Mode.git
97
+ cd Tandem-Mode
98
+ pnpm install
99
+ pnpm dev # interactive REPL
100
+ pnpm dev "explain what this repo does" --yes # one-shot
101
+ ```
102
+
103
+ On first run you'll be asked for a DeepSeek API key (get one at
104
+ [platform.deepseek.com](https://platform.deepseek.com)) and a default
105
+ model/reasoning effort. The key is stored in your OS's own secure credential
106
+ store — never in a config file or log:
107
+
108
+ - **Windows** — DPAPI-encrypted (tied to your OS user account) in
109
+ `%LOCALAPPDATA%\TandemMode\credentials.dat`.
110
+ - **macOS** — the login Keychain, via the built-in `security` CLI.
111
+ - **Linux** — the Secret Service (gnome-keyring, KWallet, or similar), via
112
+ `secret-tool` (install `libsecret-tools` if it's missing).
113
+
114
+ `DEEPSEEK_API_KEY` works everywhere and always takes priority, for CI use.
115
+
116
+ ## Usage
117
+
118
+ ```bash
119
+ tandem # interactive REPL
120
+ tandem "fix the off-by-one in foo.ts"
121
+ tandem --plan "add dark mode to the settings page"
122
+ tandem "task" --model deepseek-v4-flash --effort low --budget 0.50 --yes
123
+ ```
124
+
125
+ | Flag | Effect |
126
+ |---|---|
127
+ | `--resume` / `-r` | Continue the latest session in this directory |
128
+ | `--yes` / `-y` | Auto-approve destructive actions (prints what it approved) |
129
+ | `--plan` | Run planner/worker/reviewer orchestration instead of a single turn |
130
+ | `--model` | `deepseek-v4-pro` or `deepseek-v4-flash` |
131
+ | `--effort` | `low`, `high`, or `max` reasoning effort |
132
+ | `--budget` | Hard USD cap for the run |
133
+ | `--max-review-loops` | Cap on orchestration correction cycles (default 3) |
134
+ | `--max-iterations` | Model calls per chunk of a turn (default 25); at the limit the agent asks whether to continue |
135
+ | `--auto-continue` | Continue at the iteration limit without asking — only together with a budget (`--budget`) |
136
+
137
+ ### REPL commands
138
+
139
+ | Command | Effect |
140
+ |---|---|
141
+ | `/plan <task>` | Planner/worker/reviewer orchestration |
142
+ | `/model [name]` | Show or switch model |
143
+ | `/thinking [on\|off]` | Toggle thinking mode |
144
+ | `/effort [low\|high\|max]` | Show or set reasoning effort |
145
+ | `/status` | Current session/settings |
146
+ | `/diff [--staged] [path]` | Working-tree diff, no API call |
147
+ | `/usage` | Tokens, cost, off-peak projection |
148
+ | `/budget [amount]` | Show or set the session budget |
149
+ | `/new` | Start a fresh session |
150
+ | `/save-profile <name>` | Save model, effort, budget and review loops as a named profile |
151
+ | `/profile [name]` | Load a saved profile, or list them |
152
+ | `/compact` | Summarize the history into a new session to free up context; the old session stays on disk |
153
+ | `/fork` | New session, carrying over the current history |
154
+ | `/resume [id]` | Continue the latest (or a named) session |
155
+ | `/paste [message]` | Send a clipboard image to the agent |
156
+ | `/clear` | Clear the screen |
157
+ | `/help` | This list |
158
+ | `/exit` | Quit |
159
+
160
+ Press **ESC** at any time during a turn to interrupt it.
161
+
162
+ ## Architecture
163
+
164
+ ```
165
+ tandem (no args) ─┬─> interactive REPL ─┬─> single turn: agent loop (tools)
166
+ │ └─> /plan: planner → worker(s) → reviewer
167
+ tandem "task" ──┘ (single-shot, same agent loop, for scripts/CI)
168
+ ```
169
+
170
+ - `src/deepseek/` — thin client for DeepSeek's OpenAI-compatible API
171
+ (streaming + non-streaming chat completions, vision, web search via
172
+ Responses API). See [`docs/api-notes.en.md`](./docs/api-notes.en.md)
173
+ (Serbian original: [`docs/api-notes.md`](./docs/api-notes.md)) for the
174
+ API behavior this is built against, verified live rather than assumed.
175
+ - `src/agent/` — the tool-calling loop, tool implementations, session
176
+ persistence, usage/cost tracking.
177
+ - `src/orchestrator/` — planner/worker/reviewer, repo-map generation, the
178
+ cache-aware stable-prefix construction.
179
+ - `src/repl/` — slash commands, runtime config, the first-run wizard.
180
+ - `src/cli.tsx` — Ink-based terminal UI and the REPL/single-shot entry points.
181
+
182
+ ## Configuration
183
+
184
+ Layered: **session** (in-REPL commands) > **project** (`.tandem/config.json`
185
+ in the current directory) > **global** (`~/.tandem/config.json`).
186
+
187
+ ```json
188
+ {
189
+ "defaultModel": "deepseek-v4-pro",
190
+ "defaultReasoningEffort": "high",
191
+ "budgetUsd": 5,
192
+ "maxReviewLoops": 3,
193
+ "maxIterations": 25,
194
+ "autoContinue": false,
195
+ "verifyCommand": "pnpm typecheck && pnpm test",
196
+ "notifyCommand": "curl -s -d \"Tandem: $TANDEM_OUTCOME\" https://ntfy.sh/my-topic",
197
+ "language": "en"
198
+ }
199
+ ```
200
+
201
+ `maxIterations` is how many model calls one turn may make before Tandem stops and
202
+ asks "continue for N more calls? (y/n/a)"; `a` remembers the answer for the rest of
203
+ the session. With `autoContinue` (or `--auto-continue`) it continues on its own, but
204
+ only when a budget is set, because the budget is the hard stop before the next API
205
+ call. In non-interactive runs (`--yes`) without `--auto-continue` it stops instead of
206
+ waiting for an answer, and the session is saved so you can send "continue".
207
+
208
+ `verifyCommand` runs before each `/plan` review round and its result goes to
209
+ the reviewer. `language` is the UI language (`en` or `sr`, default `en`); the
210
+ `--lang` flag and the `TANDEM_LANG` environment variable override it, in that
211
+ order. The first-run wizard asks for it.
212
+
213
+ `notifyCommand` runs when a `/plan` run ends: reviewer approved, `max_review_loops`
214
+ reached, or an error (not when you interrupt it with ESC). The details come in as
215
+ environment variables, never spliced into the command text: `TANDEM_OUTCOME`
216
+ (`approved`, `max_review_loops` or `error`), `TANDEM_TASK`, `TANDEM_NOTES` (the
217
+ reviewer's notes, or the error message), `TANDEM_REVIEW_LOOPS` and `TANDEM_COST_USD`.
218
+ The command runs in your shell (PowerShell on Windows, `sh` elsewhere) with a 15 s
219
+ limit, and a failure only prints a warning. Any shell command works, so Slack,
220
+ ntfy, a desktop notification or a webhook are all just examples:
221
+
222
+ ```json
223
+ { "notifyCommand": "curl -s -d \"Tandem: $TANDEM_OUTCOME\" https://ntfy.sh/my-topic" }
224
+ ```
225
+
226
+ On Windows (PowerShell) the variables are written `$env:TANDEM_OUTCOME`.
227
+
228
+ Like `verifyCommand`, `notifyCommand` runs without asking, so only put it in a
229
+ project's `.tandem/config.json` if you trust that project.
230
+
231
+ **Profiles:** `/save-profile <name>` stores the current model, reasoning effort,
232
+ budget and review loops in `~/.tandem/profiles/<name>.json`, and `/profile <name>`
233
+ loads them back into the running session (only the fields present in the file are
234
+ applied). `/profile` alone lists the saved ones. Names use `a-z`, `0-9`, `-` and `_`.
235
+ Profiles never include `verifyCommand` or the API key.
236
+
237
+ The API key is never stored in these files — it lives in the OS credential
238
+ store (or `DEEPSEEK_API_KEY` env var, which takes priority, for CI use).
239
+
240
+ ## Development
241
+
242
+ See [CONTRIBUTING.md](./CONTRIBUTING.md).
243
+
244
+ ```bash
245
+ pnpm install
246
+ pnpm typecheck
247
+ pnpm dev "some task"
248
+ ```
249
+
250
+ ## Disclaimer
251
+
252
+ This is an independent, community-run project. It is not affiliated with,
253
+ endorsed by, or sponsored by DeepSeek.
254
+
255
+ ## License
256
+
257
+ [MIT](./LICENSE)
@@ -5,49 +5,49 @@ import { unlink } from "node:fs/promises";
5
5
  const execFileAsync = promisify(execFile);
6
6
  async function saveClipboardImageWindows(destPath) {
7
7
  const escapedPath = destPath.replace(/'/g, "''");
8
- const script = `
9
- Add-Type -AssemblyName System.Windows.Forms
10
- Add-Type -AssemblyName System.Drawing
11
- if ([System.Windows.Forms.Clipboard]::ContainsImage()) {
12
- $img = [System.Windows.Forms.Clipboard]::GetImage()
13
- $img.Save('${escapedPath}', [System.Drawing.Imaging.ImageFormat]::Png)
14
- Write-Output "OK"
15
- } else {
16
- Write-Output "NO_IMAGE"
8
+ const script = `
9
+ Add-Type -AssemblyName System.Windows.Forms
10
+ Add-Type -AssemblyName System.Drawing
11
+ if ([System.Windows.Forms.Clipboard]::ContainsImage()) {
12
+ $img = [System.Windows.Forms.Clipboard]::GetImage()
13
+ $img.Save('${escapedPath}', [System.Drawing.Imaging.ImageFormat]::Png)
14
+ Write-Output "OK"
15
+ } else {
16
+ Write-Output "NO_IMAGE"
17
17
  }`;
18
18
  const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script]);
19
19
  return stdout.trim() === "OK";
20
20
  }
21
21
  /**
22
- * Ne postoji ugrađen Node/OS API za sliku sa clipboard-a na macOS-u, ali
23
- * AppleScript ume da upiše sirove podatke sa clipboard-a direktno u fajl —
24
- * bez trećih alata (pngpaste i sl.). Aplikacije obično stave PNG na
25
- * clipboard; screenshot alati i neki editori stave TIFF, pa se u tom
26
- * slučaju konvertuje u PNG preko `sips` (takođe ugrađen u macOS).
22
+ * There is no built-in Node/OS API for reading an image from the clipboard on macOS, but
23
+ * AppleScript can write the raw clipboard data straight to a file, so no third-party
24
+ * tool (pngpaste and the like) is needed. Apps usually put PNG on the clipboard;
25
+ * screenshot tools and some editors put TIFF, which is then converted to PNG with
26
+ * `sips` (also built into macOS).
27
27
  */
28
28
  async function saveClipboardImageMac(destPath) {
29
29
  const escapedDest = destPath.replace(/"/g, '\\"');
30
30
  const tmpTiff = `${destPath}.tmp.tiff`;
31
31
  const escapedTmpTiff = tmpTiff.replace(/"/g, '\\"');
32
- const script = `
33
- try
34
- set imgData to the clipboard as «class PNGf»
35
- set fileRef to open for access (POSIX file "${escapedDest}") with write permission
36
- set eof of fileRef to 0
37
- write imgData to fileRef
38
- close access fileRef
39
- return "OK"
40
- on error
41
- try
42
- set imgData to the clipboard as «class TIFF»
43
- set fileRef to open for access (POSIX file "${escapedTmpTiff}") with write permission
44
- set eof of fileRef to 0
45
- write imgData to fileRef
46
- close access fileRef
47
- return "TIFF"
48
- on error
49
- return "NO_IMAGE"
50
- end try
32
+ const script = `
33
+ try
34
+ set imgData to the clipboard as «class PNGf»
35
+ set fileRef to open for access (POSIX file "${escapedDest}") with write permission
36
+ set eof of fileRef to 0
37
+ write imgData to fileRef
38
+ close access fileRef
39
+ return "OK"
40
+ on error
41
+ try
42
+ set imgData to the clipboard as «class TIFF»
43
+ set fileRef to open for access (POSIX file "${escapedTmpTiff}") with write permission
44
+ set eof of fileRef to 0
45
+ write imgData to fileRef
46
+ close access fileRef
47
+ return "TIFF"
48
+ on error
49
+ return "NO_IMAGE"
50
+ end try
51
51
  end try`;
52
52
  const { stdout } = await execFileAsync("osascript", ["-e", script]);
53
53
  const result = stdout.trim();
@@ -59,7 +59,7 @@ end try`;
59
59
  }
60
60
  return true;
61
61
  }
62
- /** Čita binarni stdout komande; razlikuje "komanda ne postoji" od "nema šta da vrati". */
62
+ /** Reads a command's binary stdout; tells "command does not exist" apart from "nothing to return". */
63
63
  function readBinaryStdout(cmd, args) {
64
64
  return new Promise((resolve) => {
65
65
  const child = spawn(cmd, args);
@@ -77,9 +77,9 @@ function readBinaryStdout(cmd, args) {
77
77
  });
78
78
  }
79
79
  /**
80
- * Linux nema jedinstven clipboard API — bira se alat prema session tipu:
81
- * `wl-paste` (wl-clipboard) na Wayland-u, `xclip` na X11. Nijedan nije
82
- * garantovano instaliran; jasna greška upućuje korisnika koji paket da doda.
80
+ * Linux has no single clipboard API, so the tool is chosen by session type:
81
+ * `wl-paste` (wl-clipboard) on Wayland, `xclip` on X11. Neither is guaranteed to be
82
+ * installed; a clear error tells the user which package to add.
83
83
  */
84
84
  async function saveClipboardImageLinux(destPath) {
85
85
  const isWayland = Boolean(process.env["WAYLAND_DISPLAY"]);