shadok-ai 0.1.148 → 0.1.149
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.
- package/README.md +147 -90
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
A **web cockpit that drives multiple real Claude Code sessions in parallel** —
|
|
4
4
|
each channel is one `claude` process, on your Claude subscription (not the API).
|
|
5
5
|
Pilot them from a browser **and** from **Telegram** (one topic = one agent),
|
|
6
|
-
with git-worktree isolation,
|
|
6
|
+
with git-worktree isolation, agent profiles, a secret vault, a diff panel, and
|
|
7
|
+
quota gauges.
|
|
7
8
|
|
|
8
9
|
## Quick start
|
|
9
10
|
|
|
@@ -15,25 +16,83 @@ Then open **http://localhost:3789**.
|
|
|
15
16
|
|
|
16
17
|
**Prerequisites:** Node ≥ 20 and the [`claude`](https://claude.com/claude-code)
|
|
17
18
|
CLI installed and signed in on the machine (shadok-ai drives your existing
|
|
18
|
-
Claude Code, on your subscription).
|
|
19
|
+
Claude Code, on your subscription). `tmux` is optional but recommended — with
|
|
20
|
+
it, agents survive the server restarting.
|
|
19
21
|
|
|
20
|
-
On the first run it asks once for an optional **Telegram bot token** (press
|
|
21
|
-
to skip; you can add it later
|
|
22
|
-
|
|
22
|
+
On the first run it asks once for an optional **Telegram bot token** (press
|
|
23
|
+
Enter to skip; you can add it later from the web UI).
|
|
24
|
+
|
|
25
|
+
| Flag | Effect |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `--port, -p <n>` | HTTP/WS port (default 3789; falls back to the next free one) |
|
|
28
|
+
| `--no-telegram` | web-only; don't prompt for or use a bot token |
|
|
29
|
+
| `--password <p>` | require this password to open the GUI (stored in config) |
|
|
30
|
+
| `--version, -v` · `--help, -h` | version / help |
|
|
31
|
+
|
|
32
|
+
## What you get
|
|
33
|
+
|
|
34
|
+
- **Channels** — one `claude` process each, running in parallel. Renaming,
|
|
35
|
+
grouping, and closing all sync live between the browser and Telegram: it's
|
|
36
|
+
one list, server-owned, not two copies.
|
|
37
|
+
- **Worktree isolation** — spawned agents get their own git worktree and branch
|
|
38
|
+
by default, so parallel agents never collide. Work is never auto-discarded:
|
|
39
|
+
an empty worktree is reclaimed on close, anything with changes or commits
|
|
40
|
+
stays (and `Recover` reopens it).
|
|
41
|
+
- **Interactive dialogs** — the TUI's permission prompts and multiple-choice
|
|
42
|
+
questions become clickable buttons in the chat, and an inline keyboard in
|
|
43
|
+
Telegram.
|
|
44
|
+
- **Agent profiles** — a named bundle of role prompt, permission guardrails
|
|
45
|
+
(e.g. forbid `git commit`), model, and which secrets to inject. Applied at
|
|
46
|
+
spawn, remembered across resume.
|
|
47
|
+
- **Secret vault** — stored under `~/.shadok-ai`, never in your repo, injected
|
|
48
|
+
as env vars into the agents that need them.
|
|
49
|
+
- **Quota gauges + pace guard** — 5h and 7d subscription usage, with an
|
|
50
|
+
optional block when you're burning faster than the window elapses (any
|
|
51
|
+
message can force through).
|
|
52
|
+
- **Engine room** — the raw TUI screen, live, with clickable keys, for anything
|
|
53
|
+
the chat can't express.
|
|
54
|
+
- **Diff panel** — what an agent actually changed, against its base.
|
|
55
|
+
- **Self-update** — polls npm and can update and reload itself in place.
|
|
23
56
|
|
|
24
57
|
### Telegram (optional)
|
|
25
58
|
|
|
26
59
|
Add your bot to a group with **Topics** enabled, make it an admin with *Manage
|
|
27
|
-
topics* (and *Delete messages*
|
|
60
|
+
topics* (and *Delete messages* so `/secret` can scrub values), then in the group:
|
|
28
61
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
62
|
+
| Command | |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `/setup` | bind this group as the board (one group per instance) |
|
|
65
|
+
| `/spawn [profile] <name>` | new isolated agent in its own topic (also a web tab) |
|
|
66
|
+
| `/stop` (alias `/esc`) | interrupt the current turn — does **not** end the session |
|
|
67
|
+
| `/new` · `/end` | reset · kill the session |
|
|
68
|
+
| `/restart` | respawn the agent in place (e.g. to pick up new secrets) |
|
|
69
|
+
| `/profiles` · `/list` | list profiles · list bindings |
|
|
70
|
+
| `/secrets` · `/secret KEY value` · `/unsecret KEY` | the secret vault |
|
|
71
|
+
| `/update` | fetch `@latest` and respawn |
|
|
72
|
+
|
|
73
|
+
Creating a topic by hand also spawns an agent. Anything that isn't a command is
|
|
74
|
+
sent to that topic's agent as a prompt — including photos and files, which are
|
|
75
|
+
downloaded and handed to Claude Code.
|
|
76
|
+
|
|
77
|
+
### Configuration
|
|
78
|
+
|
|
79
|
+
Config lives in `~/.shadok-ai/config.json` (mode 600) and is **authoritative
|
|
80
|
+
over the environment once set** from the GUI. The Telegram token, allowed
|
|
81
|
+
chats, and the bridge on/off switch are **per launch directory** — running the
|
|
82
|
+
server from another repo gives you a different cockpit and a different bot.
|
|
83
|
+
|
|
84
|
+
| Env var | |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `PORT` | HTTP/WS port |
|
|
87
|
+
| `SHADOK_GUI_PASSWORD` | require a password for the GUI |
|
|
88
|
+
| `SHADOK_TMUX=0` | force the node-pty transport instead of tmux |
|
|
89
|
+
| `SHADOK_IDLE_MIN` | minutes with no client before a session is reclaimed (60) |
|
|
90
|
+
| `SHADOK_PERMISSION_MODE` | mode new agents start in (default `acceptEdits`) |
|
|
91
|
+
| `SHADOK_AUTOUPDATE` | fallback only — the GUI setting wins once used |
|
|
92
|
+
| `SHADOK_PILOT_PROMPT=0` | don't inject the cockpit system prompt |
|
|
93
|
+
| `SHADOK_RESUME_SUMMARY=1` | don't auto-answer the resume-from-summary prompt |
|
|
94
|
+
| `TELEGRAM_BOT_TOKEN` · `TELEGRAM_ALLOWED_CHATS` | override the stored config |
|
|
95
|
+
| `CLAUDE_CODE_OAUTH_TOKEN` | only for the usage gauges; `claude` itself uses the keychain |
|
|
37
96
|
|
|
38
97
|
---
|
|
39
98
|
|
|
@@ -42,7 +101,8 @@ both ways, live.
|
|
|
42
101
|
|
|
43
102
|
## How it works
|
|
44
103
|
|
|
45
|
-
Drives the **Claude Code TUI** (the interactive `claude` CLI) through a
|
|
104
|
+
Drives the **Claude Code TUI** (the interactive `claude` CLI) through a real
|
|
105
|
+
terminal, and reads the answers from Claude Code's own transcript.
|
|
46
106
|
|
|
47
107
|
> ⚠️ The Claude Code TUI is not a stable API: a CLI update can break the
|
|
48
108
|
> detection heuristics (`❯`, `⏺`, `esc to interrupt` markers).
|
|
@@ -50,31 +110,50 @@ Drives the **Claude Code TUI** (the interactive `claude` CLI) through a pseudo-t
|
|
|
50
110
|
> [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk)
|
|
51
111
|
> or `claude -p --output-format stream-json`.
|
|
52
112
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
PTY
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
113
|
+
Two transports, same interface:
|
|
114
|
+
|
|
115
|
+
- **tmux (default when installed)** — `claude` runs in a detached tmux session,
|
|
116
|
+
so it survives the server restarting or crashing and is reattached on the
|
|
117
|
+
next start. tmux answers terminal queries itself, which removes a whole class
|
|
118
|
+
of PTY hacks. It does not survive a machine reboot.
|
|
119
|
+
- **node-pty** — `claude` runs as a child of the server inside a pseudo-terminal
|
|
120
|
+
(the TUI believes it talks to a human), with **@xterm/headless** replaying the
|
|
121
|
+
ANSI stream into a virtual screen we can read. Terminal query responses
|
|
122
|
+
(cursor position `\x1b[?6n`, identification `\x1b[c`…) must be forwarded back
|
|
123
|
+
to the PTY, or the TUI ignores every keystroke. Dies with the server.
|
|
124
|
+
|
|
125
|
+
In both cases prompts are sent via **bracketed paste** with retries (the TUI
|
|
126
|
+
flushes stdin received during initialization), and "Claude is done" is detected
|
|
127
|
+
heuristically: no `esc to interrupt` marker + screen stable for N ms.
|
|
128
|
+
|
|
129
|
+
**Responses are not scraped from the screen.** Claude Code writes every turn to
|
|
130
|
+
a `.jsonl` transcript; that file is the source of truth for chat content, which
|
|
131
|
+
is what makes long answers reliable. The screen is used only for control —
|
|
132
|
+
submitting, detecting the end of a turn, reading dialogs, and mirroring the raw
|
|
133
|
+
TUI in the engine room.
|
|
63
134
|
|
|
64
|
-
##
|
|
135
|
+
## Build & test
|
|
65
136
|
|
|
66
137
|
```bash
|
|
67
138
|
npm install
|
|
68
|
-
npm run build
|
|
139
|
+
npm run build # tsc → dist/
|
|
140
|
+
npm test
|
|
141
|
+
npm run web # run the server directly (no supervisor, no auto-update)
|
|
69
142
|
```
|
|
70
143
|
|
|
71
144
|
The `postinstall` script fixes the executable bit of node-pty's
|
|
72
145
|
`spawn-helper` (a known npm prebuilds bug on macOS).
|
|
73
146
|
|
|
74
|
-
|
|
147
|
+
Note that `npx shadok-ai` does **not** run your working tree: it runs a
|
|
148
|
+
supervisor that manages an auto-updating npm-installed copy. To test a local
|
|
149
|
+
build, see "Running YOUR build" in [`CLAUDE.md`](CLAUDE.md).
|
|
150
|
+
|
|
151
|
+
## One-shot CLI
|
|
152
|
+
|
|
153
|
+
Separate from the cockpit: run a single prompt and print the answer.
|
|
75
154
|
|
|
76
155
|
```bash
|
|
77
|
-
|
|
156
|
+
shadok-ai-run [options] "<prompt>" # or: node dist/cli.js …
|
|
78
157
|
|
|
79
158
|
--cwd <dir> working directory of the claude session
|
|
80
159
|
--continue, -c resume the latest session of this directory
|
|
@@ -84,94 +163,67 @@ node dist/cli.js [options] "<prompt>"
|
|
|
84
163
|
--timeout <sec> max wait for the response (default 600)
|
|
85
164
|
```
|
|
86
165
|
|
|
87
|
-
Without `--continue`/`--resume`, every run starts a **new** session.
|
|
88
|
-
|
|
166
|
+
Without `--continue`/`--resume`, every run starts a **new** session. The
|
|
167
|
+
session id is printed at the end of the run (found in
|
|
89
168
|
`~/.claude/projects/<encoded cwd>/`, like any Claude Code session):
|
|
90
169
|
|
|
91
170
|
```bash
|
|
92
|
-
|
|
171
|
+
shadok-ai-run --cwd ~/my-project "Explain this project's structure"
|
|
93
172
|
# ▶ session: 5fe046dd-…
|
|
94
173
|
|
|
95
|
-
|
|
96
|
-
|
|
174
|
+
shadok-ai-run --cwd ~/my-project --resume 5fe046dd-… "Now refactor it"
|
|
175
|
+
shadok-ai-run --cwd ~/my-project -c "Continue on the latest session"
|
|
97
176
|
```
|
|
98
177
|
|
|
99
|
-
##
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
npm run web # http://localhost:3789 (PORT=… to change)
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
A chat in the browser driving persistent TUI sessions, in parallel through
|
|
106
|
-
channels:
|
|
107
|
-
|
|
108
|
-
- **channels** (left column): "+ new channel" opens a new independent
|
|
109
|
-
claude session (one WebSocket connection each). Each channel's LED shows
|
|
110
|
-
its state (green: ready, amber: responding, red: ended); the × closes the
|
|
111
|
-
channel and detaches from its session; double-click a name to rename it
|
|
112
|
-
(the name is remembered by session id and reapplied on resume). The
|
|
113
|
-
header, composer and Terminal view follow the active channel. Channels
|
|
114
|
-
are remembered (localStorage): on the next page load each one is restored
|
|
115
|
-
automatically — session resumed via `--resume` and history replayed.
|
|
116
|
-
Closing a channel (×) removes it from the remembered list;
|
|
117
|
-
- **interactive dialogs**: when the TUI shows a choice (multiple-choice
|
|
118
|
-
question, permission prompt…), the options appear as clickable buttons in
|
|
119
|
-
the chat. Single select: one click validates. Multi-select (`[ ]`
|
|
120
|
-
checkboxes in the TUI): clicks toggle (the displayed state is re-read
|
|
121
|
-
from the TUI screen), then "Submit selection" confirms (Tab → Submit page
|
|
122
|
-
→ Enter). The composer is locked while a choice is pending. The
|
|
123
|
-
"Type something" option becomes a free-form text input inside the dialog
|
|
124
|
-
bubble;
|
|
125
|
-
- **history on resume**: when resuming a session (`--continue` or
|
|
126
|
-
`--resume`), the transcript is re-read from the session's `.jsonl` and
|
|
127
|
-
replayed in the chat (last 100 turns, dimmed). The markdown of the
|
|
128
|
-
responses is rendered as HTML (tables, code, lists) via `marked`, served
|
|
129
|
-
locally;
|
|
130
|
-
- **"engine room"** (Terminal button): the raw TUI screen live, with
|
|
131
|
-
clickable keys (Esc, Enter, arrows, 1/2/3, y/n…) to answer permission
|
|
132
|
-
dialogs, and "Resync chat" to resynchronize the chat after a manual
|
|
133
|
-
intervention.
|
|
134
|
-
|
|
135
|
-
### WebSocket protocol (to replace the interface)
|
|
178
|
+
## WebSocket protocol (to replace the interface)
|
|
136
179
|
|
|
137
180
|
The interface is a plain static page: all the intelligence lives server-side.
|
|
138
|
-
Any client (another front-end, a
|
|
139
|
-
|
|
181
|
+
Any client (another front-end, a bot, a script…) can replace it by speaking
|
|
182
|
+
this JSON protocol on `ws://…/ws`. The built-in Telegram bridge *is* such a
|
|
183
|
+
client — it connects to the same server over loopback.
|
|
140
184
|
|
|
141
|
-
**Shared sessions**: the server keeps a single claude process per session
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
client
|
|
147
|
-
for everyone).
|
|
185
|
+
**Shared sessions**: the server keeps a single claude process per session id.
|
|
186
|
+
If several clients (tabs, browsers, interfaces) `start` the same id, they
|
|
187
|
+
attach to the same process and all receive the same events — other clients'
|
|
188
|
+
prompts (`prompt-echo`), answers, dialogs, screen. Closing a connection
|
|
189
|
+
detaches the client; the session is reclaimed after `SHADOK_IDLE_MIN` with no
|
|
190
|
+
client, or immediately on an explicit `stop` (which ends it for everyone).
|
|
148
191
|
|
|
149
192
|
**client → server**
|
|
150
193
|
|
|
151
194
|
| Message | Purpose |
|
|
152
195
|
|---|---|
|
|
153
|
-
| `{type:"start", cwd?, resume?, continue?}` | starts or attaches to the session (once per connection) |
|
|
154
|
-
| `{type:"prompt", text}` | sends a prompt |
|
|
196
|
+
| `{type:"start", cwd?, resume?, continue?, worktree?, branch?, repo?, profile?}` | starts or attaches to the session (once per connection) |
|
|
197
|
+
| `{type:"prompt", text, force?}` | sends a prompt (`force` bypasses the pace guard) |
|
|
155
198
|
| `{type:"choose", n}` | single-select dialog: picks and validates option n |
|
|
156
|
-
| `{type:"toggle", n}` | multi-select
|
|
157
|
-
| `{type:"confirm"}` | multi-select dialog: submits the selection |
|
|
199
|
+
| `{type:"toggle", n}` / `{type:"confirm"}` | multi-select: toggles option n / submits |
|
|
158
200
|
| `{type:"freetext", n, text}` | "Type something" option: sends a free-form answer |
|
|
159
201
|
| `{type:"key", key}` | raw keystroke (`enter`, `escape`, `up`, `down`, `tab`, `ctrl-c`, or a single character) |
|
|
160
202
|
| `{type:"settle"}` | after a manual intervention: waits for the turn to finish |
|
|
161
|
-
| `{type:"
|
|
203
|
+
| `{type:"restart"}` | respawns the agent in place (picks up new secrets/profile) |
|
|
204
|
+
| `{type:"stop", sessionId?}` | ends the session for all clients; `sessionId` targets another channel (zombie cleanup) |
|
|
162
205
|
|
|
163
206
|
**server → client**
|
|
164
207
|
|
|
165
208
|
| Message | Purpose |
|
|
166
209
|
|---|---|
|
|
167
210
|
| `{type:"ready", sessionId, cwd}` | session started (or attached) |
|
|
168
|
-
| `{type:"working"}` | turn
|
|
211
|
+
| `{type:"working"}` / `{type:"turn-done", sessionId}` | turn started / finished |
|
|
212
|
+
| `{type:"stream-text", text}` | a complete assistant text block, from the transcript |
|
|
213
|
+
| `{type:"stream-tool", id, name, summary}` / `{type:"stream-result", …}` | tool call / tool result |
|
|
214
|
+
| `{type:"tokens", tokens}` / `{type:"context", pct}` | token usage / context fill |
|
|
169
215
|
| `{type:"prompt-echo", text}` | prompt sent by another client of the session |
|
|
170
|
-
| `{type:"answer", text, sessionId}` | response extracted from the transcript |
|
|
171
216
|
| `{type:"dialog", question, options:[{n,label,hint,checked?}], multi}` | choice pending |
|
|
172
|
-
| `{type:"history", turns:[
|
|
217
|
+
| `{type:"history", turns:[…]}` | transcript replayed when resuming/attaching |
|
|
173
218
|
| `{type:"screen", text, working}` | rendered TUI screen (whenever it changes) |
|
|
174
|
-
| `{type:"
|
|
219
|
+
| `{type:"pace-blocked"}` / `{type:"pace-hold"}` / `{type:"pace-resumed"}` | quota guardrail |
|
|
220
|
+
| `{type:"auto-retry"}` / `-cancelled` / `-gave-up` | transient API error being retried |
|
|
221
|
+
| `{type:"version", …}` / `{type:"server-reload", version}` | update available / server updated, reload |
|
|
222
|
+
| `{type:"gone"}` / `{type:"error", message}` / `{type:"exited", code}` / `{type:"stopped"}` | session lost, errors, termination |
|
|
223
|
+
|
|
224
|
+
HTTP endpoints (same auth): `/usage`, `/live`, `/sessions`, `/recover`,
|
|
225
|
+
`/diff`, `/channels`, `/groups`, `/profiles`, `/secrets`, `/telegram`,
|
|
226
|
+
`/defaults`, `/version`, `/autoupdate`, `/permission-mode`.
|
|
175
227
|
|
|
176
228
|
## Library
|
|
177
229
|
|
|
@@ -205,13 +257,18 @@ Main API:
|
|
|
205
257
|
| `isWorking()` | true while Claude is working |
|
|
206
258
|
| `onData(cb)` / `onExit(cb)` | raw ANSI stream (mirror mode) / process exit |
|
|
207
259
|
|
|
208
|
-
`
|
|
260
|
+
`TmuxPilot` exposes the same surface and additionally survives a server
|
|
261
|
+
restart. `examples/two-turns.mjs` shows a two-turn conversation;
|
|
209
262
|
`debug/probe.mjs` logs the terminal sequences exchanged (useful when a CLI
|
|
210
263
|
update breaks the detection).
|
|
211
264
|
|
|
212
265
|
## Known limits
|
|
213
266
|
|
|
214
267
|
- Every session launched consumes your Claude quota like a normal session.
|
|
268
|
+
- Profile guardrails are **soft**: agents run as the same OS user. It prevents
|
|
269
|
+
misfires, it is not a sandbox.
|
|
270
|
+
- Agent worktrees are branched at spawn and never rebased, so a long-running
|
|
271
|
+
agent drifts from a moving main branch.
|
|
215
272
|
- Dialogs the chat cannot handle in one click are managed through the
|
|
216
273
|
engine room (`waitFor()` + `press()` in library mode).
|
|
217
274
|
- The TUI runs in the alternate screen: `fullBuffer()` ≈ visible screen;
|