shadok-ai 0.9.0 → 0.9.2

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
@@ -354,7 +354,7 @@ machine, which silently shifts every daily prompt on a server running in UTC.
354
354
  | `SHADOK_PERMISSION_MODE` | mode new agents start in (default `acceptEdits`) |
355
355
  | `SHADOK_AUTOUPDATE` | fallback only — the GUI setting wins once used |
356
356
  | `SHADOK_PILOT_PROMPT=0` | don't inject the cockpit system prompt |
357
- | `SHADOK_LEDGER=1` | opt into the shared-ledger reflex (OFF by default; the GUI/config setting wins once used) |
357
+ | `SHADOK_LEDGER=1` | opt into the shared-ledger reflex — a per-instance state table siblings read/write so they stop re-surfacing resolved work, plus a `⟦ledger⟧` delta pushed ahead of each message (OFF by default; the GUI/config setting wins once used) |
358
358
  | `SHADOK_RESUME_SUMMARY=1` | don't auto-answer the resume-from-summary prompt |
359
359
  | `SHADOK_SSH_IDENTITY=0` · `SHADOK_FORCE_SSH_IDENTITY=1` | disable / force the Docker SSH identity |
360
360
  | `TELEGRAM_BOT_TOKEN` · `TELEGRAM_ALLOWED_CHATS` | override the stored config |
@@ -16,9 +16,18 @@ Then let it steer you:
16
16
  ("de mémoire, à faire — dis-moi si c'est déjà réglé"), don't assert;
17
17
  - a **stale** record (days old) → confirm before acting.
18
18
 
19
+ **You are also pushed the changes.** Before each user message you may receive a
20
+ `⟦ledger · N updates since your last message⟧` block listing what sibling agents
21
+ resolved or decided since you last heard — each line `• [id] entity — status`.
22
+ Treat it as current status (don't re-raise what it shows resolved). It is the
23
+ push half; `check` is still there for older or specific items the block didn't
24
+ carry.
25
+
19
26
  When you **resolve, decide, or change** something notable, record it so the next
20
27
  agent doesn't redo it:
21
28
  `node ~/.claude/skills/shadok-ledger/ledger.mjs record --entity "<name>" --status <resolved|open|in-progress|decided> --note "<line>" --source "<PR#/who>"`.
29
+ To update an item you saw in the block, quote its handle instead of retyping the
30
+ name: `… record --id <id> --status resolved --note "<line>"`.
22
31
 
23
32
  This gates **status** claims only. A durable lesson or constraint (not a status)
24
33
  is used freely.
@@ -46,6 +46,15 @@ node ~/.claude/skills/shadok-ledger/ledger.mjs record \
46
46
  Re-recording the same entity **supersedes** its row (no duplicates). Log facts —
47
47
  resolutions, decisions, state changes, launched actions — never chatter.
48
48
 
49
+ Every row has a short **id** (shown as `[id]` in `check`/`list` and in the ledger
50
+ block pushed ahead of your messages). To update a row you already saw, quote its
51
+ id instead of retyping the name — no risk of a typo forking a second row:
52
+
53
+ ```
54
+ node ~/.claude/skills/shadok-ledger/ledger.mjs record \
55
+ --id <id> --status resolved --note "<one line>"
56
+ ```
57
+
49
58
  ## To consult the whole table
50
59
 
51
60
  ```
@@ -12,13 +12,52 @@ export function normEntity(s) {
12
12
  }
13
13
 
14
14
  /**
15
- * Upsert by normalised entitySUPERSEDE the existing row, never append a twin.
16
- * `now` (epoch ms) is injected so the core stays pure/testable.
15
+ * Resolve a short idexact match, else a UNIQUE prefix — to its row, or null.
16
+ * Empty and ambiguous both resolve to null (never guess), the same rule the cron
17
+ * ids follow (invariant 17). The id is a durable HANDLE an agent can quote from
18
+ * the pushed ledger block to update a row without retyping its (long) entity.
17
19
  */
18
- export function upsertEntry(entries, patch, now) {
20
+ export function resolveId(entries, idOrPrefix) {
21
+ const q = String(idOrPrefix ?? "").trim().toLowerCase();
22
+ if (!q) return null;
23
+ const exact = entries.find((e) => e.id === q);
24
+ if (exact) return exact;
25
+ const pre = entries.filter((e) => typeof e.id === "string" && e.id.startsWith(q));
26
+ return pre.length === 1 ? pre[0] : null;
27
+ }
28
+
29
+ /**
30
+ * Upsert a row, two ways:
31
+ * - `patch.id` present → UPDATE that exact row in place (resolved via resolveId),
32
+ * keeping its id AND its entity (a rename only if `patch.entity` is given).
33
+ * An unknown id THROWS — an update must never silently spawn a new row.
34
+ * - else → upsert by normalised entity: supersede the existing twin, PRESERVING
35
+ * its id (the handle is durable across every write), or create a new row with
36
+ * `newId`.
37
+ * `now` (epoch ms) and `newId` are injected so the core stays pure/testable.
38
+ */
39
+ export function upsertEntry(entries, patch, now, newId) {
40
+ if (patch.id != null && String(patch.id).trim()) {
41
+ const cur = resolveId(entries, patch.id);
42
+ if (!cur) throw new Error(`no ledger entry with id "${patch.id}"`);
43
+ const row = {
44
+ id: cur.id,
45
+ entity:
46
+ patch.entity != null && String(patch.entity).trim() ? String(patch.entity).trim() : cur.entity,
47
+ status:
48
+ patch.status != null && String(patch.status).trim() ? String(patch.status).trim() : cur.status,
49
+ ...((patch.note ?? cur.note) ? { note: String(patch.note ?? cur.note).trim() } : {}),
50
+ ...((patch.source ?? cur.source) ? { source: String(patch.source ?? cur.source).trim() } : {}),
51
+ updatedAt: now,
52
+ };
53
+ return [...entries.filter((e) => e.id !== cur.id), row];
54
+ }
55
+
19
56
  const key = normEntity(patch.entity);
20
57
  if (!key) throw new Error("entity is required");
58
+ const existing = entries.find((e) => normEntity(e.entity) === key);
21
59
  const row = {
60
+ id: existing?.id ?? newId,
22
61
  entity: String(patch.entity).trim(),
23
62
  status: String(patch.status ?? "").trim(),
24
63
  ...(patch.note ? { note: String(patch.note).trim() } : {}),
@@ -29,14 +68,17 @@ export function upsertEntry(entries, patch, now) {
29
68
  }
30
69
 
31
70
  /**
32
- * Rows whose entity OR note contains the query (case-insensitive), most-recent
33
- * first. An empty query returns the whole table (for `list`).
71
+ * Rows whose id, entity OR note contains the query (case-insensitive), most-
72
+ * recent first. An empty query returns the whole table (for `list`).
34
73
  */
35
74
  export function findEntries(entries, query) {
36
75
  const q = normEntity(query);
37
76
  const rows = q
38
77
  ? entries.filter(
39
- (e) => normEntity(e.entity).includes(q) || normEntity(e.note ?? "").includes(q),
78
+ (e) =>
79
+ String(e.id ?? "").toLowerCase().includes(q) ||
80
+ normEntity(e.entity).includes(q) ||
81
+ normEntity(e.note ?? "").includes(q),
40
82
  )
41
83
  : entries.slice();
42
84
  return rows.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
@@ -1,13 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  // shadok-ledger CLI — a tiny state table so agents verify a status before they
3
- // assert or act on it. Store: ~/.shadok-ai/ledger.json (per instance, NOT the
4
- // repo). See SKILL.md and the design spec. No server involved.
3
+ // assert or act on it, and record what they resolve/decide.
4
+ //
5
+ // Store: the PER-INSTANCE ledger. The server hands each agent the exact path in
6
+ // SHADOK_LEDGER_FILE at spawn (an agent's own cwd is a worktree, not the launch
7
+ // dir, so it cannot derive it); a hand-run CLI with no env falls back to the
8
+ // legacy global file. See SKILL.md and the design spec. No server involved.
5
9
  import fs from "node:fs";
6
10
  import os from "node:os";
7
11
  import path from "node:path";
8
- import { upsertEntry, findEntries, ageDays } from "./ledger-core.mjs";
12
+ import crypto from "node:crypto";
13
+ import { upsertEntry, findEntries, ageDays, resolveId, normEntity } from "./ledger-core.mjs";
9
14
 
10
- const FILE = path.join(os.homedir(), ".shadok-ai", "ledger.json");
15
+ const FILE =
16
+ (process.env.SHADOK_LEDGER_FILE || "").trim() ||
17
+ path.join(os.homedir(), ".shadok-ai", "ledger.json");
11
18
 
12
19
  function load() {
13
20
  try {
@@ -25,6 +32,16 @@ function save(rows) {
25
32
  fs.renameSync(tmp, FILE); // atomic: a concurrent reader never sees half a file
26
33
  }
27
34
 
35
+ /** A short, unique handle (4 hex): quotable from the pushed ledger block. */
36
+ function mintId(rows) {
37
+ const taken = new Set(rows.map((r) => r.id).filter(Boolean));
38
+ for (let i = 0; i < 1000; i++) {
39
+ const id = crypto.randomBytes(2).toString("hex");
40
+ if (!taken.has(id)) return id;
41
+ }
42
+ return crypto.randomBytes(4).toString("hex");
43
+ }
44
+
28
45
  /** `--key value` pairs → object. Values may be quoted by the shell already. */
29
46
  function parseFlags(args) {
30
47
  const out = {};
@@ -37,7 +54,8 @@ function parseFlags(args) {
37
54
  function fmt(e, now) {
38
55
  const age = ageDays(e, now);
39
56
  const when = age <= 0 ? "today" : age === 1 ? "1d ago" : `${age}d ago`;
40
- const head = `• ${e.entity} — ${e.status} (${when}${e.source ? ` · ${e.source}` : ""})`;
57
+ const handle = e.id ? `[${e.id}] ` : "";
58
+ const head = `• ${handle}${e.entity} — ${e.status} (${when}${e.source ? ` · ${e.source}` : ""})`;
41
59
  return e.note ? `${head}\n ${e.note}` : head;
42
60
  }
43
61
 
@@ -59,13 +77,33 @@ if (cmd === "check") {
59
77
  }
60
78
  } else if (cmd === "record") {
61
79
  const f = parseFlags(rest);
62
- if (!f.entity || !f.status) {
63
- console.error('usage: ledger record --entity "<name>" --status <resolved|open|in-progress|decided> [--note "<line>"] [--source "<ref>"]');
80
+ // Two shapes: create/supersede by --entity (+ --status), or update an existing
81
+ // row by its --id handle (change any of status/note/source; entity optional).
82
+ const byId = f.id != null && String(f.id).trim();
83
+ const idHasNoFields = f.status == null && f.note == null && f.source == null && f.entity == null;
84
+ if (byId ? idHasNoFields : !f.entity || !f.status) {
85
+ console.error(
86
+ 'usage: ledger record --entity "<name>" --status <resolved|open|in-progress|decided> [--note "<line>"] [--source "<ref>"]\n' +
87
+ ' or: ledger record --id <handle> [--status <…>] [--note "<line>"] [--source "<ref>"]',
88
+ );
89
+ process.exit(2);
90
+ }
91
+ const rows0 = load();
92
+ let rows;
93
+ try {
94
+ rows = upsertEntry(
95
+ rows0,
96
+ { id: f.id, entity: f.entity, status: f.status, note: f.note, source: f.source },
97
+ now,
98
+ mintId(rows0),
99
+ );
100
+ } catch (e) {
101
+ console.error(String(e?.message ?? e));
64
102
  process.exit(2);
65
103
  }
66
- const rows = upsertEntry(load(), { entity: f.entity, status: f.status, note: f.note, source: f.source }, now);
67
104
  save(rows);
68
- console.log(`recorded: ${f.entity} ${f.status}`);
105
+ const row = byId ? resolveId(rows, f.id) : rows.find((r) => normEntity(r.entity) === normEntity(f.entity));
106
+ console.log(`recorded: ${row.entity} — ${row.status} [${row.id}]`);
69
107
  } else if (cmd === "list") {
70
108
  const rows = findEntries(load(), "");
71
109
  console.log(rows.length ? rows.map((e) => fmt(e, now)).join("\n") : "(ledger empty)");
@@ -1,4 +1,5 @@
1
1
  import { execFile, spawn } from "node:child_process";
2
+ import { claudeCommand } from "./claude-bin.js";
2
3
  /** Pure: `claude auth status --json`. Unreadable output is `unknown`, NOT signed out. */
3
4
  export function parseAuthStatus(stdout) {
4
5
  try {
@@ -59,7 +60,7 @@ export function invalidateAuthStatus() {
59
60
  cached = null;
60
61
  }
61
62
  const probe = () => new Promise((resolve) => {
62
- execFile("claude", ["auth", "status", "--json"], { timeout: 15_000 }, (_err, stdout) => resolve(parseAuthStatus(stdout ?? "")));
63
+ execFile(claudeCommand(), ["auth", "status", "--json"], { timeout: 15_000 }, (_err, stdout) => resolve(parseAuthStatus(stdout ?? "")));
63
64
  });
64
65
  export async function authStatus(force = false) {
65
66
  if (!force && cached && Date.now() - cached.at < STATUS_TTL_MS)
@@ -106,7 +107,7 @@ export async function startLogin() {
106
107
  if ((await authStatus(true)).state === "signed-in")
107
108
  return { alreadySignedIn: true };
108
109
  cancelLogin();
109
- const child = spawn("claude", ["auth", "login", "--claudeai"], {
110
+ const child = spawn(claudeCommand(), ["auth", "login", "--claudeai"], {
110
111
  // BROWSER is neutralised: on a desktop host the CLI would otherwise open a
111
112
  // tab on the SERVER's machine, which is not where the user is.
112
113
  env: { ...process.env, BROWSER: "/usr/bin/true" },
@@ -1 +1 @@
1
- {"version":3,"file":"claude-auth.js","sourceRoot":"","sources":["../src/claude-auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude-auth.js","sourceRoot":"","sources":["../src/claude-auth.ts"],"names":[],"mappings":"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"}
@@ -4,11 +4,121 @@
4
4
  * (name, env, exists) — inject `exists` in tests.
5
5
  */
6
6
  export declare function resolveBin(name: string, env?: NodeJS.ProcessEnv, exists?: (p: string) => boolean): string | null;
7
+ /** Bytes read to classify a launcher. The whole placeholder is 500 B. */
8
+ export declare const BIN_HEAD_BYTES = 512;
9
+ export type BinKind = "usable" | "stub" | "missing";
10
+ /** What a cheap look at a launcher file yields: its size and its first bytes. */
11
+ export interface BinSample {
12
+ size: number;
13
+ head: string;
14
+ }
15
+ /**
16
+ * Tell the npm placeholder from something we can actually run. Pure.
17
+ *
18
+ * Deliberately CONSERVATIVE: it condemns only what it positively recognises as
19
+ * the placeholder, and calls everything else usable. The tempting rule — "a
20
+ * real claude is an ELF/Mach-O of hundreds of MB, so anything else is broken" —
21
+ * would also condemn every legitimate small launcher: pnpm's shell shim,
22
+ * volta/asdf/mise shims, npm's `.cmd` shim on Windows, a user's own wrapper.
23
+ * Those work fine, and refusing to spawn them would be a worse bug than the one
24
+ * this fixes. Same rule as invariant 27: assert only what you observed.
25
+ *
26
+ * No execution probe. Running `claude --version` before each spawn would cost a
27
+ * process launch per session start, and it would not even be sound: the answer
28
+ * is stale the moment it returns, and the window this guards against is
29
+ * milliseconds wide. A stat plus a 512-byte read costs microseconds and is
30
+ * exactly as (un)raceable, so it buys the safety at none of the price.
31
+ */
32
+ export declare function classifyBin(sample: BinSample | null): BinKind;
33
+ /** Read the first {@link BIN_HEAD_BYTES} of a file, or null if it isn't there. */
34
+ export declare function sampleBin(p: string): BinSample | null;
35
+ /**
36
+ * The optional dependency holding the native binary for a platform, mirroring
37
+ * the PLATFORMS map of `install.cjs`. Returns null for a platform the package
38
+ * does not publish — the caller then has no fallback, which is the truth.
39
+ */
40
+ export declare function platformPkg(platform: string, arch: string, musl?: boolean): {
41
+ pkg: string;
42
+ bin: string;
43
+ } | null;
44
+ /**
45
+ * Where the native binary may sit, given the REAL path of the launcher
46
+ * (`.../@anthropic-ai/claude-code/bin/claude.exe`). Pure: it only builds
47
+ * candidates, the caller decides which one exists.
48
+ *
49
+ * The `node_modules` walk is not decoration. npm hoists an optional dependency
50
+ * to the top level as readily as it nests it, and the layout differs between a
51
+ * global install, `npx`, and the managed `~/.shadok-ai/app`. Assuming the nested
52
+ * layout is precisely the mistake that made `node-pty-fix.ts`'s chmod silently
53
+ * miss on every non-dev install (see CLAUDE.md) — so we reproduce node's own
54
+ * resolution instead: every ancestor directory, each with `node_modules/<pkg>`.
55
+ */
56
+ export declare function nativeBinCandidates(launcherRealPath: string, pkg: string, bin: string): string[];
57
+ export type ClaudeBin = {
58
+ ok: true;
59
+ path: string;
60
+ via: "launcher" | "native";
61
+ } | {
62
+ ok: false;
63
+ reason: "missing" | "stub";
64
+ };
65
+ export interface FindClaudeDeps {
66
+ /** Find the launcher now (typically resolveBin("claude")). */
67
+ resolve: () => string | null;
68
+ /** Follow symlinks (typically fs.realpathSync); may throw, caller guards. */
69
+ realpath: (p: string) => string;
70
+ /** Cheap look at a file (typically sampleBin). */
71
+ sample: (p: string) => BinSample | null;
72
+ platform: string;
73
+ arch: string;
74
+ musl: boolean;
75
+ }
76
+ /**
77
+ * Resolve a claude we can actually run: the launcher when it is real, else the
78
+ * native binary the launcher is only a placeholder for. Pure over injected
79
+ * deps, so the whole decision is unit-tested without touching the real FS.
80
+ */
81
+ export declare function findClaudeBin(deps: FindClaudeDeps): ClaudeBin;
82
+ export interface RetryOptions {
83
+ /** Total attempts, including the first. */
84
+ tries?: number;
85
+ delayMs?: number;
86
+ sleep?: (ms: number) => Promise<void>;
87
+ }
88
+ /**
89
+ * {@link findClaudeBin}, retried briefly. The failure this guards is a
90
+ * TRANSIENT window in someone else's postinstall (unlink → relink), so a hard
91
+ * refusal on the first look would report a healthy install as broken. Bounded
92
+ * on purpose — a couple of seconds at worst, and only on the spawn path, never
93
+ * at boot (same rule as `ensureTmux` / `ensureSshIdentity`: nothing here may
94
+ * hold the server back from serving).
95
+ */
96
+ export declare function findClaudeBinWithRetry(deps: FindClaudeDeps, opts?: RetryOptions): Promise<ClaudeBin>;
97
+ /** Live deps for {@link findClaudeBin}, reading the real filesystem. */
98
+ export declare function liveClaudeDeps(): FindClaudeDeps;
99
+ /**
100
+ * The claude to spawn. The resolved binary when we have one — which is NOT
101
+ * always the launcher, see {@link findClaudeBin} — else the bare name, so a
102
+ * layout we failed to understand behaves exactly as it did before.
103
+ *
104
+ * Re-validated on each call: a cached path rots the moment claude-code
105
+ * upgrades, and one stat is a rounding error next to the process spawn every
106
+ * caller is about to pay for.
107
+ */
108
+ export declare function claudeCommand(): string;
109
+ /** Record a path already resolved (by `ensureClaude`), so we don't look twice. */
110
+ export declare function rememberClaudeBin(p: string | null): void;
7
111
  /** The manual-install fallback message, shown when the auto-install can't help. */
8
112
  export declare function claudeMissingMessage(detail: string): string;
113
+ /**
114
+ * Why a spawn was refused when `claude` IS on PATH. The point is to say what is
115
+ * wrong instead of letting a bare `posix_spawnp failed` — or the placeholder's
116
+ * own exit 1 — reach the user, in the spirit of `describeStuckScreen`.
117
+ */
118
+ export declare function claudeStubMessage(): string;
9
119
  export interface EnsureClaudeDeps {
10
- /** Find claude now (typically resolveBin("claude")). Called again after install. */
11
- resolve: () => string | null;
120
+ /** Locate a RUNNABLE claude now. Called again after an install. */
121
+ find: () => Promise<ClaudeBin>;
12
122
  /** Perform the global install (typically `npm i -g @anthropic-ai/claude-code`). */
13
123
  install: () => Promise<void>;
14
124
  /** Surface progress (server log / a line to the client). */
@@ -22,10 +132,14 @@ export type EnsureClaudeResult = {
22
132
  error: string;
23
133
  };
24
134
  /**
25
- * Make the claude CLI available, installing it ONCE if it is missing. Returns
26
- * the resolved path, or a clear, actionable error when it still isn't there
27
- * (install failed on permissions, or the global bin dir isn't on PATH) — never
28
- * the raw `posix_spawnp failed` a bare spawn would throw. Pure orchestration
29
- * over injected deps, so it is tested without touching npm.
135
+ * Make a runnable claude CLI available, installing it ONCE if it is missing.
136
+ * Returns the path to spawn which is NOT always the launcher: when the
137
+ * launcher is the placeholder we hand back the native binary behind it.
138
+ * Otherwise a clear, actionable error, never the raw `posix_spawnp failed` a
139
+ * bare spawn would throw, and never the placeholder's opaque exit 1.
140
+ *
141
+ * A placeholder is NOT a reason to reinstall: the package is plainly installed,
142
+ * so `npm i -g` would neither be the missing-CLI case nor a safe move while
143
+ * someone else's postinstall is mid-rewrite. We say what is wrong instead.
30
144
  */
31
145
  export declare function ensureClaude(deps: EnsureClaudeDeps): Promise<EnsureClaudeResult>;