shadok-ai 0.13.5 → 0.13.7

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 CHANGED
@@ -87,11 +87,20 @@ Enter to skip; you can add it later from the web UI).
87
87
  (e.g. forbid `git commit`), model, and which secrets to inject. Applied at
88
88
  spawn, remembered across resume.
89
89
  - **A model per agent** — the creation form picks the model this one agent runs
90
- on (Opus, Sonnet, Haiku, Fable), overriding its profile's, with a **long
91
- context (1M)** tick beside it. Leave it on *profile default* and nothing
92
- changes: no flag is sent at all. The choice is stored on the agent, so a
93
- resume or a restart keeps it, and the quota gauge meters it against the
94
- window it actually asked for. Eight roles ship with it: a lead that
90
+ on, overriding its profile's, with a **long context (1M)** tick beside it.
91
+ Pick a family (Opus, Sonnet, Haiku, Fable) and it follows Claude Code's
92
+ releases, or fold out **Pin an exact version** — that list is read from the
93
+ installed `claude` binary, so a model Anthropic ships appears on its own at
94
+ the next Claude Code update, with nothing to maintain here. Leave it on
95
+ *profile default* and nothing changes: no flag is sent at all. The choice is
96
+ stored on the agent, so a resume or a restart keeps it, and the quota gauge
97
+ meters it against the window it actually asked for.
98
+ - **A default model for the machine** — ⋯ → the version menu → *Default model*,
99
+ beside the permission mode. It writes Claude Code's own `settings.json`, which
100
+ is already the last step of the resolution chain (**agent → profile →
101
+ machine**), and applies at each agent's next spawn. Agents that carry no model
102
+ of their own simply follow Claude Code's default, which tracks the newest
103
+ model — so this is for **pinning** one, not for getting the latest. Eight roles ship with it: a lead that
95
104
  delegates, a dev, a QA agent that writes the failing test and cannot fix the
96
105
  code it covers, a release agent that prepares a deployment and leaves the
97
106
  trigger to you, a product agent that writes the spec instead of the change,
@@ -131,6 +140,15 @@ Enter to skip; you can add it later from the web UI).
131
140
  - **Engine room** — the raw TUI screen, live, with clickable keys, for anything
132
141
  the chat can't express. With tmux there is also an **experimental real
133
142
  terminal** (xterm.js over the pane's byte stream) when a snapshot isn't enough.
143
+ - **Files an agent hands you** — when an agent's result IS a file (a report, an
144
+ export, a screenshot, an archive) it arrives as a download card in the chat,
145
+ inline if it is an image, and as an upload in the Telegram topic. It goes
146
+ through shadok's own `shadok-files` skill rather than depending on the
147
+ harness's file tool, which is **not on every agent**: a long-lived agent keeps
148
+ the Claude Code binary it started with, so one older than that tool would
149
+ otherwise just print a local path you cannot open. **Artifacts are denied**
150
+ cockpit-wide for the same reason — a page on claude.ai is not the file you
151
+ asked for (`SHADOK_ALLOW_ARTIFACTS=1` lifts it).
134
152
  - **Diff panel** — what an agent actually changed, against its base.
135
153
  - **What an agent left running, on its tab** — a turning ⏳ for background
136
154
  shells, a still 👁 for monitors, each with its count past one. Claude Code
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: shadok-files
3
+ description: Hand a file to the person you are talking to — a report, an export, a screenshot, a built artifact. Use whenever your result IS a file. They are in a browser or on Telegram, NOT on this machine, so a local path is a string they cannot open.
4
+ ---
5
+
6
+ # shadok-files
7
+
8
+ The person reading you is in a web cockpit or on Telegram. **They are not on
9
+ this machine.** Printing `/workspace/report.pdf` gives them a string they cannot
10
+ click, cannot open and cannot save. Hand the file over instead:
11
+
12
+ ```bash
13
+ node ~/.claude/skills/shadok-files/scripts/send.mjs /abs/path/report.pdf
14
+ node ~/.claude/skills/shadok-files/scripts/send.mjs a.png b.png --caption "before / after"
15
+ ```
16
+
17
+ It appears in the chat as a download card — inline if it is an image — and is
18
+ uploaded into the Telegram topic when the agent is mirrored.
19
+
20
+ ## When to use it, and when not
21
+
22
+ **Use it when the file IS the deliverable**: a report you generated, an export,
23
+ a screenshot, a diagram, an archive, a built binary.
24
+
25
+ **Do not use it for a path you are merely citing.** `src/server.ts:412` in a
26
+ review is a reference, not a deliverable — attaching the file there is noise.
27
+
28
+ **Do not publish an artifact instead.** An artifact is a page on claude.ai; the
29
+ person asked for a file. Several shipped roles deny the tool outright.
30
+
31
+ **Do not send the same file again** unless it has meaningfully changed. Each
32
+ call renders a card, and a row of cards for one file is noise.
33
+
34
+ ## Paths
35
+
36
+ Give an **absolute** path. The server's working directory is its launch
37
+ directory and never yours, so a relative path would resolve somewhere else —
38
+ the script resolves it against your cwd before sending, but being explicit
39
+ costs nothing and removes the question.
40
+
41
+ ## If something is missing
42
+
43
+ If this skill is not installed, or the call fails, **say so plainly and stop**.
44
+ Do not invent a replacement convention — a `fichier: /path/to/x` line of your
45
+ own making looks like a feature to the person reading it, and is not one. Ask
46
+ them to check the cockpit instead.
47
+
48
+ ## Authenticating a direct API call
49
+
50
+ The command above does this for you. If you call the cockpit's API yourself,
51
+ send your session key — it is in your environment and it does not expire:
52
+
53
+ ```bash
54
+ curl -sS -X POST -H "content-type: application/json" \
55
+ -H "x-shadok-session-key: $SHADOK_SESSION_KEY" \
56
+ -d '{"paths":["/abs/path/report.pdf"],"caption":"the report"}' \
57
+ "http://127.0.0.1:$SHADOK_PORT/files"
58
+ ```
59
+
60
+ `$SHADOK_AUTH` (a cookie) is still accepted elsewhere, but it was frozen into
61
+ this process's environment at spawn and expires after a week, so never rely on
62
+ it alone. A `401` or `403` means that header is missing — or that you were
63
+ started before this existed, in which case nothing you hold can authenticate
64
+ any more and only a human can fix it: ask them for *Reload agent* on your ⋯
65
+ menu.
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ // Hand a file to the person driving this cockpit.
3
+ //
4
+ // Deliberately a script and not the harness's SendUserFile tool: a tmux agent
5
+ // keeps the Claude Code binary it was spawned with, so an agent older than that
6
+ // tool never sees it — and the whole fleet runs pre-upgrade binaries by design.
7
+ // An HTTP call works on every build, including the ones already running.
8
+ import path from "node:path";
9
+
10
+ const args = process.argv.slice(2);
11
+ const die = (m) => { console.error(m); process.exit(1); };
12
+
13
+ let caption;
14
+ const paths = [];
15
+ for (let i = 0; i < args.length; i++) {
16
+ if (args[i] === "--caption" || args[i] === "-c") caption = args[++i];
17
+ else if (args[i] === "--help" || args[i] === "-h")
18
+ die("usage: send.mjs <file...> [--caption \"…\"]");
19
+ else paths.push(args[i]);
20
+ }
21
+ if (!paths.length) die("usage: send.mjs <file...> [--caption \"…\"]");
22
+
23
+ const port = process.env.SHADOK_PORT;
24
+ const key = process.env.SHADOK_SESSION_KEY;
25
+ if (!port) die("Not inside a shadok-ai agent (SHADOK_PORT unset).");
26
+ // Said plainly rather than worked around: an agent that cannot authenticate
27
+ // must tell the human, not invent its own way of surfacing a file.
28
+ if (!key) die("SHADOK_SESSION_KEY is unset — ask the human to reload this agent.");
29
+
30
+ // Resolved HERE, where the cwd is the agent's. The server's cwd is its launch
31
+ // directory and never this one, so a relative path sent as-is would point
32
+ // somewhere else and only ever work by coincidence.
33
+ const abs = paths.map((p) => path.resolve(p));
34
+
35
+ let res;
36
+ try {
37
+ res = await fetch(`http://127.0.0.1:${port}/files`, {
38
+ method: "POST",
39
+ headers: { "content-type": "application/json", "x-shadok-session-key": key },
40
+ body: JSON.stringify({ paths: abs, ...(caption ? { caption } : {}) }),
41
+ signal: AbortSignal.timeout(30_000),
42
+ });
43
+ } catch (e) {
44
+ die(`The cockpit did not answer (${e?.message ?? e}). It may be restarting — say so rather than working around it.`);
45
+ }
46
+
47
+ const body = await res.json().catch(() => ({}));
48
+ if (!res.ok) die(`Refused (${res.status}): ${body.error ?? "no reason given"}`);
49
+ for (const f of body.sent ?? []) console.log(`sent: ${f.name} (${f.size} bytes)`);
50
+ // A partial success is reported, never swallowed: the agent has to know WHICH
51
+ // file did not make it.
52
+ for (const r of body.refused ?? []) console.error(`not sent: ${r}`);
@@ -23,6 +23,17 @@ Claude Code sessions in parallel. A human pilots you from a browser chat
23
23
  Avoid very wide previews in the options.
24
24
  - The user may be on a phone: conclusion first, compact responses, no long
25
25
  tables where a list would do.
26
+ - **Handing over a file.** The person reading you is in a browser or on
27
+ Telegram — **not on this machine**. A local path is a string they cannot open,
28
+ so when your result IS a file (a report, an export, a screenshot, an archive)
29
+ hand it over: `node ~/.claude/skills/shadok-files/scripts/send.mjs <abs path>`.
30
+ It becomes a download card in the chat and an upload in the Telegram topic.
31
+ Citing a path stays right when the path is the point (`src/foo.ts:12` in a
32
+ review); attach when the file is the deliverable. **Do not publish an artifact
33
+ instead** — that is a page on claude.ai, not the file they asked for, and
34
+ several roles deny the tool outright. And if a capability is missing, say so
35
+ and stop: do not invent a replacement convention, because a `fichier: /path`
36
+ line of your own making looks like a feature to the reader and is not one.
26
37
  - **Saying nothing.** You always have to reply *something*, but a scheduled
27
38
  run that found no signal should not ping anyone. Reply with exactly
28
39
  `NOTHING TO SHOW` (the whole message, nothing else) and the cockpit drops
@@ -64,3 +64,22 @@ export declare function ensureClaudeHome(): void;
64
64
  * for every agent, therefore a brand-new trust dialog every time.
65
65
  */
66
66
  export declare function ensureProjectTrusted(cwd: string): void;
67
+ /**
68
+ * The machine-wide default model, read from and written to the SAME
69
+ * `~/.claude/settings.json` this module already owns.
70
+ *
71
+ * Deliberately here and not in a module of its own: `seedSettings` already
72
+ * writes that file, and two writers on one JSON file is how a `tui` seeded at
73
+ * boot and a model written from the GUI would eventually erase each other. One
74
+ * owner, one atomic write.
75
+ *
76
+ * Verified 2026-09-25 against claude 2.1.282, because the whole feature rests
77
+ * on it: with `model` in that file and NO `--model` flag, the run used
78
+ * `claude-haiku-4-5-20251001`; and it is still honoured when shadok also passes
79
+ * `--settings` for a profile's guardrails, so the flag MERGES rather than
80
+ * replacing the file. Without that second check the setting would have been
81
+ * silently inert for every agent that carries a profile.
82
+ */
83
+ export declare function getDefaultModel(): string | null;
84
+ /** Write (or clear, with null) that default. Returns false if it could not. */
85
+ export declare function setDefaultModel(model: string | null): boolean;
@@ -210,4 +210,58 @@ function seedSettings() {
210
210
  export function ensureProjectTrusted(cwd) {
211
211
  seed(cwd);
212
212
  }
213
+ /**
214
+ * The machine-wide default model, read from and written to the SAME
215
+ * `~/.claude/settings.json` this module already owns.
216
+ *
217
+ * Deliberately here and not in a module of its own: `seedSettings` already
218
+ * writes that file, and two writers on one JSON file is how a `tui` seeded at
219
+ * boot and a model written from the GUI would eventually erase each other. One
220
+ * owner, one atomic write.
221
+ *
222
+ * Verified 2026-09-25 against claude 2.1.282, because the whole feature rests
223
+ * on it: with `model` in that file and NO `--model` flag, the run used
224
+ * `claude-haiku-4-5-20251001`; and it is still honoured when shadok also passes
225
+ * `--settings` for a profile's guardrails, so the flag MERGES rather than
226
+ * replacing the file. Without that second check the setting would have been
227
+ * silently inert for every agent that carries a profile.
228
+ */
229
+ export function getDefaultModel() {
230
+ try {
231
+ const raw = fs.readFileSync(settingsFile(), "utf8");
232
+ const m = JSON.parse(raw)?.model;
233
+ return typeof m === "string" && m.trim() ? m.trim() : null;
234
+ }
235
+ catch {
236
+ return null;
237
+ }
238
+ }
239
+ /** Write (or clear, with null) that default. Returns false if it could not. */
240
+ export function setDefaultModel(model) {
241
+ try {
242
+ const file = settingsFile();
243
+ let existing = {};
244
+ if (fs.existsSync(file)) {
245
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
246
+ // Hand-edited into invalid JSON, or not an object: leave it ALONE rather
247
+ // than "repair" it — same rule as `seed`, and for the same reason.
248
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
249
+ return false;
250
+ existing = parsed;
251
+ }
252
+ const next = { ...existing };
253
+ if (model?.trim())
254
+ next.model = model.trim();
255
+ else
256
+ delete next.model;
257
+ fs.mkdirSync(path.dirname(file), { recursive: true });
258
+ const tmp = `${file}.shadok-${process.pid}.tmp`;
259
+ fs.writeFileSync(tmp, JSON.stringify(next, null, 2));
260
+ fs.renameSync(tmp, file);
261
+ return true;
262
+ }
263
+ catch {
264
+ return false;
265
+ }
266
+ }
213
267
  //# sourceMappingURL=claude-home.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-home.js","sourceRoot":"","sources":["../src/claude-home.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude-home.js","sourceRoot":"","sources":["../src/claude-home.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Which models THIS Claude Code build supports, read out of the binary.
3
+ *
4
+ * The alternative was a hardcoded list, and it is the alternative that rots:
5
+ * the profile panel still suggests `claude-opus-4-8`, current the day it was
6
+ * written and stale now, and `claude-opus-5-5` shipped without shadok noticing.
7
+ * The CLI has no command that enumerates models (`claude models` is not a
8
+ * subcommand — it is taken as a PROMPT and answered, which is its own trap), so
9
+ * the names are taken from where they actually live: the binary's own strings.
10
+ *
11
+ * Read, never executed, and never trusted blindly — see `parseModelNames`.
12
+ */
13
+ /** Families the CLI accepts as aliases. An alias is always offered, scan or no scan. */
14
+ export declare const MODEL_FAMILIES: readonly ["opus", "sonnet", "haiku", "fable"];
15
+ /**
16
+ * Pure: the model names in a blob of binary strings, conservatively.
17
+ *
18
+ * DELIBERATELY NARROW, and the narrowness is the point. A `strings` dump glues
19
+ * adjacent bytes together, which mints plausible-looking rubbish: this binary
20
+ * contains `claude-haiku-3-55` (a `3-5` with the next byte stuck to it) and
21
+ * `claude-fable-5-mythos-5`. Offering those as choices would produce an agent
22
+ * that dies at spawn on a model nobody can find. So only a plain
23
+ * `claude-<family>-<major>[-<minor>]` is kept, with a single-digit minor.
24
+ *
25
+ * Dropped on purpose:
26
+ * - `-v1` suffixes and 8-digit dated forms (`claude-opus-4-5-20251101`): the
27
+ * same model spelled longer, and three spellings of one model in a picker is
28
+ * noise, not choice.
29
+ * - anything with a non-numeric segment, which is where the glue artifacts are.
30
+ *
31
+ * KNOWN LIMIT, written down rather than discovered later: a two-digit minor
32
+ * (`claude-opus-5-10`) would be skipped, because admitting two digits is what
33
+ * lets `3-55` back in. The family row (`opus`, `sonnet`…) never depends on this
34
+ * scan, so a missed name costs discoverability, never the ability to run it.
35
+ */
36
+ export declare function parseModelNames(text: string): string[];
37
+ /** Pure: family, then newest version first — the order a picker should show. */
38
+ export declare function rankModels(names: readonly string[]): string[];
39
+ export interface ModelCache {
40
+ /** Identity of the binary these names came from. */
41
+ bin: string;
42
+ size: number;
43
+ mtimeMs: number;
44
+ models: string[];
45
+ }
46
+ /** Pure: is a cache entry still about the binary on disk right now? */
47
+ export declare function cacheIsFresh(cache: ModelCache | null, stat: {
48
+ size: number;
49
+ mtimeMs: number;
50
+ } | null, bin: string): boolean;
51
+ /**
52
+ * Scan the binary. Returns [] on ANY failure — a mid-upgrade binary is the
53
+ * normal case here, not the exception (invariant 32): the launcher is unlinked
54
+ * and relinked on every claude-code update, so a scan can meet ETXTBSY, a
55
+ * 500-byte placeholder, or nothing at all. An empty result is never cached, so
56
+ * the next boot simply tries again.
57
+ */
58
+ export declare function scanModels(bin: string): string[];
59
+ /**
60
+ * The models this build supports, cached per binary identity.
61
+ *
62
+ * One scan per claude-code upgrade, not per call: the cache key is the
63
+ * binary's path, size and mtime, exactly the trio that changes when it is
64
+ * replaced. `force` re-reads without touching the memo's contract.
65
+ */
66
+ export declare function availableModels(bin: string, force?: boolean): string[];
67
+ /** Test seam: forget the in-process memo. */
68
+ export declare function resetModelMemo(): void;
@@ -0,0 +1,181 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ /**
5
+ * Which models THIS Claude Code build supports, read out of the binary.
6
+ *
7
+ * The alternative was a hardcoded list, and it is the alternative that rots:
8
+ * the profile panel still suggests `claude-opus-4-8`, current the day it was
9
+ * written and stale now, and `claude-opus-5-5` shipped without shadok noticing.
10
+ * The CLI has no command that enumerates models (`claude models` is not a
11
+ * subcommand — it is taken as a PROMPT and answered, which is its own trap), so
12
+ * the names are taken from where they actually live: the binary's own strings.
13
+ *
14
+ * Read, never executed, and never trusted blindly — see `parseModelNames`.
15
+ */
16
+ /** Families the CLI accepts as aliases. An alias is always offered, scan or no scan. */
17
+ export const MODEL_FAMILIES = ["opus", "sonnet", "haiku", "fable"];
18
+ /**
19
+ * Pure: the model names in a blob of binary strings, conservatively.
20
+ *
21
+ * DELIBERATELY NARROW, and the narrowness is the point. A `strings` dump glues
22
+ * adjacent bytes together, which mints plausible-looking rubbish: this binary
23
+ * contains `claude-haiku-3-55` (a `3-5` with the next byte stuck to it) and
24
+ * `claude-fable-5-mythos-5`. Offering those as choices would produce an agent
25
+ * that dies at spawn on a model nobody can find. So only a plain
26
+ * `claude-<family>-<major>[-<minor>]` is kept, with a single-digit minor.
27
+ *
28
+ * Dropped on purpose:
29
+ * - `-v1` suffixes and 8-digit dated forms (`claude-opus-4-5-20251101`): the
30
+ * same model spelled longer, and three spellings of one model in a picker is
31
+ * noise, not choice.
32
+ * - anything with a non-numeric segment, which is where the glue artifacts are.
33
+ *
34
+ * KNOWN LIMIT, written down rather than discovered later: a two-digit minor
35
+ * (`claude-opus-5-10`) would be skipped, because admitting two digits is what
36
+ * lets `3-55` back in. The family row (`opus`, `sonnet`…) never depends on this
37
+ * scan, so a missed name costs discoverability, never the ability to run it.
38
+ */
39
+ export function parseModelNames(text) {
40
+ const fam = MODEL_FAMILIES.join("|");
41
+ const re = new RegExp(`claude-(?:${fam})-\\d{1,2}(?:-\\d)?(?![\\w-])`, "g");
42
+ return [...new Set(text.match(re) ?? [])];
43
+ }
44
+ /** Pure: family, then newest version first — the order a picker should show. */
45
+ export function rankModels(names) {
46
+ const key = (n) => {
47
+ const m = /^claude-([a-z]+)-(\d{1,2})(?:-(\d))?$/.exec(n);
48
+ if (!m)
49
+ return null;
50
+ return { family: m[1], major: Number(m[2]), minor: m[3] ? Number(m[3]) : 0 };
51
+ };
52
+ return [...names]
53
+ .filter((n) => key(n))
54
+ .sort((a, b) => {
55
+ const x = key(a), y = key(b);
56
+ const fx = MODEL_FAMILIES.indexOf(x.family);
57
+ const fy = MODEL_FAMILIES.indexOf(y.family);
58
+ if (fx !== fy)
59
+ return fx - fy;
60
+ if (x.major !== y.major)
61
+ return y.major - x.major;
62
+ return y.minor - x.minor;
63
+ });
64
+ }
65
+ /** Pure: is a cache entry still about the binary on disk right now? */
66
+ export function cacheIsFresh(cache, stat, bin) {
67
+ if (!cache || !stat)
68
+ return false;
69
+ return cache.bin === bin && cache.size === stat.size && cache.mtimeMs === stat.mtimeMs;
70
+ }
71
+ const cacheFile = () => path.join(os.homedir(), ".shadok-ai", "models.json");
72
+ /** How much of a ~240MB binary we are willing to read in one pass. */
73
+ const CHUNK = 1 << 22;
74
+ /** Names can straddle a chunk boundary; carry this much across. */
75
+ const OVERLAP = 64;
76
+ /**
77
+ * Scan the binary. Returns [] on ANY failure — a mid-upgrade binary is the
78
+ * normal case here, not the exception (invariant 32): the launcher is unlinked
79
+ * and relinked on every claude-code update, so a scan can meet ETXTBSY, a
80
+ * 500-byte placeholder, or nothing at all. An empty result is never cached, so
81
+ * the next boot simply tries again.
82
+ */
83
+ export function scanModels(bin) {
84
+ let fd = null;
85
+ try {
86
+ fd = fs.openSync(bin, "r");
87
+ const buf = Buffer.allocUnsafe(CHUNK);
88
+ let carry = "";
89
+ const found = new Set();
90
+ for (;;) {
91
+ const n = fs.readSync(fd, buf, 0, CHUNK, null);
92
+ if (n <= 0)
93
+ break;
94
+ // latin1: every byte maps to one char, so offsets stay sane and an
95
+ // invalid UTF-8 run cannot swallow a name next to it.
96
+ const text = carry + buf.toString("latin1", 0, n);
97
+ for (const m of parseModelNames(text))
98
+ found.add(m);
99
+ carry = text.slice(-OVERLAP);
100
+ }
101
+ return rankModels([...found]);
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ finally {
107
+ if (fd !== null)
108
+ try {
109
+ fs.closeSync(fd);
110
+ }
111
+ catch { /* already gone */ }
112
+ }
113
+ }
114
+ function readCache() {
115
+ try {
116
+ const j = JSON.parse(fs.readFileSync(cacheFile(), "utf8"));
117
+ return Array.isArray(j?.models) ? j : null;
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ function writeCache(c) {
124
+ try {
125
+ const f = cacheFile();
126
+ fs.mkdirSync(path.dirname(f), { recursive: true });
127
+ const tmp = `${f}.shadok-${process.pid}.tmp`;
128
+ fs.writeFileSync(tmp, JSON.stringify(c, null, 2));
129
+ fs.renameSync(tmp, f);
130
+ }
131
+ catch {
132
+ /* a cache we cannot write just means we scan again next boot */
133
+ }
134
+ }
135
+ let memo = null;
136
+ /**
137
+ * The models this build supports, cached per binary identity.
138
+ *
139
+ * One scan per claude-code upgrade, not per call: the cache key is the
140
+ * binary's path, size and mtime, exactly the trio that changes when it is
141
+ * replaced. `force` re-reads without touching the memo's contract.
142
+ */
143
+ export function availableModels(bin, force = false) {
144
+ // `memo.length`, NOT `memo`: an empty array is truthy, so memoising a failed
145
+ // scan would freeze the picker family-only for the life of the process — and
146
+ // a failed scan is the NORMAL case here, since the binary is unlinked and
147
+ // relinked on every claude-code upgrade (invariant 32). Same rule as the
148
+ // file cache below: an empty result is a "could not look", never an answer.
149
+ if (memo?.length && !force)
150
+ return memo;
151
+ let stat = null;
152
+ try {
153
+ const s = fs.statSync(bin);
154
+ stat = { size: s.size, mtimeMs: s.mtimeMs };
155
+ }
156
+ catch {
157
+ stat = null;
158
+ }
159
+ const cached = readCache();
160
+ if (!force && cacheIsFresh(cached, stat, bin))
161
+ return (memo = cached.models);
162
+ const models = scanModels(bin);
163
+ // NEVER cache an empty result: that is what a mid-upgrade binary produces,
164
+ // and freezing it would leave the picker permanently family-only.
165
+ if (models.length && stat)
166
+ writeCache({ bin, ...stat, models });
167
+ // Falling back to the cache is what carries the picker through an upgrade
168
+ // window — but only for the SAME binary. Without the path check, an entry
169
+ // written for one launcher answered a question about another, which is a
170
+ // cache bleeding across identities rather than a stale-but-useful answer.
171
+ const stale = cached && cached.bin === bin ? cached.models : [];
172
+ memo = models.length ? models : stale;
173
+ // `memo` may legitimately be [] here; the `memo?.length` guard above is what
174
+ // stops that from hardening into a permanent answer.
175
+ return memo;
176
+ }
177
+ /** Test seam: forget the in-process memo. */
178
+ export function resetModelMemo() {
179
+ memo = null;
180
+ }
181
+ //# sourceMappingURL=claude-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-models.js","sourceRoot":"","sources":["../src/claude-models.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import { type FileCard } from "./download.js";
2
+ /**
3
+ * Files an agent OFFERS the user through shadok's own path, rather than through
4
+ * the harness's `SendUserFile` tool.
5
+ *
6
+ * Why this exists next to a tool that already does it: a tmux agent keeps the
7
+ * Claude Code binary it was spawned with — that is what lets it survive an
8
+ * auto-update — so an agent older than the tool never sees it, and the whole
9
+ * fleet runs pre-upgrade binaries by design (measured: 67 panes of 67). One
10
+ * instance answered "cet outil n'existe pas dans ma session" and invented a
11
+ * `fichier: /path` line of its own, which looks like a feature without being
12
+ * one. A skill calling this endpoint works on any build, including the ones
13
+ * already running.
14
+ *
15
+ * This is a registry of OFFERS, not a copy: the bytes stay where the agent
16
+ * wrote them, and the row records that this session chose to hand them over.
17
+ *
18
+ * See docs/superpowers/specs/2026-09-25-file-handover-design.md.
19
+ */
20
+ /**
21
+ * The `stream-tool` name a shadok-side delivery travels under.
22
+ *
23
+ * Deliberately NOT "SendUserFile": the two reach the same card, but one is a
24
+ * harness tool call recorded in the transcript and the other is an HTTP offer,
25
+ * and spelling them alike would make every later `grep SendUserFile` lie about
26
+ * where a file came from. The consumers accept both names; nothing else about
27
+ * the message differs, which is why no new client or Telegram rendering was
28
+ * needed for this feature.
29
+ */
30
+ export declare const OFFER_TOOL = "ShadokFile";
31
+ export interface OfferedFile {
32
+ sessionId: string;
33
+ path: string;
34
+ name: string;
35
+ size: number;
36
+ /** ms epoch of the offer. */
37
+ at: number;
38
+ caption?: string;
39
+ }
40
+ /** Biggest file we will register. Beyond this the agent is told, not ignored. */
41
+ export declare const MAX_OFFER_BYTES: number;
42
+ /** How many offers we keep per instance — a bound, like the ledger's. */
43
+ export declare const MAX_OFFERS = 500;
44
+ export type OfferRefusal = {
45
+ ok: false;
46
+ reason: "not-absolute" | "missing" | "not-a-file" | "too-big" | "unreadable";
47
+ path: string;
48
+ detail?: string;
49
+ };
50
+ export type OfferVerdict = {
51
+ ok: true;
52
+ file: OfferedFile;
53
+ } | OfferRefusal;
54
+ /**
55
+ * Pure: may this path be offered, given what the filesystem says about it?
56
+ *
57
+ * `stat` is injected so the decision is testable without touching a disk — the
58
+ * same shape as `classifyBin` and `readGround`. "missing" and "unreadable" are
59
+ * kept APART rather than collapsed into one failure: they send the agent to
60
+ * different places (it wrote the wrong path, versus the server cannot see a
61
+ * path that is really there), and telling it the wrong one costs a round trip.
62
+ * Invariant 27's discipline applied to a much smaller thing.
63
+ */
64
+ export type OfferStat = {
65
+ isFile: boolean;
66
+ size: number;
67
+ } | "missing" | "unreadable";
68
+ export declare function offerVerdict(sessionId: string, p: string, stat: OfferStat, now: number, caption?: string): OfferVerdict;
69
+ /**
70
+ * Pure: the table after an offer, newest last, bounded and de-duplicated.
71
+ *
72
+ * Re-offering the same path from the same session REPLACES the row rather than
73
+ * appending one: an agent that regenerates a report and sends it again should
74
+ * leave one card's worth of state, not a log. Same "supersede, not append" rule
75
+ * the ledger learned the hard way.
76
+ */
77
+ export declare function withOffer(rows: readonly OfferedFile[], f: OfferedFile): OfferedFile[];
78
+ /**
79
+ * Pure: every path this SESSION has offered.
80
+ *
81
+ * The read-side gate, and the twin of `sentFilePaths`: `GET /download` serves a
82
+ * path only when it is in one of the two, so the endpoint still cannot be walked
83
+ * into an arbitrary file — it serves back exactly what an agent chose to hand
84
+ * over, and only to that agent's own channel.
85
+ */
86
+ export declare function offeredPaths(rows: readonly OfferedFile[], sessionId: string): Set<string>;
87
+ /** Pure: the cards for an offer, the shape the client and Telegram already take. */
88
+ export declare function offerCards(files: readonly OfferedFile[]): FileCard[];
89
+ /** Pure: a refusal, worded for the agent that will read it. */
90
+ export declare function refusalMessage(r: OfferRefusal): string;
91
+ export declare const filesFileFor: (cwd: string) => string;
92
+ export declare function loadOffers(file: string): OfferedFile[];
93
+ export declare function saveOffers(file: string, rows: readonly OfferedFile[]): void;
94
+ /** Stat for `offerVerdict`, keeping absent and unreadable apart. */
95
+ export declare function statForOffer(p: string): OfferStat;