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/dist/tmux.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { PilotOptions, WaitIdleOptions, WaitOptions } from "./session.js";
|
|
2
|
+
/**
|
|
3
|
+
* Same interface as {@link PtyPilot}, but runs `claude` inside a detached
|
|
4
|
+
* **tmux** session instead of a node-pty child. tmux (its own daemon) owns the
|
|
5
|
+
* terminal, so the agent survives the shadok-ai server restarting or
|
|
6
|
+
* crashing: on restart the server reattaches to the running tmux session and
|
|
7
|
+
* the in-flight turn continues uninterrupted.
|
|
8
|
+
*
|
|
9
|
+
* The tmux session name is derived from the Claude session id, so reattach is
|
|
10
|
+
* deterministic. Content still comes from the .jsonl tail (unchanged); tmux is
|
|
11
|
+
* only about keeping the live process alive.
|
|
12
|
+
*/
|
|
13
|
+
export interface TmuxPilotOptions extends PilotOptions {
|
|
14
|
+
/** tmux session name (stable across restarts — derive from the session id). */
|
|
15
|
+
tmuxName: string;
|
|
16
|
+
}
|
|
17
|
+
/** True when a tmux session with this exact name is alive. */
|
|
18
|
+
export declare function tmuxHasSession(name: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The working directory of a live tmux session's pane — the source of truth for
|
|
21
|
+
* a reattached session's cwd (a worktree agent's real directory), which the
|
|
22
|
+
* client can't always supply on resume. Null if the session is gone.
|
|
23
|
+
*/
|
|
24
|
+
export declare function tmuxPaneCwd(name: string): string | null;
|
|
25
|
+
export declare class TmuxPilot {
|
|
26
|
+
private readonly opts;
|
|
27
|
+
private readonly name;
|
|
28
|
+
private exitListeners;
|
|
29
|
+
private poller;
|
|
30
|
+
private _screen;
|
|
31
|
+
private exited;
|
|
32
|
+
/** True when we reattached to an already-running session (survived restart). */
|
|
33
|
+
attached: boolean;
|
|
34
|
+
constructor(options: TmuxPilotOptions);
|
|
35
|
+
/** Whether a tmux session with our name is currently alive. */
|
|
36
|
+
private hasSession;
|
|
37
|
+
start(): void;
|
|
38
|
+
private tick;
|
|
39
|
+
/** Refreshes the cached rendered screen from tmux. */
|
|
40
|
+
private capture;
|
|
41
|
+
onExit(cb: (code: number) => void): () => void;
|
|
42
|
+
get hasExited(): boolean;
|
|
43
|
+
/** The currently rendered screen (cached, refreshed by the poller). */
|
|
44
|
+
screen(): string;
|
|
45
|
+
/** tmux gives us the rendered pane incl. scrollback; used rarely. */
|
|
46
|
+
fullBuffer(): string;
|
|
47
|
+
/** Sends literal text to the session (no submit). */
|
|
48
|
+
write(text: string): void;
|
|
49
|
+
/** Pastes text with bracketed-paste framing (reliable for the TUI input). */
|
|
50
|
+
private paste;
|
|
51
|
+
press(key: "enter" | "escape" | "up" | "down" | "left" | "right" | "tab" | "ctrl-c"): void;
|
|
52
|
+
isWorking(): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Types a prompt then presses Enter — same robustness as PtyPilot:
|
|
55
|
+
* bracketed paste with retry until the text shows, then Enter with retry
|
|
56
|
+
* until the turn is actually sent.
|
|
57
|
+
*/
|
|
58
|
+
submit(text: string): Promise<void>;
|
|
59
|
+
/** A concise client-facing error; the full screen goes to the server log only. */
|
|
60
|
+
private submitError;
|
|
61
|
+
waitFor(predicate: (screen: string) => boolean, { timeoutMs }?: WaitOptions): Promise<string>;
|
|
62
|
+
waitForIdle({ stableMs, timeoutMs, }?: WaitIdleOptions): Promise<string>;
|
|
63
|
+
/** Clean shutdown: /exit, then kill the tmux session as a fallback. */
|
|
64
|
+
stop(): Promise<void>;
|
|
65
|
+
/** Kills the tmux session (ends the agent). */
|
|
66
|
+
kill(): void;
|
|
67
|
+
}
|
|
68
|
+
/** True when tmux is available on this host. */
|
|
69
|
+
export declare function tmuxAvailable(): boolean;
|
package/dist/tmux.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { screenShowsWork, inputHasProbe } from "./detect.js";
|
|
3
|
+
function tmux(args, input) {
|
|
4
|
+
return execFileSync("tmux", args, {
|
|
5
|
+
encoding: "utf8",
|
|
6
|
+
input,
|
|
7
|
+
timeout: 10_000,
|
|
8
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
9
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function tmuxOk(args) {
|
|
13
|
+
try {
|
|
14
|
+
tmux(args);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** True when a tmux session with this exact name is alive. */
|
|
22
|
+
export function tmuxHasSession(name) {
|
|
23
|
+
return tmuxOk(["has-session", "-t", name]);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The working directory of a live tmux session's pane — the source of truth for
|
|
27
|
+
* a reattached session's cwd (a worktree agent's real directory), which the
|
|
28
|
+
* client can't always supply on resume. Null if the session is gone.
|
|
29
|
+
*/
|
|
30
|
+
export function tmuxPaneCwd(name) {
|
|
31
|
+
try {
|
|
32
|
+
const p = tmux(["display-message", "-p", "-t", name, "#{pane_current_path}"]).trim();
|
|
33
|
+
return p || null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export class TmuxPilot {
|
|
40
|
+
opts;
|
|
41
|
+
name;
|
|
42
|
+
exitListeners = new Set();
|
|
43
|
+
poller = null;
|
|
44
|
+
_screen = "";
|
|
45
|
+
exited = false;
|
|
46
|
+
/** True when we reattached to an already-running session (survived restart). */
|
|
47
|
+
attached = false;
|
|
48
|
+
constructor(options) {
|
|
49
|
+
this.opts = { cols: 100, rows: 40, ...options };
|
|
50
|
+
this.name = options.tmuxName;
|
|
51
|
+
}
|
|
52
|
+
/** Whether a tmux session with our name is currently alive. */
|
|
53
|
+
hasSession() {
|
|
54
|
+
return tmuxOk(["has-session", "-t", this.name]);
|
|
55
|
+
}
|
|
56
|
+
start() {
|
|
57
|
+
if (this.hasSession()) {
|
|
58
|
+
// Reattach to a session that outlived a previous server: claude is
|
|
59
|
+
// already past the trust dialog and holding its state.
|
|
60
|
+
this.attached = true;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Strip a parent Claude Code session's vars (a nested claude that sees
|
|
64
|
+
// them may disable interactive mode), like PtyPilot does.
|
|
65
|
+
const unset = Object.keys(process.env)
|
|
66
|
+
.filter((k) => /^(CLAUDE|CLAUDECODE|AI_AGENT)/.test(k))
|
|
67
|
+
.flatMap((k) => ["-u", k]);
|
|
68
|
+
const bin = this.opts.claudePath ?? "claude";
|
|
69
|
+
// Repo secrets → KEY=VALUE assignments for the `env` prefix (each token is
|
|
70
|
+
// single-quoted below, so values with spaces/specials are safe).
|
|
71
|
+
const secretEnv = Object.entries(this.opts.env ?? {}).map(([k, v]) => `${k}=${v}`);
|
|
72
|
+
const cmd = ["env", ...unset, "TERM=xterm-256color", ...secretEnv, bin, ...(this.opts.args ?? [])]
|
|
73
|
+
.map((a) => `'${String(a).replace(/'/g, "'\\''")}'`)
|
|
74
|
+
.join(" ");
|
|
75
|
+
tmux([
|
|
76
|
+
"new-session",
|
|
77
|
+
"-d",
|
|
78
|
+
"-s", this.name,
|
|
79
|
+
"-x", String(this.opts.cols),
|
|
80
|
+
"-y", String(this.opts.rows),
|
|
81
|
+
"-c", this.opts.cwd ?? process.cwd(),
|
|
82
|
+
cmd,
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
this.capture();
|
|
86
|
+
this.poller = setInterval(() => this.tick(), 250);
|
|
87
|
+
}
|
|
88
|
+
tick() {
|
|
89
|
+
if (this.exited)
|
|
90
|
+
return;
|
|
91
|
+
if (!this.hasSession()) {
|
|
92
|
+
this.exited = true;
|
|
93
|
+
if (this.poller)
|
|
94
|
+
clearInterval(this.poller);
|
|
95
|
+
this.poller = null;
|
|
96
|
+
for (const cb of this.exitListeners)
|
|
97
|
+
cb(0);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.capture();
|
|
101
|
+
}
|
|
102
|
+
/** Refreshes the cached rendered screen from tmux. */
|
|
103
|
+
capture() {
|
|
104
|
+
try {
|
|
105
|
+
this._screen = tmux(["capture-pane", "-t", this.name, "-p"]).replace(/\n+$/, "");
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
/* session gone or transient tmux error — keep last */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
onExit(cb) {
|
|
112
|
+
this.exitListeners.add(cb);
|
|
113
|
+
return () => this.exitListeners.delete(cb);
|
|
114
|
+
}
|
|
115
|
+
get hasExited() {
|
|
116
|
+
return this.exited;
|
|
117
|
+
}
|
|
118
|
+
/** The currently rendered screen (cached, refreshed by the poller). */
|
|
119
|
+
screen() {
|
|
120
|
+
return this._screen;
|
|
121
|
+
}
|
|
122
|
+
/** tmux gives us the rendered pane incl. scrollback; used rarely. */
|
|
123
|
+
fullBuffer() {
|
|
124
|
+
try {
|
|
125
|
+
return tmux(["capture-pane", "-t", this.name, "-p", "-S", "-"]).replace(/\n+$/, "");
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return this._screen;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Sends literal text to the session (no submit). */
|
|
132
|
+
write(text) {
|
|
133
|
+
tmux(["send-keys", "-t", this.name, "-l", "--", text]);
|
|
134
|
+
}
|
|
135
|
+
/** Pastes text with bracketed-paste framing (reliable for the TUI input). */
|
|
136
|
+
paste(text) {
|
|
137
|
+
tmux(["load-buffer", "-b", "cp", "-"], text);
|
|
138
|
+
tmux(["paste-buffer", "-t", this.name, "-b", "cp", "-p", "-d"]);
|
|
139
|
+
}
|
|
140
|
+
press(key) {
|
|
141
|
+
const map = {
|
|
142
|
+
enter: "Enter",
|
|
143
|
+
escape: "Escape",
|
|
144
|
+
up: "Up",
|
|
145
|
+
down: "Down",
|
|
146
|
+
left: "Left",
|
|
147
|
+
right: "Right",
|
|
148
|
+
tab: "Tab",
|
|
149
|
+
"ctrl-c": "C-c",
|
|
150
|
+
};
|
|
151
|
+
tmux(["send-keys", "-t", this.name, map[key]]);
|
|
152
|
+
}
|
|
153
|
+
isWorking() {
|
|
154
|
+
return screenShowsWork(this.screen());
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Types a prompt then presses Enter — same robustness as PtyPilot:
|
|
158
|
+
* bracketed paste with retry until the text shows, then Enter with retry
|
|
159
|
+
* until the turn is actually sent.
|
|
160
|
+
*/
|
|
161
|
+
async submit(text) {
|
|
162
|
+
const probe = text.slice(0, 15);
|
|
163
|
+
let typed = false;
|
|
164
|
+
for (let attempt = 0; attempt < 6; attempt++) {
|
|
165
|
+
this.paste(text);
|
|
166
|
+
try {
|
|
167
|
+
await this.waitFor((s) => s.includes(probe), { timeoutMs: 2_000 });
|
|
168
|
+
typed = true;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
this.write("\x15"); // Ctrl-U: clear partial input
|
|
173
|
+
await sleep(300);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!typed) {
|
|
177
|
+
throw this.submitError("the text never appeared in the input box");
|
|
178
|
+
}
|
|
179
|
+
await sleep(200);
|
|
180
|
+
// Enter was accepted once the text we typed has LEFT the input box (or the
|
|
181
|
+
// spinner is up). Don't require the echo to still be visible in scrollback:
|
|
182
|
+
// a fast turn scrolls it away and that caused false "not sent" dumps.
|
|
183
|
+
const submitted = (s) => screenShowsWork(s) || !inputHasProbe(s, probe);
|
|
184
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
185
|
+
this.press("enter");
|
|
186
|
+
try {
|
|
187
|
+
await this.waitFor(submitted, { timeoutMs: 3_000 });
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
/* Enter swallowed — retry */
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
throw this.submitError("the prompt does not seem to have been sent");
|
|
195
|
+
}
|
|
196
|
+
/** A concise client-facing error; the full screen goes to the server log only. */
|
|
197
|
+
submitError(reason) {
|
|
198
|
+
console.error(`[${this.name}] submit failed — ${reason}. Screen:\n${this.screen()}`);
|
|
199
|
+
return new Error(`submit: ${reason}.`);
|
|
200
|
+
}
|
|
201
|
+
async waitFor(predicate, { timeoutMs = 60_000 } = {}) {
|
|
202
|
+
const deadline = Date.now() + timeoutMs;
|
|
203
|
+
while (Date.now() < deadline) {
|
|
204
|
+
if (this.exited)
|
|
205
|
+
throw new Error("The claude process has exited.");
|
|
206
|
+
const s = this.screen();
|
|
207
|
+
if (predicate(s))
|
|
208
|
+
return s;
|
|
209
|
+
await sleep(120);
|
|
210
|
+
}
|
|
211
|
+
throw new Error(`waitFor: timed out after ${timeoutMs} ms. Last screen:\n${this.screen()}`);
|
|
212
|
+
}
|
|
213
|
+
async waitForIdle({ stableMs = 1_500, timeoutMs = 600_000, } = {}) {
|
|
214
|
+
const deadline = Date.now() + timeoutMs;
|
|
215
|
+
let lastScreen = "";
|
|
216
|
+
let stableSince = 0;
|
|
217
|
+
while (Date.now() < deadline) {
|
|
218
|
+
if (this.exited)
|
|
219
|
+
throw new Error("The claude process has exited.");
|
|
220
|
+
const s = this.screen();
|
|
221
|
+
const working = screenShowsWork(s);
|
|
222
|
+
if (!working && s === lastScreen) {
|
|
223
|
+
if (stableSince === 0)
|
|
224
|
+
stableSince = Date.now();
|
|
225
|
+
if (Date.now() - stableSince >= stableMs)
|
|
226
|
+
return s;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
stableSince = 0;
|
|
230
|
+
lastScreen = s;
|
|
231
|
+
}
|
|
232
|
+
await sleep(160);
|
|
233
|
+
}
|
|
234
|
+
throw new Error(`waitForIdle: timed out after ${timeoutMs} ms. Last screen:\n${this.screen()}`);
|
|
235
|
+
}
|
|
236
|
+
/** Clean shutdown: /exit, then kill the tmux session as a fallback. */
|
|
237
|
+
async stop() {
|
|
238
|
+
if (this.exited)
|
|
239
|
+
return;
|
|
240
|
+
try {
|
|
241
|
+
await this.submit("/exit");
|
|
242
|
+
const deadline = Date.now() + 8_000;
|
|
243
|
+
while (Date.now() < deadline && this.hasSession())
|
|
244
|
+
await sleep(200);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
/* fall through to kill */
|
|
248
|
+
}
|
|
249
|
+
this.kill();
|
|
250
|
+
}
|
|
251
|
+
/** Kills the tmux session (ends the agent). */
|
|
252
|
+
kill() {
|
|
253
|
+
tmuxOk(["kill-session", "-t", this.name]);
|
|
254
|
+
if (this.poller)
|
|
255
|
+
clearInterval(this.poller);
|
|
256
|
+
this.poller = null;
|
|
257
|
+
this.exited = true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/** True when tmux is available on this host. */
|
|
261
|
+
export function tmuxAvailable() {
|
|
262
|
+
return tmuxOk(["-V"]);
|
|
263
|
+
}
|
|
264
|
+
function sleep(ms) {
|
|
265
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=tmux.js.map
|
package/dist/tmux.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux.js","sourceRoot":"","sources":["../src/tmux.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmB7D,SAAS,IAAI,CAAC,IAAc,EAAE,KAAc;IAC1C,OAAO,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE;QAChC,QAAQ,EAAE,MAAM;QAChB,KAAK;QACL,OAAO,EAAE,MAAM;QACf,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;QAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;KAClC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,MAAM,CAAC,IAAc;IAC5B,IAAI,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,MAAM,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACrF,OAAO,CAAC,IAAI,IAAI,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,OAAO,SAAS;IACH,IAAI,CAAuE;IAC3E,IAAI,CAAS;IACtB,aAAa,GAAG,IAAI,GAAG,EAA0B,CAAC;IAClD,MAAM,GAA0C,IAAI,CAAC;IACrD,OAAO,GAAG,EAAE,CAAC;IACb,MAAM,GAAG,KAAK,CAAC;IACvB,gFAAgF;IAChF,QAAQ,GAAG,KAAK,CAAC;IAEjB,YAAY,OAAyB;QACnC,IAAI,CAAC,IAAI,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAED,+DAA+D;IACvD,UAAU;QAChB,OAAO,MAAM,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACtB,mEAAmE;YACnE,uDAAuD;YACvD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,uEAAuE;YACvE,0DAA0D;YAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;iBACnC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,+BAA+B,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;iBACtD,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,QAAQ,CAAC;YAC7C,2EAA2E;YAC3E,iEAAiE;YACjE,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnF,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,EAAE,qBAAqB,EAAE,GAAG,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;iBAC/F,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;iBACnD,IAAI,CAAC,GAAG,CAAC,CAAC;YACb,IAAI,CAAC;gBACH,aAAa;gBACb,IAAI;gBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC5B,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;gBACpC,GAAG;aACJ,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;IACpD,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,IAAI,IAAI,CAAC,MAAM;gBAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,aAAa;gBAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,sDAAsD;IAC9C,OAAO;QACb,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnF,CAAC;QAAC,MAAM,CAAC;YACP,sDAAsD;QACxD,CAAC;IACH,CAAC;IAED,MAAM,CAAC,EAA0B;QAC/B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,uEAAuE;IACvE,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,qEAAqE;IACrE,UAAU;QACR,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACtF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,OAAO,CAAC;QACtB,CAAC;IACH,CAAC;IAED,qDAAqD;IACrD,KAAK,CAAC,IAAY;QAChB,IAAI,CAAC,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,6EAA6E;IACrE,KAAK,CAAC,IAAY;QACxB,IAAI,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,GAA6E;QACjF,MAAM,GAAG,GAA2B;YAClC,KAAK,EAAE,OAAO;YACd,MAAM,EAAE,QAAQ;YAChB,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,OAAO;YACd,GAAG,EAAE,KAAK;YACV,QAAQ,EAAE,KAAK;SAChB,CAAC;QACF,IAAI,CAAC,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,SAAS;QACP,OAAO,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChC,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;gBACnE,KAAK,GAAG,IAAI,CAAC;gBACb,MAAM;YACR,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,8BAA8B;gBAClD,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,CAAC,WAAW,CAAC,0CAA0C,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACjB,2EAA2E;QAC3E,4EAA4E;QAC5E,sEAAsE;QACtE,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAChF,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;gBACpD,OAAO;YACT,CAAC;YAAC,MAAM,CAAC;gBACP,6BAA6B;YAC/B,CAAC;QACH,CAAC;QACD,MAAM,IAAI,CAAC,WAAW,CAAC,4CAA4C,CAAC,CAAC;IACvE,CAAC;IAED,kFAAkF;IAC1E,WAAW,CAAC,MAAc;QAChC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,qBAAqB,MAAM,cAAc,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACrF,OAAO,IAAI,KAAK,CAAC,WAAW,MAAM,GAAG,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,OAAO,CACX,SAAsC,EACtC,EAAE,SAAS,GAAG,MAAM,EAAE,GAAgB,EAAE;QAExC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YACnE,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACxB,IAAI,SAAS,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,CAAC;YAC3B,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,4BAA4B,SAAS,sBAAsB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,EAChB,QAAQ,GAAG,KAAK,EAChB,SAAS,GAAG,OAAO,GACpB,GAAoB,EAAE;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACxC,IAAI,UAAU,GAAG,EAAE,CAAC;QACpB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YACnE,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,UAAU,EAAE,CAAC;gBACjC,IAAI,WAAW,KAAK,CAAC;oBAAE,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAChD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,IAAI,QAAQ;oBAAE,OAAO,CAAC,CAAC;YACrD,CAAC;iBAAM,CAAC;gBACN,WAAW,GAAG,CAAC,CAAC;gBAChB,UAAU,GAAG,CAAC,CAAC;YACjB,CAAC;YACD,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,gCAAgC,SAAS,sBAAsB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAClG,CAAC;IAED,uEAAuE;IACvE,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;YACpC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,IAAI,CAAC,UAAU,EAAE;gBAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,0BAA0B;QAC5B,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,+CAA+C;IAC/C,IAAI;QACF,MAAM,CAAC,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,MAAM;YAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;CACF;AAED,gDAAgD;AAChD,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-shot handshake between the supervisor (which performs an update) and
|
|
3
|
+
* the freshly-spawned server (which announces the outcome to the board group).
|
|
4
|
+
* Written by the supervisor after `/update`, read+deleted once on server boot.
|
|
5
|
+
*/
|
|
6
|
+
export interface UpdateResult {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
version?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function writeUpdateResult(r: UpdateResult): void;
|
|
12
|
+
/** Read the pending update result, deleting it so it announces exactly once. */
|
|
13
|
+
export declare function readAndClearUpdateResult(): UpdateResult | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { SHADOK_DIR } from "./config.js";
|
|
4
|
+
const RESULT_FILE = path.join(SHADOK_DIR, ".update-result");
|
|
5
|
+
export function writeUpdateResult(r) {
|
|
6
|
+
try {
|
|
7
|
+
fs.mkdirSync(SHADOK_DIR, { recursive: true });
|
|
8
|
+
fs.writeFileSync(RESULT_FILE, JSON.stringify(r));
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Non-fatal: worst case the update just isn't announced.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Read the pending update result, deleting it so it announces exactly once. */
|
|
15
|
+
export function readAndClearUpdateResult() {
|
|
16
|
+
let raw;
|
|
17
|
+
try {
|
|
18
|
+
raw = fs.readFileSync(RESULT_FILE, "utf8");
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
fs.rmSync(RESULT_FILE, { force: true });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* ignore */
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=update-flag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-flag.js","sourceRoot":"","sources":["../src/update-flag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAazC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;AAE5D,MAAM,UAAU,iBAAiB,CAAC,CAAe;IAC/C,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;IAC3D,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,wBAAwB;IACtC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACH,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server runs from a managed install at ~/.shadok-ai/app so that `/update`
|
|
3
|
+
* can refresh just the server (npm i shadok-ai@latest there) while the
|
|
4
|
+
* supervisor — launched from the npx cache — keeps running.
|
|
5
|
+
*/
|
|
6
|
+
export declare const APP_DIR: string;
|
|
7
|
+
/** Absolute path to the installed server entry point. */
|
|
8
|
+
export declare function serverEntry(): string;
|
|
9
|
+
/** Version of the currently installed managed server, or null if absent. */
|
|
10
|
+
export declare function installedVersion(): string | null;
|
|
11
|
+
/** Install the latest server; report the resolved version (or the failure). */
|
|
12
|
+
export declare function update(): Promise<{
|
|
13
|
+
ok: true;
|
|
14
|
+
version: string;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: string;
|
|
18
|
+
}>;
|
package/dist/updater.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { SHADOK_DIR } from "./config.js";
|
|
6
|
+
const pexec = promisify(execFile);
|
|
7
|
+
/**
|
|
8
|
+
* The server runs from a managed install at ~/.shadok-ai/app so that `/update`
|
|
9
|
+
* can refresh just the server (npm i shadok-ai@latest there) while the
|
|
10
|
+
* supervisor — launched from the npx cache — keeps running.
|
|
11
|
+
*/
|
|
12
|
+
export const APP_DIR = path.join(SHADOK_DIR, "app");
|
|
13
|
+
const PKG = "shadok-ai";
|
|
14
|
+
/** Absolute path to the installed server entry point. */
|
|
15
|
+
export function serverEntry() {
|
|
16
|
+
return path.join(APP_DIR, "node_modules", PKG, "dist", "server.js");
|
|
17
|
+
}
|
|
18
|
+
/** Version of the currently installed managed server, or null if absent. */
|
|
19
|
+
export function installedVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(APP_DIR, "node_modules", PKG, "package.json"), "utf8"));
|
|
22
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function npmInstallLatest() {
|
|
29
|
+
fs.mkdirSync(APP_DIR, { recursive: true });
|
|
30
|
+
// --prefix installs into APP_DIR/node_modules; no package.json needed there.
|
|
31
|
+
await pexec("npm", ["install", `${PKG}@latest`, "--prefix", APP_DIR, "--no-audit", "--no-fund"], {
|
|
32
|
+
timeout: 5 * 60_000,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Install the latest server; report the resolved version (or the failure). */
|
|
36
|
+
export async function update() {
|
|
37
|
+
try {
|
|
38
|
+
await npmInstallLatest();
|
|
39
|
+
return { ok: true, version: installedVersion() ?? "unknown" };
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
return { ok: false, error: e instanceof Error ? e.message.split("\n")[0] : String(e) };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=updater.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updater.js","sourceRoot":"","sources":["../src/updater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;AACpD,MAAM,GAAG,GAAG,WAAW,CAAC;AAExB,yDAAyD;AACzD,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;AACtE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACjF,CAAC;QACF,OAAO,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,gBAAgB;IAC7B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,6EAA6E;IAC7E,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,GAAG,GAAG,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,CAAC,EAAE;QAC/F,OAAO,EAAE,CAAC,GAAG,MAAM;KACpB,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,IAAI,CAAC;QACH,MAAM,gBAAgB,EAAE,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,IAAI,SAAS,EAAE,CAAC;IAChE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACzF,CAAC;AACH,CAAC"}
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage of a rolling rate-limit window, as returned by Claude Code's own
|
|
3
|
+
* `/usage` (endpoint GET /api/oauth/usage — a plain GET, it spends no quota).
|
|
4
|
+
*/
|
|
5
|
+
export interface Window {
|
|
6
|
+
/** 0–100. */
|
|
7
|
+
usedPercentage: number;
|
|
8
|
+
/** Unix epoch seconds when the window resets, or null. */
|
|
9
|
+
resetsAt: number | null;
|
|
10
|
+
}
|
|
11
|
+
export interface Usage {
|
|
12
|
+
fiveHour: Window | null;
|
|
13
|
+
sevenDay: Window | null;
|
|
14
|
+
/** When this snapshot was fetched (ms since epoch). */
|
|
15
|
+
fetchedAt: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseWindow(w: any): Window | null;
|
|
18
|
+
/**
|
|
19
|
+
* Fetches the current 5-hour and 7-day usage. Cached for 60s and
|
|
20
|
+
* de-duplicated across concurrent callers, so polling is cheap.
|
|
21
|
+
* Returns null if no token is available or the request fails.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getUsage(): Promise<Usage | null>;
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
6
|
+
/**
|
|
7
|
+
* Reads the Claude Code OAuth access token the same way the CLI does.
|
|
8
|
+
* Order: env override, then the credentials file, then the macOS keychain.
|
|
9
|
+
* Returns null if none is found (usage simply won't be shown).
|
|
10
|
+
*/
|
|
11
|
+
function readOAuthToken() {
|
|
12
|
+
const env = process.env.CLAUDE_CODE_OAUTH_TOKEN?.trim();
|
|
13
|
+
if (env)
|
|
14
|
+
return env;
|
|
15
|
+
// Credentials file (Linux, and some macOS setups).
|
|
16
|
+
const file = path.join(os.homedir(), ".claude", ".credentials.json");
|
|
17
|
+
try {
|
|
18
|
+
const j = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
19
|
+
const tok = j?.claudeAiOauth?.accessToken ?? j?.accessToken;
|
|
20
|
+
if (typeof tok === "string" && tok)
|
|
21
|
+
return tok;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// fall through to keychain
|
|
25
|
+
}
|
|
26
|
+
// macOS keychain (default on macOS): the CLI stores the token under the
|
|
27
|
+
// "Claude Code-credentials" generic-password service.
|
|
28
|
+
if (process.platform === "darwin") {
|
|
29
|
+
for (const account of [process.env.USER ?? "", ""]) {
|
|
30
|
+
try {
|
|
31
|
+
const out = execFileSync("security", ["find-generic-password", "-s", "Claude Code-credentials", ...(account ? ["-a", account] : []), "-w"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
32
|
+
if (!out)
|
|
33
|
+
continue;
|
|
34
|
+
// The stored blob is JSON; pull the access token out of it.
|
|
35
|
+
try {
|
|
36
|
+
const j = JSON.parse(out);
|
|
37
|
+
const tok = j?.claudeAiOauth?.accessToken ?? j?.accessToken;
|
|
38
|
+
if (typeof tok === "string" && tok)
|
|
39
|
+
return tok;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
if (out.startsWith("ey") || out.length > 40)
|
|
43
|
+
return out; // raw token
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// try next account form
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
export function parseWindow(w) {
|
|
54
|
+
if (!w)
|
|
55
|
+
return null;
|
|
56
|
+
// The endpoint returns `utilization` (0–100); older/statusline shapes use
|
|
57
|
+
// `used_percentage`. `resets_at` is an ISO string here (epoch seconds
|
|
58
|
+
// elsewhere).
|
|
59
|
+
const pct = typeof w.utilization === "number"
|
|
60
|
+
? w.utilization
|
|
61
|
+
: typeof w.used_percentage === "number"
|
|
62
|
+
? w.used_percentage
|
|
63
|
+
: null;
|
|
64
|
+
if (pct === null)
|
|
65
|
+
return null;
|
|
66
|
+
let resetsAt = null;
|
|
67
|
+
if (typeof w.resets_at === "number")
|
|
68
|
+
resetsAt = w.resets_at;
|
|
69
|
+
else if (typeof w.resets_at === "string") {
|
|
70
|
+
const t = Date.parse(w.resets_at);
|
|
71
|
+
if (!Number.isNaN(t))
|
|
72
|
+
resetsAt = Math.floor(t / 1000);
|
|
73
|
+
}
|
|
74
|
+
return { usedPercentage: pct, resetsAt };
|
|
75
|
+
}
|
|
76
|
+
let cache = null;
|
|
77
|
+
let inflight = null;
|
|
78
|
+
const TTL_MS = 60_000;
|
|
79
|
+
/**
|
|
80
|
+
* Fetches the current 5-hour and 7-day usage. Cached for 60s and
|
|
81
|
+
* de-duplicated across concurrent callers, so polling is cheap.
|
|
82
|
+
* Returns null if no token is available or the request fails.
|
|
83
|
+
*/
|
|
84
|
+
export async function getUsage() {
|
|
85
|
+
if (cache && Date.now() - cache.fetchedAt < TTL_MS)
|
|
86
|
+
return cache;
|
|
87
|
+
if (inflight)
|
|
88
|
+
return inflight;
|
|
89
|
+
inflight = (async () => {
|
|
90
|
+
const token = readOAuthToken();
|
|
91
|
+
if (!token)
|
|
92
|
+
return null;
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch(USAGE_URL, {
|
|
95
|
+
headers: {
|
|
96
|
+
Authorization: `Bearer ${token}`,
|
|
97
|
+
Accept: "application/json",
|
|
98
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
99
|
+
"User-Agent": "shadok-ai",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
if (!res.ok)
|
|
103
|
+
return cache; // keep last good snapshot on transient errors
|
|
104
|
+
const j = await res.json();
|
|
105
|
+
// The endpoint may nest the windows under rate_limits or expose them
|
|
106
|
+
// at the top level depending on version — handle both.
|
|
107
|
+
const src = j?.rate_limits ?? j ?? {};
|
|
108
|
+
const usage = {
|
|
109
|
+
fiveHour: parseWindow(src.five_hour),
|
|
110
|
+
sevenDay: parseWindow(src.seven_day),
|
|
111
|
+
fetchedAt: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
cache = usage;
|
|
114
|
+
return usage;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return cache;
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
inflight = null;
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
return inflight;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
|