shadok-ai 0.2.45 → 0.2.47

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
@@ -15,9 +15,10 @@ npx shadok-ai
15
15
  Then open **http://localhost:3789**.
16
16
 
17
17
  **Prerequisites:** Node ≥ 20 and the [`claude`](https://claude.com/claude-code)
18
- CLI installed and signed in on the machine (shadok-ai drives your existing
19
- Claude Code, on your subscription). `tmux` is optional but recommendedwith
20
- it, agents survive the server restarting.
18
+ CLI installed (shadok-ai drives your existing Claude Code, on your
19
+ subscription). It does **not** need to be signed in beforehandif it isn't,
20
+ the cockpit offers the sign-in itself (see below). `tmux` is optional but
21
+ recommended — with it, agents survive the server restarting.
21
22
 
22
23
  On the first run it asks once for an optional **Telegram bot token** (press
23
24
  Enter to skip; you can add it later from the web UI).
@@ -74,6 +75,12 @@ Enter to skip; you can add it later from the web UI).
74
75
  the chat can't express. With tmux there is also an **experimental real
75
76
  terminal** (xterm.js over the pane's byte stream) when a snapshot isn't enough.
76
77
  - **Diff panel** — what an agent actually changed, against its base.
78
+ - **Phone layout** — the cockpit is a fixed chassis that never pans sideways,
79
+ sized on the *visual* viewport so the composer stays clear of the browser's
80
+ own bar and, when the keyboard opens, sits right above it (the instrument bar
81
+ folds away while you type). Fields are 16px, which is what stops Safari from
82
+ zooming in on focus. The agents column is replaced by a channel selector in
83
+ the channel bar.
77
84
  - **Tweak Shadok-AI** — a card pinned at the bottom of the agents column. One
78
85
  click clones shadok-ai's own source into `~/.shadok-ai/self/shadok-ai` and
79
86
  starts an agent on it in its own worktree; that agent delivers its change as a
@@ -156,6 +163,48 @@ location / {
156
163
  The client picks `wss://` on its own when the page is HTTPS, so there is nothing
157
164
  to configure on that side.
158
165
 
166
+ ### Signing in to Claude
167
+
168
+ A fresh machine — most often a fresh container — has two things in its way, and
169
+ shadok handles both so you never need a shell on the host.
170
+
171
+ **The first-run screens are pre-answered.** A virgin `claude` opens on a theme
172
+ picker, then on a per-directory trust dialog, and an agent started there never
173
+ reaches a prompt: no input box, every prompt fails, the tab reads "failed to
174
+ start". shadok seeds the answers in `~/.claude.json` — at boot for the global
175
+ ones, and before every spawn for the session's directory, because a worktree is
176
+ a brand-new directory and therefore a brand-new trust dialog. The seeding is
177
+ **purely additive**: a value already there is never overwritten, so on a machine
178
+ that has used Claude Code before, nothing is written at all.
179
+
180
+ **The sign-in is offered in the cockpit.** When the instance isn't signed in, a
181
+ card takes over the page with the OAuth link and a field for the code you get
182
+ back; agents refuse to start until it's done, so you can't accidentally
183
+ manufacture an agent with no credentials. The card exists only while it's
184
+ needed — there is no button that opens it when everything is fine.
185
+
186
+ **From Telegram**, which is where you usually are when a session signs out
187
+ mid-life:
188
+
189
+ ```
190
+ /login → replies with the link
191
+ /code <code> → finishes the sign-in (your message is deleted afterwards)
192
+ ```
193
+
194
+ Both are restricted to the bound board group or the owner's DM, like `/secret` —
195
+ an OAuth code grants access to the account. The sign-in is instance-global, so
196
+ the link is the same on both sides and a code pasted in Telegram also closes the
197
+ card open in a browser. When a spawn is refused for lack of credentials, shadok
198
+ posts **one** message to the board group; it won't repeat until the state flips
199
+ back.
200
+
201
+ **In Docker**, this replaces the old recreate ritual. `/root/.claude.json` is
202
+ not on a volume, so `docker rm` + recreate used to lose the onboarding state and
203
+ the respawned agents landed on the first-run screen — which meant restoring the
204
+ file *before* starting the container (`docker create` → `docker cp` →
205
+ `docker start`). That ordering is no longer necessary: shadok writes the file
206
+ itself, before any session spawns.
207
+
159
208
  ### SSH identity in Docker
160
209
 
161
210
  When shadok-ai runs **in a container** it gives itself an SSH key on first boot
@@ -364,14 +413,16 @@ client, or immediately on an explicit `stop` (which ends it for everyone).
364
413
  | `{type:"auto-retry"}` / `-cancelled` / `-gave-up` | transient API error being retried |
365
414
  | `{type:"version", …}` / `{type:"server-reload", version}` | update available / server updated, reload |
366
415
  | `{type:"term-data", data}` | **experimental** — raw pane output (base64) for a client-side terminal emulator |
367
- | `{type:"gone"}` / `{type:"error", message, code?}` / `{type:"exited", code}` / `{type:"stopped"}` | session lost, errors, termination. `error.code` is `"busy"` for a prompt refused mid-turn, so a machine client needn't match on the message text |
416
+ | `{type:"gone"}` / `{type:"error", message, code?}` / `{type:"exited", code}` / `{type:"stopped"}` | session lost, errors, termination. `error.code` is `"busy"` for a prompt refused mid-turn and `"logged-out"` for a spawn refused because the instance isn't signed in — so a machine client needn't match on the message text |
368
417
 
369
418
  HTTP endpoints (same auth): `/usage`, `/live`, `/sessions`, `/recover`,
370
419
  `/diff`, `/channels` (its GET adds a **derived** `crons` field — never stored),
371
420
  `/channel` (DELETE), `/groups`, `/crons`, `/timezone`, `/profiles`, `/secrets`,
372
421
  `/telegram`, `/defaults`, `/version`, `/autoupdate`, `/permission-mode`,
373
422
  `/tweak/prepare` (POST — clone/refresh shadok-ai's own source, returns the cwd
374
- to start the tweak agent in).
423
+ to start the tweak agent in), and the sign-in group: `/auth` (GET — `{loggedIn,
424
+ email?, subscriptionType?}`), `/auth/login` (POST — start a flow, returns
425
+ `{url}`; DELETE — cancel it), `/auth/code` (POST `{code}`).
375
426
 
376
427
  ## Library
377
428
 
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Claude Code authentication: status, and the interactive login.
3
+ *
4
+ * The load-bearing finding behind this module (verified 2026-08-08 against
5
+ * claude v2.1.226): `claude auth login --claudeai` needs NO PTY. Run with plain
6
+ * pipes it prints the OAuth URL on stdout and reads the code from stdin. So the
7
+ * login touches none of the screen heuristics — no `detectDialog`, no
8
+ * `screenShowsWork`, nothing from the fragile family invariant nº 2 warns
9
+ * about. It is a spawn, a stdout parser and one write to stdin.
10
+ *
11
+ * See docs/superpowers/specs/2026-08-08-claude-onboarding-design.md.
12
+ */
13
+ export interface AuthStatus {
14
+ loggedIn: boolean;
15
+ authMethod?: string;
16
+ email?: string;
17
+ subscriptionType?: string;
18
+ }
19
+ /** Pure: `claude auth status --json`. Anything unreadable reads as logged out. */
20
+ export declare function parseAuthStatus(stdout: string): AuthStatus;
21
+ /** Pure: the OAuth URL the CLI printed, or null if it hasn't printed one yet. */
22
+ export declare function parseLoginUrl(chunk: string): string | null;
23
+ /**
24
+ * Pure: the refusal the CLI printed for the code we submitted, if any.
25
+ *
26
+ * There is deliberately no "success" case. The invalid-code wording was
27
+ * captured from the real CLI; a success wording never was, and a parser that
28
+ * guessed one would produce a sign-in that silently never completes. Success is
29
+ * the child exiting cleanly — see `startLogin`.
30
+ */
31
+ export declare function parseLoginOutcome(chunk: string): "invalid-code" | null;
32
+ export declare function invalidateAuthStatus(): void;
33
+ export declare function authStatus(force?: boolean): Promise<AuthStatus>;
34
+ export declare function loginPending(): boolean;
35
+ export declare function cancelLogin(): void;
36
+ export declare function startLogin(): Promise<{
37
+ url: string;
38
+ } | {
39
+ error: string;
40
+ }>;
41
+ export declare function submitLoginCode(code: string): Promise<{
42
+ ok: true;
43
+ } | {
44
+ ok: false;
45
+ error: string;
46
+ }>;
@@ -0,0 +1,191 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ /** Pure: `claude auth status --json`. Anything unreadable reads as logged out. */
3
+ export function parseAuthStatus(stdout) {
4
+ try {
5
+ const j = JSON.parse(stdout);
6
+ if (!j || typeof j !== "object" || Array.isArray(j))
7
+ return { loggedIn: false };
8
+ if (j.loggedIn !== true)
9
+ return { loggedIn: false };
10
+ return {
11
+ loggedIn: true,
12
+ ...(typeof j.authMethod === "string" ? { authMethod: j.authMethod } : {}),
13
+ ...(typeof j.email === "string" ? { email: j.email } : {}),
14
+ ...(typeof j.subscriptionType === "string" ? { subscriptionType: j.subscriptionType } : {}),
15
+ };
16
+ }
17
+ catch {
18
+ return { loggedIn: false };
19
+ }
20
+ }
21
+ /**
22
+ * Strip ANSI/OSC escape sequences.
23
+ *
24
+ * The login output wraps the URL in an OSC 8 hyperlink
25
+ * (`ESC ] 8 ; ; <url> BEL <visible text> ESC ] 8 ; ; BEL`), so the URL is
26
+ * physically present TWICE and a naive `visit:\s*(\S+)` captures the escape
27
+ * plus a fragment of it. Stripping first makes the match trivial.
28
+ */
29
+ function stripEscapes(s) {
30
+ return s
31
+ // OSC 8 open (ESC ] 8 ; ; <url> BEL) and close (ESC ] 8 ; ; BEL).
32
+ .replace(/\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
33
+ // Ordinary CSI colour/cursor sequences.
34
+ .replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
35
+ }
36
+ /** Pure: the OAuth URL the CLI printed, or null if it hasn't printed one yet. */
37
+ export function parseLoginUrl(chunk) {
38
+ const m = stripEscapes(chunk).match(/visit:\s*(https:\/\/\S+)/);
39
+ return m ? m[1] : null;
40
+ }
41
+ /**
42
+ * Pure: the refusal the CLI printed for the code we submitted, if any.
43
+ *
44
+ * There is deliberately no "success" case. The invalid-code wording was
45
+ * captured from the real CLI; a success wording never was, and a parser that
46
+ * guessed one would produce a sign-in that silently never completes. Success is
47
+ * the child exiting cleanly — see `startLogin`.
48
+ */
49
+ export function parseLoginOutcome(chunk) {
50
+ return /Invalid code/i.test(stripEscapes(chunk)) ? "invalid-code" : null;
51
+ }
52
+ /** How long a status answer is reused. It is a spawn, and the card polls it. */
53
+ const STATUS_TTL_MS = 30_000;
54
+ /** The OAuth URL expires anyway; a child holding stdin open forever is worse. */
55
+ const FLOW_IDLE_MS = 10 * 60_000;
56
+ let cached = null;
57
+ export function invalidateAuthStatus() {
58
+ cached = null;
59
+ }
60
+ export async function authStatus(force = false) {
61
+ if (!force && cached && Date.now() - cached.at < STATUS_TTL_MS)
62
+ return cached.status;
63
+ const status = await new Promise((resolve) => {
64
+ execFile("claude", ["auth", "status", "--json"], { timeout: 15_000 }, (_err, stdout) => resolve(parseAuthStatus(stdout ?? "")));
65
+ });
66
+ cached = { at: Date.now(), status };
67
+ return status;
68
+ }
69
+ /**
70
+ * ONE flow at a time for the whole instance: the credentials are machine-global,
71
+ * so a second concurrent login would race the first for the same keychain
72
+ * entry. The upside is free — the web card and Telegram share the same URL, and
73
+ * a code pasted from either finishes the other's flow.
74
+ */
75
+ let flow = null;
76
+ export function loginPending() {
77
+ return flow !== null && !flow.ended;
78
+ }
79
+ export function cancelLogin() {
80
+ if (!flow)
81
+ return;
82
+ clearTimeout(flow.timer);
83
+ flow.child.kill();
84
+ flow = null;
85
+ }
86
+ export async function startLogin() {
87
+ // Reuse a flow that already printed its URL instead of replacing it. Each
88
+ // flow carries its own PKCE challenge, so restarting would silently kill the
89
+ // link the user is already looking at — which a plain page reload, or a
90
+ // second tab, would otherwise do.
91
+ if (flow && !flow.ended && flow.url)
92
+ return { url: flow.url };
93
+ cancelLogin();
94
+ const child = spawn("claude", ["auth", "login", "--claudeai"], {
95
+ // BROWSER is neutralised: on a desktop host the CLI would otherwise open a
96
+ // tab on the SERVER's machine, which is not where the user is.
97
+ env: { ...process.env, BROWSER: "/usr/bin/true" },
98
+ stdio: ["pipe", "pipe", "pipe"],
99
+ });
100
+ const f = {
101
+ child,
102
+ url: null,
103
+ out: "",
104
+ pending: null,
105
+ ended: false,
106
+ timer: setTimeout(() => cancelLogin(), FLOW_IDLE_MS),
107
+ };
108
+ flow = f;
109
+ const settle = (v) => {
110
+ const done = f.pending;
111
+ f.pending = null;
112
+ done?.(v);
113
+ };
114
+ const onData = (d) => {
115
+ f.out += d.toString();
116
+ if (parseLoginOutcome(f.out) === "invalid-code")
117
+ settle("invalid-code");
118
+ };
119
+ child.stdout.on("data", onData);
120
+ child.stderr.on("data", onData);
121
+ // SUCCESS IS AN EXIT, NOT A STRING. The "Invalid code…" wording was captured
122
+ // from the real CLI; the success wording never was, and inventing one would
123
+ // produce a sign-in that silently never completes. A clean exit after a code
124
+ // was submitted is the signal we can actually prove.
125
+ child.on("exit", (code) => {
126
+ f.ended = true;
127
+ settle(code === 0 ? "success" : "ended");
128
+ });
129
+ // A `claude` that is not on PATH must not leave the caller hanging until the
130
+ // 30s deadline with no explanation.
131
+ child.on("error", () => {
132
+ f.ended = true;
133
+ settle("ended");
134
+ });
135
+ return new Promise((resolve) => {
136
+ const deadline = setTimeout(() => {
137
+ cancelLogin();
138
+ resolve({ error: "claude auth login printed no URL within 30s" });
139
+ }, 30_000);
140
+ const poll = setInterval(() => {
141
+ const url = parseLoginUrl(f.out);
142
+ if (url) {
143
+ f.url = url;
144
+ clearInterval(poll);
145
+ clearTimeout(deadline);
146
+ resolve({ url });
147
+ }
148
+ else if (flow !== f || f.ended) {
149
+ clearInterval(poll);
150
+ clearTimeout(deadline);
151
+ resolve({ error: "the login process ended before printing a URL" });
152
+ }
153
+ }, 100);
154
+ });
155
+ }
156
+ export async function submitLoginCode(code) {
157
+ const f = flow;
158
+ if (!f || f.ended)
159
+ return { ok: false, error: "no sign-in is in progress — start one first" };
160
+ const trimmed = code.trim();
161
+ if (!trimmed)
162
+ return { ok: false, error: "empty code" };
163
+ // The user is clearly still here: push the idle deadline back.
164
+ clearTimeout(f.timer);
165
+ f.timer = setTimeout(() => cancelLogin(), FLOW_IDLE_MS);
166
+ // Forget what came before, so a stale "Invalid code" from a previous attempt
167
+ // is never mistaken for the verdict on THIS one.
168
+ f.out = "";
169
+ const verdict = await new Promise((resolve) => {
170
+ const t = setTimeout(() => {
171
+ f.pending = null;
172
+ resolve("timeout");
173
+ }, 30_000);
174
+ f.pending = (r) => {
175
+ clearTimeout(t);
176
+ resolve(r);
177
+ };
178
+ f.child.stdin.write(trimmed + "\n");
179
+ });
180
+ if (verdict === "success") {
181
+ cancelLogin();
182
+ invalidateAuthStatus();
183
+ return { ok: true };
184
+ }
185
+ if (verdict === "invalid-code")
186
+ return { ok: false, error: "Invalid code. Please make sure the full code was copied." };
187
+ if (verdict === "ended")
188
+ return { ok: false, error: "the sign-in was refused — start a new one and try again" };
189
+ return { ok: false, error: "the CLI did not answer within 30s" };
190
+ }
191
+ //# sourceMappingURL=claude-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-auth.js","sourceRoot":"","sources":["../src/claude-auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Seeding of Claude Code's first-run state.
3
+ *
4
+ * A virgin `claude` opens on its theme picker, then on a per-directory trust
5
+ * dialog, and never reaches a prompt: no input box, every submitted prompt
6
+ * fails, the tab reads "failed to start". That is how three zombie agents were
7
+ * once produced on a production container and found a day later.
8
+ *
9
+ * So shadok writes the handful of keys that answer those screens. The module is
10
+ * PURELY ADDITIVE — it only ever adds keys that are absent — which is what
11
+ * makes it safe to run unconditionally. Unlike `src/ssh.ts`, which had to
12
+ * restrict itself to containers (invariant 21), no Docker gate is needed here:
13
+ * on a machine that has used Claude Code before, the plan is empty and nothing
14
+ * is written at all.
15
+ *
16
+ * See docs/superpowers/specs/2026-08-08-claude-onboarding-design.md.
17
+ */
18
+ export interface ClaudeHome {
19
+ hasCompletedOnboarding?: boolean;
20
+ lastOnboardingVersion?: string;
21
+ theme?: string;
22
+ projects?: Record<string, Record<string, unknown>>;
23
+ [k: string]: unknown;
24
+ }
25
+ /** Pure: the semver out of `claude --version` ("2.1.226 (Claude Code)"). */
26
+ export declare function parseClaudeVersion(stdout: string): string | null;
27
+ /**
28
+ * Pure: the object to write, or null when nothing is missing.
29
+ *
30
+ * Returning null (rather than an identical object) is what lets the writer skip
31
+ * the write entirely — a multi-megabyte file is not rewritten for nothing.
32
+ */
33
+ export declare function seedPlan(existing: ClaudeHome, opts: {
34
+ version: string;
35
+ cwd?: string;
36
+ }): ClaudeHome | null;
37
+ export interface ClaudeSettings {
38
+ tui?: string;
39
+ [k: string]: unknown;
40
+ }
41
+ /**
42
+ * Pure: `~/.claude/settings.json` after adding the keys that suppress a
43
+ * first-run *upsell*, or null when nothing is missing.
44
+ *
45
+ * The fullscreen upsell ("Flicker-free output · Mouse support · Selected text
46
+ * auto-copies") is not gated by the onboarding flags — it appears AFTER the
47
+ * sign-in, which is why no probe caught it before a real account reached the
48
+ * screen. It is counted by `fullscreenUpsellSeenCount`, but seeding a counter
49
+ * means guessing its threshold; recording an explicit `tui` preference is the
50
+ * durable answer, because a choice already made cannot be upsold. Evidence: a
51
+ * developer machine with `tui` set has never accumulated that counter in 792
52
+ * startups.
53
+ */
54
+ export declare function settingsPlan(existing: ClaudeSettings): ClaudeSettings | null;
55
+ /** Called once at boot: the globals, plus the settings that kill the upsell. */
56
+ export declare function ensureClaudeHome(): void;
57
+ /**
58
+ * Called before EVERY spawn, for that session's directory.
59
+ *
60
+ * This cannot be a boot-time-only concern: a worktree is a brand-new directory
61
+ * for every agent, therefore a brand-new trust dialog every time.
62
+ */
63
+ export declare function ensureProjectTrusted(cwd: string): void;
@@ -0,0 +1,169 @@
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
+ /** The per-project keys that answer the trust dialog. */
6
+ const PROJECT_KEYS = ["hasTrustDialogAccepted", "hasCompletedProjectOnboarding"];
7
+ /** Pure: the semver out of `claude --version` ("2.1.226 (Claude Code)"). */
8
+ export function parseClaudeVersion(stdout) {
9
+ const m = stdout.match(/^\s*(\d+\.\d+\.\d+[^\s(]*)/);
10
+ return m ? m[1] : null;
11
+ }
12
+ /**
13
+ * Pure: the object to write, or null when nothing is missing.
14
+ *
15
+ * Returning null (rather than an identical object) is what lets the writer skip
16
+ * the write entirely — a multi-megabyte file is not rewritten for nothing.
17
+ */
18
+ export function seedPlan(existing, opts) {
19
+ const out = { ...existing };
20
+ let changed = false;
21
+ const add = (key, value) => {
22
+ if (!(key in out)) {
23
+ out[key] = value;
24
+ changed = true;
25
+ }
26
+ };
27
+ // These two answer the theme picker. `theme` is deliberately NOT written:
28
+ // verified 2026-08-08 that the CLI DELETES an unknown top-level `theme` key
29
+ // on its next write, and that the picker is already skipped without it — so
30
+ // seeding one would be cargo cult that also implies we control the theme.
31
+ add("hasCompletedOnboarding", true);
32
+ add("lastOnboardingVersion", opts.version);
33
+ if (opts.cwd) {
34
+ const projects = { ...(existing.projects ?? {}) };
35
+ const entry = { ...(projects[opts.cwd] ?? {}) };
36
+ let entryChanged = false;
37
+ for (const key of PROJECT_KEYS) {
38
+ if (!(key in entry)) {
39
+ entry[key] = true;
40
+ entryChanged = true;
41
+ }
42
+ }
43
+ if (entryChanged) {
44
+ projects[opts.cwd] = entry;
45
+ out.projects = projects;
46
+ changed = true;
47
+ }
48
+ }
49
+ return changed ? out : null;
50
+ }
51
+ /**
52
+ * Pure: `~/.claude/settings.json` after adding the keys that suppress a
53
+ * first-run *upsell*, or null when nothing is missing.
54
+ *
55
+ * The fullscreen upsell ("Flicker-free output · Mouse support · Selected text
56
+ * auto-copies") is not gated by the onboarding flags — it appears AFTER the
57
+ * sign-in, which is why no probe caught it before a real account reached the
58
+ * screen. It is counted by `fullscreenUpsellSeenCount`, but seeding a counter
59
+ * means guessing its threshold; recording an explicit `tui` preference is the
60
+ * durable answer, because a choice already made cannot be upsold. Evidence: a
61
+ * developer machine with `tui` set has never accumulated that counter in 792
62
+ * startups.
63
+ */
64
+ export function settingsPlan(existing) {
65
+ if ("tui" in existing)
66
+ return null; // the user's own choice, never overridden
67
+ // "fullscreen" is the value both working installations run — shadok reads
68
+ // content from the transcript, not the screen, so the TUI's rendering mode
69
+ // does not affect it either way.
70
+ return { ...existing, tui: "fullscreen" };
71
+ }
72
+ const homeFile = () => path.join(os.homedir(), ".claude.json");
73
+ const settingsFile = () => path.join(os.homedir(), ".claude", "settings.json");
74
+ /** The claude CLI's version, or a conservative fallback. */
75
+ function claudeVersion() {
76
+ try {
77
+ return (parseClaudeVersion(execFileSync("claude", ["--version"], { encoding: "utf8" })) ?? "0.0.0");
78
+ }
79
+ catch {
80
+ return "0.0.0";
81
+ }
82
+ }
83
+ /**
84
+ * Read the file, or null when it exists but does not parse.
85
+ *
86
+ * Null means "leave it alone". We never "repair" an unparseable ~/.claude.json
87
+ * by overwriting it: the file carries the whole per-project history, and
88
+ * destroying it costs incomparably more than the screen this module avoids.
89
+ */
90
+ function readHome(file) {
91
+ if (!fs.existsSync(file))
92
+ return {};
93
+ try {
94
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
95
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ /**
102
+ * Atomic write: a temp file in the SAME directory, then rename. An interrupted
103
+ * write must never leave a truncated ~/.claude.json behind.
104
+ */
105
+ function writeHome(file, data) {
106
+ const tmp = `${file}.shadok-${process.pid}.tmp`;
107
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
108
+ fs.renameSync(tmp, file);
109
+ }
110
+ /** Apply a plan for `cwd` (or the globals only when cwd is undefined). */
111
+ function seed(cwd) {
112
+ try {
113
+ const file = homeFile();
114
+ const existing = readHome(file);
115
+ if (existing === null)
116
+ return; // unparseable → hands off
117
+ const plan = seedPlan(existing, { version: claudeVersion(), cwd });
118
+ if (plan)
119
+ writeHome(file, plan);
120
+ }
121
+ catch {
122
+ // A failed seed must never take down the boot path or a spawn — same rule
123
+ // as ensureSshIdentity.
124
+ }
125
+ }
126
+ /** Called once at boot: the globals, plus the settings that kill the upsell. */
127
+ export function ensureClaudeHome() {
128
+ seed();
129
+ seedSettings();
130
+ }
131
+ /** Apply `settingsPlan` to `~/.claude/settings.json`. Same rules as `seed`. */
132
+ function seedSettings() {
133
+ try {
134
+ const file = settingsFile();
135
+ let existing = {};
136
+ if (fs.existsSync(file)) {
137
+ try {
138
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
139
+ existing =
140
+ parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
141
+ }
142
+ catch {
143
+ existing = null; // hand-edited into invalid JSON → never clobber it
144
+ }
145
+ }
146
+ if (existing === null)
147
+ return;
148
+ const plan = settingsPlan(existing);
149
+ if (!plan)
150
+ return;
151
+ fs.mkdirSync(path.dirname(file), { recursive: true });
152
+ const tmp = `${file}.shadok-${process.pid}.tmp`;
153
+ fs.writeFileSync(tmp, JSON.stringify(plan, null, 2));
154
+ fs.renameSync(tmp, file);
155
+ }
156
+ catch {
157
+ // Never take down the boot path.
158
+ }
159
+ }
160
+ /**
161
+ * Called before EVERY spawn, for that session's directory.
162
+ *
163
+ * This cannot be a boot-time-only concern: a worktree is a brand-new directory
164
+ * for every agent, therefore a brand-new trust dialog every time.
165
+ */
166
+ export function ensureProjectTrusted(cwd) {
167
+ seed(cwd);
168
+ }
169
+ //# sourceMappingURL=claude-home.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-home.js","sourceRoot":"","sources":["../src/claude-home.ts"],"names":[],"mappings":"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"}