shadok-ai 0.1.201 → 0.1.202

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 (2) hide show
  1. package/README.md +71 -10
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -3,8 +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, agent profiles, a secret vault, a diff panel, and
7
- quota gauges.
6
+ with git-worktree isolation, agent profiles, a secret vault, a diff panel,
7
+ scheduled prompts, and quota gauges.
8
8
 
9
9
  ## Quick start
10
10
 
@@ -46,11 +46,22 @@ Enter to skip; you can add it later from the web UI).
46
46
  spawn, remembered across resume.
47
47
  - **Secret vault** — stored under `~/.shadok-ai`, never in your repo, injected
48
48
  as env vars into the agents that need them.
49
+ - **Scheduled prompts** — give a channel a recurring prompt (every N minutes, or
50
+ daily at HH:MM in a time zone you choose) for monitoring and reporting. Each
51
+ schedule can carry a **deterministic guard command** that runs *without the
52
+ model*: prints nothing → nothing to report, the agent is never woken and the
53
+ run costs **zero tokens**; prints something → that output is prepended to the
54
+ prompt and the agent runs. A watcher that is quiet most of the day costs
55
+ nothing most of the day.
49
56
  - **Quota gauges + pace guard** — 5h and 7d subscription usage, with an
50
57
  optional block when you're burning faster than the window elapses (any
51
58
  message can force through).
59
+ - **Notifications** — favicon, title badge and an optional sound when an agent
60
+ needs you. It only blinks when the tab is hidden *and* an unmuted channel is
61
+ actually waiting.
52
62
  - **Engine room** — the raw TUI screen, live, with clickable keys, for anything
53
- the chat can't express.
63
+ the chat can't express. With tmux there is also an **experimental real
64
+ terminal** (xterm.js over the pane's byte stream) when a snapshot isn't enough.
54
65
  - **Diff panel** — what an agent actually changed, against its base.
55
66
  - **Self-update** — polls npm and can update and reload itself in place.
56
67
 
@@ -67,6 +78,8 @@ topics* (and *Delete messages* so `/secret` can scrub values), then in the group
67
78
  | `/new` · `/end` | reset · kill the session |
68
79
  | `/restart` | respawn the agent in place (e.g. to pick up new secrets) |
69
80
  | `/profiles` · `/list` | list profiles · list bindings |
81
+ | `/cron every 30m <prompt>` · `/cron daily 09:00 <prompt>` | schedule a recurring prompt on this agent |
82
+ | `/cron list` · `/cron on\|off\|del <id>` | manage them (`<id>` accepts the printed 8-char prefix) |
70
83
  | `/secrets` · `/secret KEY value` · `/unsecret KEY` | the secret vault |
71
84
  | `/update` | fetch `@latest` and respawn |
72
85
 
@@ -74,6 +87,12 @@ Creating a topic by hand also spawns an agent. Anything that isn't a command is
74
87
  sent to that topic's agent as a prompt — including photos and files, which are
75
88
  downloaded and handed to Claude Code.
76
89
 
90
+ **Direct messages belong to one person.** The first user to DM the bot claims it;
91
+ everyone else is refused. On startup the owner is adopted from an existing DM or
92
+ from the board group's creator, so an instance that already has an owner never
93
+ hands itself to whoever messages next — a bot username is public, and a DM is a
94
+ shell.
95
+
77
96
  ### Exposing it beyond this machine
78
97
 
79
98
  The cockpit runs arbitrary commands on the host by design, so it binds
@@ -99,7 +118,12 @@ origin in `SHADOK_ORIGINS`.
99
118
  Config lives in `~/.shadok-ai/config.json` (mode 600) and is **authoritative
100
119
  over the environment once set** from the GUI. The Telegram token, allowed
101
120
  chats, and the bridge on/off switch are **per launch directory** — running the
102
- server from another repo gives you a different cockpit and a different bot.
121
+ server from another repo gives you a different cockpit and a different bot. So
122
+ are the channel list and the scheduled prompts.
123
+
124
+ `timezone` (an IANA name like `Europe/Paris`, settable via `/timezone`) is the
125
+ default zone for reading a `daily` schedule. Without it the hour follows the
126
+ machine, which silently shifts every daily prompt on a server running in UTC.
103
127
 
104
128
  | Env var | |
105
129
  |---|---|
@@ -121,6 +145,34 @@ server from another repo gives you a different cockpit and a different bot.
121
145
  > **Hacking on shadok-ai?** Read [`CLAUDE.md`](CLAUDE.md) (map, build/run,
122
146
  > invariants) and [`docs/architecture.md`](docs/architecture.md) first.
123
147
 
148
+ ### Keeping the docs honest
149
+
150
+ Three documents, three jobs — and **they ship with the change that makes them
151
+ wrong**, not in a catch-up pass afterwards:
152
+
153
+ | Document | Holds | Update it when |
154
+ |---|---|---|
155
+ | `README.md` | what shadok-ai does and how to drive it | a user-visible feature, flag, command, endpoint or protocol message changes |
156
+ | `CLAUDE.md` | the file→responsibility map, the build/run recipe, the invariants | you add a module, or you lose an afternoon to something the next person would lose it to as well |
157
+ | `docs/architecture.md` | how a subsystem actually works and why it was built that way | you add or reshape a subsystem, or you make a design trade-off worth remembering |
158
+ | `docs/superpowers/specs/` | one design per feature, dated | before building anything non-trivial |
159
+
160
+ This is not bookkeeping. A doc that lags is worse than no doc: it is confidently
161
+ wrong, and it is read as current. `docs/architecture.md` once went **48 commits**
162
+ without an update, and by then it was missing entire subsystems while its line
163
+ references pointed at code that had moved hundreds of lines — a reader in that
164
+ window gets misled, not merely under-informed.
165
+
166
+ Two habits that keep it cheap:
167
+
168
+ - **Cite symbols, not just line numbers.** `finishTurn` survives a refactor;
169
+ `server.ts:781` does not. Where a line number helps, say which commit it was
170
+ read at.
171
+ - **Write the *why*, not the *what*.** The what is in the diff. What the diff
172
+ cannot say is which alternative you rejected and what it cost you to find out —
173
+ that is the whole value of `architecture.md`, and the reason the invariants
174
+ list reads like a scar tissue map.
175
+
124
176
  ## How it works
125
177
 
126
178
  Drives the **Claude Code TUI** (the interactive `claude` CLI) through a real
@@ -215,7 +267,7 @@ client, or immediately on an explicit `stop` (which ends it for everyone).
215
267
 
216
268
  | Message | Purpose |
217
269
  |---|---|
218
- | `{type:"start", cwd?, resume?, continue?, worktree?, branch?, repo?, profile?}` | starts or attaches to the session (once per connection) |
270
+ | `{type:"start", cwd?, resume?, continue?, worktree?, branch?, repo?, profile?, origin?}` | starts or attaches to the session (once per connection). `origin` (`"web"`, `"cron"`, `"telegram"`, `"cli"`…) travels with `prompt-echo` so other clients can say who spoke |
219
271
  | `{type:"prompt", text, force?}` | sends a prompt (`force` bypasses the pace guard) |
220
272
  | `{type:"choose", n}` | single-select dialog: picks and validates option n |
221
273
  | `{type:"toggle", n}` / `{type:"confirm"}` | multi-select: toggles option n / submits |
@@ -223,6 +275,8 @@ client, or immediately on an explicit `stop` (which ends it for everyone).
223
275
  | `{type:"key", key}` | raw keystroke (`enter`, `escape`, `up`, `down`, `tab`, `ctrl-c`, or a single character) |
224
276
  | `{type:"settle"}` | after a manual intervention: waits for the turn to finish |
225
277
  | `{type:"restart"}` | respawns the agent in place (picks up new secrets/profile) |
278
+ | `{type:"term-attach"}` · `{type:"term-detach"}` | **experimental, tmux only** — open/close the pane's raw byte pipe |
279
+ | `{type:"term-input", data}` · `{type:"term-resize", cols, rows}` | raw input (base64) / match the pane to the viewport |
226
280
  | `{type:"stop", sessionId?}` | ends the session for all clients; `sessionId` targets another channel (zombie cleanup) |
227
281
 
228
282
  **server → client**
@@ -231,7 +285,7 @@ client, or immediately on an explicit `stop` (which ends it for everyone).
231
285
  |---|---|
232
286
  | `{type:"ready", sessionId, cwd}` | session started (or attached) |
233
287
  | `{type:"working"}` / `{type:"turn-done", sessionId}` | turn started / finished |
234
- | `{type:"stream-text", text}` | a complete assistant text block, from the transcript |
288
+ | `{type:"stream-text", text, at?}` | a complete assistant text block, from the transcript. `at` is when it was **written**, not when we read it |
235
289
  | `{type:"stream-tool", id, name, summary}` / `{type:"stream-result", …}` | tool call / tool result |
236
290
  | `{type:"tokens", tokens}` / `{type:"context", pct}` | token usage / context fill |
237
291
  | `{type:"prompt-echo", text}` | prompt sent by another client of the session |
@@ -241,11 +295,13 @@ client, or immediately on an explicit `stop` (which ends it for everyone).
241
295
  | `{type:"pace-blocked"}` / `{type:"pace-hold"}` / `{type:"pace-resumed"}` | quota guardrail |
242
296
  | `{type:"auto-retry"}` / `-cancelled` / `-gave-up` | transient API error being retried |
243
297
  | `{type:"version", …}` / `{type:"server-reload", version}` | update available / server updated, reload |
244
- | `{type:"gone"}` / `{type:"error", message}` / `{type:"exited", code}` / `{type:"stopped"}` | session lost, errors, termination |
298
+ | `{type:"term-data", data}` | **experimental** — raw pane output (base64) for a client-side terminal emulator |
299
+ | `{type:"gone"}` / `{type:"error", message, code?}` / `{type:"exited", code}` / `{type:"stopped"}` | session lost, errors, termination. `error.code` is `"busy"` for a prompt refused mid-turn, so a machine client needn't match on the message text |
245
300
 
246
301
  HTTP endpoints (same auth): `/usage`, `/live`, `/sessions`, `/recover`,
247
- `/diff`, `/channels`, `/groups`, `/profiles`, `/secrets`, `/telegram`,
248
- `/defaults`, `/version`, `/autoupdate`, `/permission-mode`.
302
+ `/diff`, `/channels` (its GET adds a **derived** `crons` field — never stored),
303
+ `/channel` (DELETE), `/groups`, `/crons`, `/timezone`, `/profiles`, `/secrets`,
304
+ `/telegram`, `/defaults`, `/version`, `/autoupdate`, `/permission-mode`.
249
305
 
250
306
  ## Library
251
307
 
@@ -290,7 +346,12 @@ update breaks the detection).
290
346
  - Profile guardrails are **soft**: agents run as the same OS user. It prevents
291
347
  misfires, it is not a sandbox.
292
348
  - Agent worktrees are branched at spawn and never rebased, so a long-running
293
- agent drifts from a moving main branch.
349
+ agent drifts from a moving main branch. A design for this exists and was
350
+ deliberately deferred:
351
+ `docs/superpowers/specs/2026-07-28-worktree-rebase-drift-design.md`.
352
+ - The interactive terminal (xterm.js over the raw pane stream) is
353
+ **experimental** and requires tmux; with node-pty the engine room is all there
354
+ is.
294
355
  - Dialogs the chat cannot handle in one click are managed through the
295
356
  engine room (`waitFor()` + `press()` in library mode).
296
357
  - The TUI runs in the alternate screen: `fullBuffer()` ≈ visible screen;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.1.201",
3
+ "version": "0.1.202",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test test/*.ts .claude/skills/shadok-ai-agents/test/*.test.mjs",