shadok-ai 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.
- package/README.md +196 -0
- package/dist/args.d.ts +8 -0
- package/dist/args.js +17 -0
- package/dist/args.js.map +1 -0
- package/dist/channels.d.ts +61 -0
- package/dist/channels.js +169 -0
- package/dist/channels.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +100 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/detect.d.ts +17 -0
- package/dist/detect.js +41 -0
- package/dist/detect.js.map +1 -0
- package/dist/extract.d.ts +55 -0
- package/dist/extract.js +269 -0
- package/dist/extract.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +90 -0
- package/dist/main.js.map +1 -0
- package/dist/pace.d.ts +33 -0
- package/dist/pace.js +53 -0
- package/dist/pace.js.map +1 -0
- package/dist/retry.d.ts +15 -0
- package/dist/retry.js +40 -0
- package/dist/retry.js.map +1 -0
- package/dist/secrets.d.ts +21 -0
- package/dist/secrets.js +71 -0
- package/dist/secrets.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +854 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +74 -0
- package/dist/session.js +244 -0
- package/dist/session.js.map +1 -0
- package/dist/setup-prompt.d.ts +6 -0
- package/dist/setup-prompt.js +25 -0
- package/dist/setup-prompt.js.map +1 -0
- package/dist/supervisor.d.ts +55 -0
- package/dist/supervisor.js +61 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/tail.d.ts +66 -0
- package/dist/tail.js +244 -0
- package/dist/tail.js.map +1 -0
- package/dist/telegram.d.ts +48 -0
- package/dist/telegram.js +565 -0
- package/dist/telegram.js.map +1 -0
- package/dist/tmux.d.ts +69 -0
- package/dist/tmux.js +267 -0
- package/dist/tmux.js.map +1 -0
- package/dist/update-flag.d.ts +13 -0
- package/dist/update-flag.js +36 -0
- package/dist/update-flag.js.map +1 -0
- package/dist/updater.d.ts +18 -0
- package/dist/updater.js +45 -0
- package/dist/updater.js.map +1 -0
- package/dist/usage.d.ts +23 -0
- package/dist/usage.js +125 -0
- package/dist/usage.js.map +1 -0
- package/dist/worktree.d.ts +63 -0
- package/dist/worktree.js +153 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +44 -0
- package/public/index.html +2583 -0
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# shadok-ai
|
|
2
|
+
|
|
3
|
+
> **Working on this codebase?** Read [`CLAUDE.md`](CLAUDE.md) (map, build/run,
|
|
4
|
+
> invariants) and [`docs/architecture.md`](docs/architecture.md) first — they
|
|
5
|
+
> are the current source of truth. This README covers the original single-session
|
|
6
|
+
> library/CLI; shadok-ai is now a multi-session web cockpit (see those docs).
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx shadok-ai # first run asks once for an optional Telegram bot token
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Open **http://localhost:3789**. To drive agents from Telegram, send the bot to a
|
|
15
|
+
group with Topics enabled and type `/setup` there; then `/spawn <name>` per agent.
|
|
16
|
+
Keep it current with `/update` (fetches `@latest` and respawns itself). Flags:
|
|
17
|
+
`--port <n>`, `--no-telegram`, `--version`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Drives a **Claude Code TUI** session (the interactive `claude` CLI) from Node.js, through a pseudo-terminal.
|
|
22
|
+
|
|
23
|
+
> ⚠️ The Claude Code TUI is not a stable API: a CLI update can break the
|
|
24
|
+
> detection heuristics (`❯`, `⏺`, `esc to interrupt` markers).
|
|
25
|
+
> For production use, prefer the
|
|
26
|
+
> [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk)
|
|
27
|
+
> or `claude -p --output-format stream-json`.
|
|
28
|
+
|
|
29
|
+
## How it works
|
|
30
|
+
|
|
31
|
+
- **node-pty** spawns `claude` inside a real pseudo-terminal (the TUI believes it talks to a human);
|
|
32
|
+
- **@xterm/headless** replays the ANSI stream into a virtual screen: we read
|
|
33
|
+
the screen as it would be displayed instead of parsing escape sequences;
|
|
34
|
+
- the **responses to terminal queries** (cursor position `\x1b[?6n`,
|
|
35
|
+
identification `\x1b[c`…) generated by xterm are forwarded back to the
|
|
36
|
+
PTY — without this, the TUI ignores every keystroke;
|
|
37
|
+
- prompts are sent via **bracketed paste** (`\x1b[200~…\x1b[201~`) with
|
|
38
|
+
retries, because the TUI flushes stdin received during its initialization;
|
|
39
|
+
- the "Claude is done" state is detected heuristically: no
|
|
40
|
+
`esc to interrupt` marker + screen stable for N ms.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install
|
|
46
|
+
npm run build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The `postinstall` script fixes the executable bit of node-pty's
|
|
50
|
+
`spawn-helper` (a known npm prebuilds bug on macOS).
|
|
51
|
+
|
|
52
|
+
## CLI
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
node dist/cli.js [options] "<prompt>"
|
|
56
|
+
|
|
57
|
+
--cwd <dir> working directory of the claude session
|
|
58
|
+
--continue, -c resume the latest session of this directory
|
|
59
|
+
--resume <id>, -r resume a specific session (id printed at end of run)
|
|
60
|
+
--watch mirror the TUI live on stdout
|
|
61
|
+
--keep keep the session open at the end
|
|
62
|
+
--timeout <sec> max wait for the response (default 600)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Without `--continue`/`--resume`, every run starts a **new** session.
|
|
66
|
+
The session id is printed at the end of the run (found in
|
|
67
|
+
`~/.claude/projects/<encoded cwd>/`, like any Claude Code session):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node dist/cli.js --cwd ~/my-project "Explain this project's structure"
|
|
71
|
+
# ▶ session: 5fe046dd-…
|
|
72
|
+
|
|
73
|
+
node dist/cli.js --cwd ~/my-project --resume 5fe046dd-… "Now refactor it"
|
|
74
|
+
node dist/cli.js --cwd ~/my-project -c "Continue on the latest session"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Web interface
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm run web # http://localhost:3789 (PORT=… to change)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A chat in the browser driving persistent TUI sessions, in parallel through
|
|
84
|
+
channels:
|
|
85
|
+
|
|
86
|
+
- **channels** (left column): "+ new channel" opens a new independent
|
|
87
|
+
claude session (one WebSocket connection each). Each channel's LED shows
|
|
88
|
+
its state (green: ready, amber: responding, red: ended); the × closes the
|
|
89
|
+
channel and detaches from its session; double-click a name to rename it
|
|
90
|
+
(the name is remembered by session id and reapplied on resume). The
|
|
91
|
+
header, composer and Terminal view follow the active channel. Channels
|
|
92
|
+
are remembered (localStorage): on the next page load each one is restored
|
|
93
|
+
automatically — session resumed via `--resume` and history replayed.
|
|
94
|
+
Closing a channel (×) removes it from the remembered list;
|
|
95
|
+
- **interactive dialogs**: when the TUI shows a choice (multiple-choice
|
|
96
|
+
question, permission prompt…), the options appear as clickable buttons in
|
|
97
|
+
the chat. Single select: one click validates. Multi-select (`[ ]`
|
|
98
|
+
checkboxes in the TUI): clicks toggle (the displayed state is re-read
|
|
99
|
+
from the TUI screen), then "Submit selection" confirms (Tab → Submit page
|
|
100
|
+
→ Enter). The composer is locked while a choice is pending. The
|
|
101
|
+
"Type something" option becomes a free-form text input inside the dialog
|
|
102
|
+
bubble;
|
|
103
|
+
- **history on resume**: when resuming a session (`--continue` or
|
|
104
|
+
`--resume`), the transcript is re-read from the session's `.jsonl` and
|
|
105
|
+
replayed in the chat (last 100 turns, dimmed). The markdown of the
|
|
106
|
+
responses is rendered as HTML (tables, code, lists) via `marked`, served
|
|
107
|
+
locally;
|
|
108
|
+
- **"engine room"** (Terminal button): the raw TUI screen live, with
|
|
109
|
+
clickable keys (Esc, Enter, arrows, 1/2/3, y/n…) to answer permission
|
|
110
|
+
dialogs, and "Resync chat" to resynchronize the chat after a manual
|
|
111
|
+
intervention.
|
|
112
|
+
|
|
113
|
+
### WebSocket protocol (to replace the interface)
|
|
114
|
+
|
|
115
|
+
The interface is a plain static page: all the intelligence lives server-side.
|
|
116
|
+
Any client (another front-end, a Slack bot, a script…) can replace it by
|
|
117
|
+
speaking this JSON protocol on `ws://…/ws`.
|
|
118
|
+
|
|
119
|
+
**Shared sessions**: the server keeps a single claude process per session
|
|
120
|
+
id. If several clients (tabs, browsers, interfaces) "start" the same id,
|
|
121
|
+
they attach to the same process and all receive the same events — other
|
|
122
|
+
clients' prompts (`prompt-echo`), answers, dialogs, screen. Closing a
|
|
123
|
+
connection detaches the client; the process is only stopped when the last
|
|
124
|
+
client detaches (session stays resumable) or on an explicit `stop` (ends it
|
|
125
|
+
for everyone).
|
|
126
|
+
|
|
127
|
+
**client → server**
|
|
128
|
+
|
|
129
|
+
| Message | Purpose |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `{type:"start", cwd?, resume?, continue?}` | starts or attaches to the session (once per connection) |
|
|
132
|
+
| `{type:"prompt", text}` | sends a prompt |
|
|
133
|
+
| `{type:"choose", n}` | single-select dialog: picks and validates option n |
|
|
134
|
+
| `{type:"toggle", n}` | multi-select dialog: toggles option n |
|
|
135
|
+
| `{type:"confirm"}` | multi-select dialog: submits the selection |
|
|
136
|
+
| `{type:"freetext", n, text}` | "Type something" option: sends a free-form answer |
|
|
137
|
+
| `{type:"key", key}` | raw keystroke (`enter`, `escape`, `up`, `down`, `tab`, `ctrl-c`, or a single character) |
|
|
138
|
+
| `{type:"settle"}` | after a manual intervention: waits for the turn to finish |
|
|
139
|
+
| `{type:"stop"}` | closes the session cleanly (/exit) for all clients |
|
|
140
|
+
|
|
141
|
+
**server → client**
|
|
142
|
+
|
|
143
|
+
| Message | Purpose |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `{type:"ready", sessionId, cwd}` | session started (or attached) |
|
|
146
|
+
| `{type:"working"}` | turn in progress |
|
|
147
|
+
| `{type:"prompt-echo", text}` | prompt sent by another client of the session |
|
|
148
|
+
| `{type:"answer", text, sessionId}` | response extracted from the transcript |
|
|
149
|
+
| `{type:"dialog", question, options:[{n,label,hint,checked?}], multi}` | choice pending |
|
|
150
|
+
| `{type:"history", turns:[{role,text}]}` | transcript replayed when resuming/attaching |
|
|
151
|
+
| `{type:"screen", text, working}` | rendered TUI screen (whenever it changes) |
|
|
152
|
+
| `{type:"error", message}` / `{type:"exited", code}` / `{type:"stopped"}` | errors and termination |
|
|
153
|
+
|
|
154
|
+
## Library
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
import { PtyPilot } from "shadok-ai";
|
|
158
|
+
|
|
159
|
+
const pilot = new PtyPilot({ cwd: "/my/project" });
|
|
160
|
+
pilot.start();
|
|
161
|
+
await pilot.waitForIdle(); // TUI ready
|
|
162
|
+
|
|
163
|
+
await pilot.submit("Fix the bug in auth.ts");
|
|
164
|
+
await pilot.waitForIdle({ timeoutMs: 600_000 });
|
|
165
|
+
console.log(pilot.screen()); // rendered screen (visible transcript)
|
|
166
|
+
|
|
167
|
+
await pilot.submit("Now add a test"); // same session, second turn
|
|
168
|
+
await pilot.waitForIdle();
|
|
169
|
+
|
|
170
|
+
await pilot.stop(); // clean /exit, kill as fallback
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Main API:
|
|
174
|
+
|
|
175
|
+
| Method | Purpose |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `start()` / `stop()` / `kill()` | process lifecycle |
|
|
178
|
+
| `submit(text)` | types a prompt + Enter, with verification and retries |
|
|
179
|
+
| `write(text)` / `press(key)` | low-level keystrokes (`enter`, `escape`, `up`, `tab`, `ctrl-c`…) |
|
|
180
|
+
| `screen()` / `fullBuffer()` | rendered screen / full buffer |
|
|
181
|
+
| `waitForIdle({stableMs, timeoutMs})` | waits for the end of a turn |
|
|
182
|
+
| `waitFor(predicate)` | waits for an arbitrary screen condition (dialogs, permissions…) |
|
|
183
|
+
| `isWorking()` | true while Claude is working |
|
|
184
|
+
| `onData(cb)` / `onExit(cb)` | raw ANSI stream (mirror mode) / process exit |
|
|
185
|
+
|
|
186
|
+
`examples/two-turns.mjs` shows a two-turn conversation;
|
|
187
|
+
`debug/probe.mjs` logs the terminal sequences exchanged (useful when a CLI
|
|
188
|
+
update breaks the detection).
|
|
189
|
+
|
|
190
|
+
## Known limits
|
|
191
|
+
|
|
192
|
+
- Every session launched consumes your Claude quota like a normal session.
|
|
193
|
+
- Dialogs the chat cannot handle in one click are managed through the
|
|
194
|
+
engine room (`waitFor()` + `press()` in library mode).
|
|
195
|
+
- The TUI runs in the alternate screen: `fullBuffer()` ≈ visible screen;
|
|
196
|
+
very long responses scroll out of view (increase `rows` if needed).
|
package/dist/args.d.ts
ADDED
package/dist/args.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Parse the CLI flags. Unknown flags are ignored (forward-compatible). */
|
|
2
|
+
export function parseArgs(argv) {
|
|
3
|
+
const a = { noTelegram: false, help: false, version: false };
|
|
4
|
+
for (let i = 0; i < argv.length; i++) {
|
|
5
|
+
const arg = argv[i];
|
|
6
|
+
if (arg === "--port" || arg === "-p")
|
|
7
|
+
a.port = Number(argv[++i]);
|
|
8
|
+
else if (arg === "--no-telegram")
|
|
9
|
+
a.noTelegram = true;
|
|
10
|
+
else if (arg === "--help" || arg === "-h")
|
|
11
|
+
a.help = true;
|
|
12
|
+
else if (arg === "--version" || arg === "-v")
|
|
13
|
+
a.version = true;
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=args.js.map
|
package/dist/args.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAOA,2EAA2E;AAC3E,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,CAAC,GAAS,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACnE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;YAAE,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;aAC5D,IAAI,GAAG,KAAK,eAAe;YAAE,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC;aACjD,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;YAAE,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC;aACpD,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI;YAAE,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC;IACjE,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** A Telegram chat/topic a channel is bound to (folded into the channel). */
|
|
2
|
+
export interface TgBinding {
|
|
3
|
+
chatId: number;
|
|
4
|
+
threadId?: number;
|
|
5
|
+
}
|
|
6
|
+
/** A remembered session — the ONE registry, whatever created it (web or
|
|
7
|
+
* Telegram). Enough to restore, resume, and route it. */
|
|
8
|
+
export interface Channel {
|
|
9
|
+
sessionId: string;
|
|
10
|
+
cwd: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
branch?: string | null;
|
|
13
|
+
/** Repo the worktree belongs to, to recreate a reclaimed checkout. */
|
|
14
|
+
repo?: string;
|
|
15
|
+
/** Tab group id (client-owned metadata). */
|
|
16
|
+
group?: number | null;
|
|
17
|
+
/** Present iff the session is bound to a Telegram chat/topic. */
|
|
18
|
+
telegram?: TgBinding | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function loadChannels(): Channel[];
|
|
21
|
+
export declare function saveChannels(list: Channel[]): void;
|
|
22
|
+
/**
|
|
23
|
+
* Insert or update a channel by sessionId, shallow-merging the given fields
|
|
24
|
+
* (an `undefined` field never clobbers an existing value). The server calls
|
|
25
|
+
* this whenever a session reaches `ready`, so every session — web or Telegram —
|
|
26
|
+
* lands in the one list.
|
|
27
|
+
*/
|
|
28
|
+
/** Pure core of upsertChannel — returns a new list; exported for testing. */
|
|
29
|
+
export declare function upsertInto(list: Channel[], patch: Partial<Channel> & {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
}): Channel[];
|
|
32
|
+
export declare function upsertChannel(patch: Partial<Channel> & {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
}): void;
|
|
35
|
+
export declare function removeChannel(sessionId: string): void;
|
|
36
|
+
/** Pure lookup of the channel bound to a Telegram chat/topic. */
|
|
37
|
+
export declare function findTelegramChannel(list: Channel[], chatId: number, threadId?: number): Channel | undefined;
|
|
38
|
+
/** The channel bound to a given Telegram chat/topic, or undefined. */
|
|
39
|
+
export declare function channelForTelegram(chatId: number, threadId?: number): Channel | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Pure core of the PUT merge (exported for testing). The client drives order
|
|
42
|
+
* and its own metadata (name, group); server-owned fields are preserved per
|
|
43
|
+
* sessionId, and any stored channel the client omitted is kept when its session
|
|
44
|
+
* is live or it has a Telegram binding — so persistence never drops a
|
|
45
|
+
* live/Telegram session (invariant #6).
|
|
46
|
+
*/
|
|
47
|
+
export declare function mergeChannels(stored: Channel[], clientList: Channel[], liveIds: Set<string>): Channel[];
|
|
48
|
+
export declare function mergeClientChannels(clientList: Channel[], liveIds: Set<string>): Channel[];
|
|
49
|
+
/** Tab groups (id, name, collapsed, order), persisted the same way as channels. */
|
|
50
|
+
export declare function loadGroups(): any[];
|
|
51
|
+
export declare function saveGroups(list: any[]): void;
|
|
52
|
+
/** The single group this instance is bound to (one board per instance), or null.
|
|
53
|
+
* Instance-level, not a session — kept separate from the channel registry. */
|
|
54
|
+
export declare function loadTgGroup(): number | null;
|
|
55
|
+
export declare function saveTgGroup(groupId: number | null): void;
|
|
56
|
+
/**
|
|
57
|
+
* One-time migration of the old separate `…-telegram.json` bindings into the
|
|
58
|
+
* channel registry. Idempotent: renames the file once folded so it's never
|
|
59
|
+
* re-applied. cwd/name are filled in later when the session next reaches ready.
|
|
60
|
+
*/
|
|
61
|
+
export declare function migrateTgBindings(): void;
|
package/dist/channels.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/** Fields the server owns; a browser PUT must never overwrite or drop them. */
|
|
5
|
+
const SERVER_OWNED = ["cwd", "branch", "repo", "telegram"];
|
|
6
|
+
/**
|
|
7
|
+
* The registry is stored server-side, keyed by the directory the server was
|
|
8
|
+
* launched from — so the set of sessions survives a wiped browser, another
|
|
9
|
+
* device, a restart, or a reboot. One file per launch dir:
|
|
10
|
+
* ~/.shadok-ai/channels/<encoded cwd>.json.
|
|
11
|
+
*/
|
|
12
|
+
function storeFile(kind) {
|
|
13
|
+
const enc = process.cwd().replace(/[^a-zA-Z0-9]/g, "-");
|
|
14
|
+
const suffix = kind === "channels" ? "" : "-" + kind;
|
|
15
|
+
return path.join(os.homedir(), ".shadok-ai", "channels", enc + suffix + ".json");
|
|
16
|
+
}
|
|
17
|
+
function readJson(kind) {
|
|
18
|
+
try {
|
|
19
|
+
const v = JSON.parse(fs.readFileSync(storeFile(kind), "utf8"));
|
|
20
|
+
return Array.isArray(v) ? v : [];
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function writeJson(kind, value) {
|
|
27
|
+
const f = storeFile(kind);
|
|
28
|
+
try {
|
|
29
|
+
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
30
|
+
fs.writeFileSync(f, JSON.stringify(value, null, 2));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// best effort: losing the persisted list is non-fatal
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function loadChannels() {
|
|
37
|
+
return readJson("channels").filter((c) => c && typeof c.sessionId === "string");
|
|
38
|
+
}
|
|
39
|
+
export function saveChannels(list) {
|
|
40
|
+
writeJson("channels", list);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Insert or update a channel by sessionId, shallow-merging the given fields
|
|
44
|
+
* (an `undefined` field never clobbers an existing value). The server calls
|
|
45
|
+
* this whenever a session reaches `ready`, so every session — web or Telegram —
|
|
46
|
+
* lands in the one list.
|
|
47
|
+
*/
|
|
48
|
+
/** Pure core of upsertChannel — returns a new list; exported for testing. */
|
|
49
|
+
export function upsertInto(list, patch) {
|
|
50
|
+
const out = list.map((c) => ({ ...c }));
|
|
51
|
+
const cur = out.find((c) => c.sessionId === patch.sessionId);
|
|
52
|
+
if (!cur) {
|
|
53
|
+
out.push({ cwd: "", ...patch });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const [k, v] of Object.entries(patch))
|
|
57
|
+
if (v !== undefined)
|
|
58
|
+
cur[k] = v;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
export function upsertChannel(patch) {
|
|
63
|
+
saveChannels(upsertInto(loadChannels(), patch));
|
|
64
|
+
}
|
|
65
|
+
export function removeChannel(sessionId) {
|
|
66
|
+
saveChannels(loadChannels().filter((c) => c.sessionId !== sessionId));
|
|
67
|
+
}
|
|
68
|
+
/** Pure lookup of the channel bound to a Telegram chat/topic. */
|
|
69
|
+
export function findTelegramChannel(list, chatId, threadId) {
|
|
70
|
+
return list.find((c) => c.telegram && c.telegram.chatId === chatId && (c.telegram.threadId ?? undefined) === threadId);
|
|
71
|
+
}
|
|
72
|
+
/** The channel bound to a given Telegram chat/topic, or undefined. */
|
|
73
|
+
export function channelForTelegram(chatId, threadId) {
|
|
74
|
+
return findTelegramChannel(loadChannels(), chatId, threadId);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Pure core of the PUT merge (exported for testing). The client drives order
|
|
78
|
+
* and its own metadata (name, group); server-owned fields are preserved per
|
|
79
|
+
* sessionId, and any stored channel the client omitted is kept when its session
|
|
80
|
+
* is live or it has a Telegram binding — so persistence never drops a
|
|
81
|
+
* live/Telegram session (invariant #6).
|
|
82
|
+
*/
|
|
83
|
+
export function mergeChannels(stored, clientList, liveIds) {
|
|
84
|
+
const byId = new Map(stored.map((c) => [c.sessionId, c]));
|
|
85
|
+
const result = [];
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
for (const c of Array.isArray(clientList) ? clientList : []) {
|
|
88
|
+
if (!c || typeof c.sessionId !== "string")
|
|
89
|
+
continue;
|
|
90
|
+
seen.add(c.sessionId);
|
|
91
|
+
const prev = byId.get(c.sessionId);
|
|
92
|
+
if (!prev) {
|
|
93
|
+
result.push(c); // a new channel the client just created
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const merged = { ...c };
|
|
97
|
+
for (const k of SERVER_OWNED)
|
|
98
|
+
if (prev[k] !== undefined)
|
|
99
|
+
merged[k] = prev[k];
|
|
100
|
+
result.push(merged);
|
|
101
|
+
}
|
|
102
|
+
for (const c of stored) {
|
|
103
|
+
if (seen.has(c.sessionId))
|
|
104
|
+
continue;
|
|
105
|
+
if (liveIds.has(c.sessionId) || c.telegram)
|
|
106
|
+
result.push(c); // never erase live/Telegram
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
export function mergeClientChannels(clientList, liveIds) {
|
|
111
|
+
const result = mergeChannels(loadChannels(), clientList, liveIds);
|
|
112
|
+
saveChannels(result);
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
/** Tab groups (id, name, collapsed, order), persisted the same way as channels. */
|
|
116
|
+
export function loadGroups() {
|
|
117
|
+
return readJson("groups");
|
|
118
|
+
}
|
|
119
|
+
export function saveGroups(list) {
|
|
120
|
+
writeJson("groups", list);
|
|
121
|
+
}
|
|
122
|
+
/** The single group this instance is bound to (one board per instance), or null.
|
|
123
|
+
* Instance-level, not a session — kept separate from the channel registry. */
|
|
124
|
+
export function loadTgGroup() {
|
|
125
|
+
const v = readJson("telegram-group")[0];
|
|
126
|
+
return typeof v === "number" ? v : null;
|
|
127
|
+
}
|
|
128
|
+
export function saveTgGroup(groupId) {
|
|
129
|
+
writeJson("telegram-group", groupId == null ? [] : [groupId]);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* One-time migration of the old separate `…-telegram.json` bindings into the
|
|
133
|
+
* channel registry. Idempotent: renames the file once folded so it's never
|
|
134
|
+
* re-applied. cwd/name are filled in later when the session next reaches ready.
|
|
135
|
+
*/
|
|
136
|
+
export function migrateTgBindings() {
|
|
137
|
+
const f = storeFile("telegram");
|
|
138
|
+
let raw;
|
|
139
|
+
try {
|
|
140
|
+
raw = fs.readFileSync(f, "utf8");
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return; // already migrated or never existed
|
|
144
|
+
}
|
|
145
|
+
let bindings;
|
|
146
|
+
try {
|
|
147
|
+
bindings = JSON.parse(raw);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(bindings)) {
|
|
153
|
+
for (const b of bindings) {
|
|
154
|
+
if (b && typeof b.sessionId === "string" && typeof b.chatId === "number") {
|
|
155
|
+
upsertChannel({
|
|
156
|
+
sessionId: b.sessionId,
|
|
157
|
+
telegram: { chatId: b.chatId, ...(typeof b.threadId === "number" ? { threadId: b.threadId } : {}) },
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
fs.renameSync(f, f + ".migrated");
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
/* best effort */
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=channels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../src/channels.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { extractResponse, findSessionId } from "./extract.js";
|
|
3
|
+
import { PtyPilot } from "./session.js";
|
|
4
|
+
function usage() {
|
|
5
|
+
console.error(`Usage: shadok-ai [options] "<prompt>"
|
|
6
|
+
|
|
7
|
+
Sends a prompt to a PTY-driven Claude Code TUI session and prints the
|
|
8
|
+
response once the session becomes idle again.
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--cwd <dir> Working directory of the session (default: current cwd)
|
|
12
|
+
--continue, -c Resume the latest session of this directory
|
|
13
|
+
--resume <id>, -r Resume a specific session (id printed at end of run)
|
|
14
|
+
--watch Mirror the TUI live on stdout
|
|
15
|
+
--keep Keep the session open at the end (Ctrl-C to quit)
|
|
16
|
+
--timeout <sec> Max wait for the response (default: 600)
|
|
17
|
+
`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const argv = process.argv.slice(2);
|
|
21
|
+
let cwd = process.cwd();
|
|
22
|
+
let watch = false;
|
|
23
|
+
let keep = false;
|
|
24
|
+
let timeoutSec = 600;
|
|
25
|
+
let continueSession = false;
|
|
26
|
+
let resumeId = null;
|
|
27
|
+
const rest = [];
|
|
28
|
+
for (let i = 0; i < argv.length; i++) {
|
|
29
|
+
const a = argv[i];
|
|
30
|
+
if (a === "--cwd")
|
|
31
|
+
cwd = argv[++i] ?? usage();
|
|
32
|
+
else if (a === "--continue" || a === "-c")
|
|
33
|
+
continueSession = true;
|
|
34
|
+
else if (a === "--resume" || a === "-r")
|
|
35
|
+
resumeId = argv[++i] ?? usage();
|
|
36
|
+
else if (a === "--watch")
|
|
37
|
+
watch = true;
|
|
38
|
+
else if (a === "--keep")
|
|
39
|
+
keep = true;
|
|
40
|
+
else if (a === "--timeout")
|
|
41
|
+
timeoutSec = Number(argv[++i] ?? usage());
|
|
42
|
+
else if (a === "--help" || a === "-h")
|
|
43
|
+
usage();
|
|
44
|
+
else
|
|
45
|
+
rest.push(a);
|
|
46
|
+
}
|
|
47
|
+
const prompt = rest.join(" ").trim();
|
|
48
|
+
if (!prompt)
|
|
49
|
+
usage();
|
|
50
|
+
if (continueSession && resumeId) {
|
|
51
|
+
console.error("--continue and --resume are mutually exclusive.");
|
|
52
|
+
usage();
|
|
53
|
+
}
|
|
54
|
+
const claudeArgs = [];
|
|
55
|
+
if (continueSession)
|
|
56
|
+
claudeArgs.push("--continue");
|
|
57
|
+
if (resumeId)
|
|
58
|
+
claudeArgs.push("--resume", resumeId);
|
|
59
|
+
const pilot = new PtyPilot({ cwd, args: claudeArgs });
|
|
60
|
+
async function main() {
|
|
61
|
+
console.error("▶ starting claude…");
|
|
62
|
+
pilot.start();
|
|
63
|
+
if (watch)
|
|
64
|
+
pilot.onData((chunk) => process.stdout.write(chunk));
|
|
65
|
+
// Wait for the TUI to be ready (stable screen, no spinner).
|
|
66
|
+
let screen = await pilot.waitForIdle({ stableMs: 1200, timeoutMs: 60_000 });
|
|
67
|
+
// First launch in a directory: trust dialog.
|
|
68
|
+
if (/do you trust the files/i.test(screen)) {
|
|
69
|
+
console.error("▶ trust dialog detected, accepting…");
|
|
70
|
+
pilot.press("enter");
|
|
71
|
+
screen = await pilot.waitForIdle({ stableMs: 1200, timeoutMs: 30_000 });
|
|
72
|
+
}
|
|
73
|
+
console.error(`▶ sending prompt: ${prompt}`);
|
|
74
|
+
await pilot.submit(prompt);
|
|
75
|
+
await pilot.waitForIdle({ stableMs: 2000, timeoutMs: timeoutSec * 1000 });
|
|
76
|
+
console.error("▶ response received:\n");
|
|
77
|
+
process.stdout.write(extractResponse(pilot.fullBuffer(), prompt) + "\n");
|
|
78
|
+
const sessionId = findSessionId(cwd);
|
|
79
|
+
if (sessionId) {
|
|
80
|
+
console.error(`\n▶ session: ${sessionId}`);
|
|
81
|
+
console.error(` resume with: shadok-ai --resume ${sessionId} "<prompt>"`);
|
|
82
|
+
}
|
|
83
|
+
if (keep) {
|
|
84
|
+
console.error("\n▶ session kept open (--keep). Ctrl-C to quit.");
|
|
85
|
+
process.on("SIGINT", async () => {
|
|
86
|
+
await pilot.stop();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
await pilot.stop();
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
main().catch((err) => {
|
|
96
|
+
console.error(err instanceof Error ? err.message : err);
|
|
97
|
+
pilot.kill();
|
|
98
|
+
process.exit(1);
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global CLI config, stored at ~/.shadok-ai/config.json (mode 600). Holds the
|
|
3
|
+
* Telegram bot token and the port. Replaces the old telegram.env shell hack:
|
|
4
|
+
* the token now lives here and is passed to the server child via env.
|
|
5
|
+
*
|
|
6
|
+
* `telegramToken` semantics:
|
|
7
|
+
* undefined → never asked yet (first run should prompt)
|
|
8
|
+
* null → asked and deliberately skipped (never prompt again)
|
|
9
|
+
* string → the token
|
|
10
|
+
*/
|
|
11
|
+
export interface Config {
|
|
12
|
+
telegramToken?: string | null;
|
|
13
|
+
port?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const SHADOK_DIR: string;
|
|
16
|
+
export declare function loadConfig(): Config;
|
|
17
|
+
export declare function saveConfig(cfg: Config): void;
|
|
18
|
+
/**
|
|
19
|
+
* One-time migration of the legacy ~/.shadok-ai/telegram.env
|
|
20
|
+
* (`TELEGRAM_BOT_TOKEN=...`) into config.json. Returns the migrated token, or
|
|
21
|
+
* null if there was nothing to migrate. The env file is left on disk but no
|
|
22
|
+
* longer read by anything.
|
|
23
|
+
*/
|
|
24
|
+
export declare function migrateLegacyEnv(cfg: Config): string | null;
|
|
25
|
+
/** Pull TELEGRAM_BOT_TOKEN out of a shell-style env file body. */
|
|
26
|
+
export declare function parseLegacyToken(raw: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* The effective token to hand the server: an explicit env var always wins,
|
|
29
|
+
* otherwise the configured value (a null/undefined config means "no token").
|
|
30
|
+
*/
|
|
31
|
+
export declare function effectiveToken(cfg: Config, env?: NodeJS.ProcessEnv): string | null;
|