@addai/node 0.4.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.
Files changed (133) hide show
  1. package/README.md +96 -0
  2. package/dist/agent-auth.d.ts +9 -0
  3. package/dist/agent-auth.js +96 -0
  4. package/dist/attachments.d.ts +20 -0
  5. package/dist/attachments.js +164 -0
  6. package/dist/capabilities.d.ts +64 -0
  7. package/dist/capabilities.js +406 -0
  8. package/dist/claude-binary.d.ts +16 -0
  9. package/dist/claude-binary.js +145 -0
  10. package/dist/claude-config.d.ts +11 -0
  11. package/dist/claude-config.js +144 -0
  12. package/dist/claude-print.d.ts +35 -0
  13. package/dist/claude-print.js +312 -0
  14. package/dist/claude-spawn.d.ts +40 -0
  15. package/dist/claude-spawn.js +220 -0
  16. package/dist/cli.d.ts +2 -0
  17. package/dist/cli.js +137 -0
  18. package/dist/codex-binary.d.ts +4 -0
  19. package/dist/codex-binary.js +70 -0
  20. package/dist/codex-spawn.d.ts +47 -0
  21. package/dist/codex-spawn.js +320 -0
  22. package/dist/command-runner.d.ts +9 -0
  23. package/dist/command-runner.js +592 -0
  24. package/dist/config.d.ts +7 -0
  25. package/dist/config.js +38 -0
  26. package/dist/control-server.d.ts +5 -0
  27. package/dist/control-server.js +292 -0
  28. package/dist/diskguard.d.ts +96 -0
  29. package/dist/diskguard.js +435 -0
  30. package/dist/error-classify.d.ts +20 -0
  31. package/dist/error-classify.js +159 -0
  32. package/dist/events.d.ts +26 -0
  33. package/dist/events.js +97 -0
  34. package/dist/gemini-binary.d.ts +4 -0
  35. package/dist/gemini-binary.js +64 -0
  36. package/dist/gemini-spawn.d.ts +52 -0
  37. package/dist/gemini-spawn.js +369 -0
  38. package/dist/git-identity.d.ts +18 -0
  39. package/dist/git-identity.js +104 -0
  40. package/dist/grok-binary.d.ts +4 -0
  41. package/dist/grok-binary.js +70 -0
  42. package/dist/grok-spawn.d.ts +41 -0
  43. package/dist/grok-spawn.js +495 -0
  44. package/dist/guardrails.d.ts +32 -0
  45. package/dist/guardrails.js +125 -0
  46. package/dist/hang-watchdog.d.ts +17 -0
  47. package/dist/hang-watchdog.js +48 -0
  48. package/dist/harness-registry.d.ts +55 -0
  49. package/dist/harness-registry.js +180 -0
  50. package/dist/heartbeat.d.ts +13 -0
  51. package/dist/heartbeat.js +95 -0
  52. package/dist/index.d.ts +12 -0
  53. package/dist/index.js +440 -0
  54. package/dist/install.d.ts +63 -0
  55. package/dist/install.js +467 -0
  56. package/dist/jsonl-tail.d.ts +20 -0
  57. package/dist/jsonl-tail.js +192 -0
  58. package/dist/kimi-binary.d.ts +4 -0
  59. package/dist/kimi-binary.js +68 -0
  60. package/dist/kimi-spawn.d.ts +47 -0
  61. package/dist/kimi-spawn.js +319 -0
  62. package/dist/lockfile.d.ts +30 -0
  63. package/dist/lockfile.js +169 -0
  64. package/dist/mcp-config.d.ts +23 -0
  65. package/dist/mcp-config.js +133 -0
  66. package/dist/memory-capture.d.ts +20 -0
  67. package/dist/memory-capture.js +45 -0
  68. package/dist/memory-pack.d.ts +34 -0
  69. package/dist/memory-pack.js +171 -0
  70. package/dist/model-filter.d.ts +4 -0
  71. package/dist/model-filter.js +34 -0
  72. package/dist/node-health.d.ts +18 -0
  73. package/dist/node-health.js +74 -0
  74. package/dist/pairing.d.ts +20 -0
  75. package/dist/pairing.js +129 -0
  76. package/dist/paths.d.ts +9 -0
  77. package/dist/paths.js +48 -0
  78. package/dist/precompact-hook.d.ts +27 -0
  79. package/dist/precompact-hook.js +136 -0
  80. package/dist/projects.d.ts +25 -0
  81. package/dist/projects.js +255 -0
  82. package/dist/prompt-keystrokes.d.ts +3 -0
  83. package/dist/prompt-keystrokes.js +56 -0
  84. package/dist/pty-helper.d.ts +26 -0
  85. package/dist/pty-helper.js +156 -0
  86. package/dist/request-pump.d.ts +12 -0
  87. package/dist/request-pump.js +249 -0
  88. package/dist/run-retry.d.ts +28 -0
  89. package/dist/run-retry.js +81 -0
  90. package/dist/self-update.d.ts +75 -0
  91. package/dist/self-update.js +192 -0
  92. package/dist/session-runner.d.ts +130 -0
  93. package/dist/session-runner.js +2271 -0
  94. package/dist/sleep-detector.d.ts +11 -0
  95. package/dist/sleep-detector.js +32 -0
  96. package/dist/state-migrate.d.ts +8 -0
  97. package/dist/state-migrate.js +91 -0
  98. package/dist/store.d.ts +5 -0
  99. package/dist/store.js +82 -0
  100. package/dist/supabase-client.d.ts +15 -0
  101. package/dist/supabase-client.js +119 -0
  102. package/dist/time-context.d.ts +10 -0
  103. package/dist/time-context.js +71 -0
  104. package/dist/tokens.d.ts +4 -0
  105. package/dist/tokens.js +33 -0
  106. package/dist/tui/app.d.ts +35 -0
  107. package/dist/tui/app.js +82 -0
  108. package/dist/tui/console-capture.d.ts +13 -0
  109. package/dist/tui/console-capture.js +62 -0
  110. package/dist/tui/dashboard.d.ts +42 -0
  111. package/dist/tui/dashboard.js +205 -0
  112. package/dist/tui/data.d.ts +57 -0
  113. package/dist/tui/data.js +62 -0
  114. package/dist/tui/harnesses.d.ts +16 -0
  115. package/dist/tui/harnesses.js +354 -0
  116. package/dist/tui/render.d.ts +21 -0
  117. package/dist/tui/render.js +116 -0
  118. package/dist/tui/requests.d.ts +18 -0
  119. package/dist/tui/requests.js +127 -0
  120. package/dist/tui/run.d.ts +13 -0
  121. package/dist/tui/run.js +178 -0
  122. package/dist/tui/transcript.d.ts +39 -0
  123. package/dist/tui/transcript.js +234 -0
  124. package/dist/tui.d.ts +1 -0
  125. package/dist/tui.js +314 -0
  126. package/dist/types.d.ts +65 -0
  127. package/dist/types.js +3 -0
  128. package/dist/win.d.ts +67 -0
  129. package/dist/win.js +317 -0
  130. package/package.json +60 -0
  131. package/scripts/fix-pty-helper.js +28 -0
  132. package/scripts/precompact-capture.js +292 -0
  133. package/scripts/smoke-test.sh +74 -0
@@ -0,0 +1,406 @@
1
+ "use strict";
2
+ // Best-effort probe of the binaries the runtime knows about. The result
3
+ // is JSON-shaped and posted up via runtime_heartbeat so the vault /
4
+ // studio UI can show live model lists, login status, and active
5
+ // account. Failures are silent — a missing binary or unauth'd CLI just
6
+ // won't show as an agent option in the pickers.
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.codexHome = codexHome;
42
+ exports.codexAuthFromStore = codexAuthFromStore;
43
+ exports.probeCapabilities = probeCapabilities;
44
+ const child_process_1 = require("child_process");
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const os = __importStar(require("os"));
48
+ const claude_binary_1 = require("./claude-binary");
49
+ const kimi_binary_1 = require("./kimi-binary");
50
+ const gemini_binary_1 = require("./gemini-binary");
51
+ const grok_binary_1 = require("./grok-binary");
52
+ const codex_binary_1 = require("./codex-binary");
53
+ const win_1 = require("./win");
54
+ const harness_registry_1 = require("./harness-registry");
55
+ function readDaemonVersion() {
56
+ try {
57
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
58
+ const pkg = require('../package.json');
59
+ return pkg.version ?? 'unknown';
60
+ }
61
+ catch {
62
+ return 'unknown';
63
+ }
64
+ }
65
+ function runCmd(cmd, args, timeoutMs = 5000) {
66
+ return new Promise(resolve => {
67
+ // On win32 the discovered binary may be an npm .cmd shim, which Node
68
+ // refuses to execFile without a shell (EINVAL, thrown SYNCHRONOUSLY).
69
+ // Resolve the shim to its node script first, and catch the sync throw —
70
+ // a single failing probe must not take down the whole heartbeat.
71
+ try {
72
+ const inv = (0, win_1.resolveCliInvocation)(cmd, args);
73
+ (0, child_process_1.execFile)(inv.file, inv.args, { timeout: timeoutMs, maxBuffer: 256 * 1024 }, (err, stdout, stderr) => {
74
+ resolve({ ok: !err, stdout: (stdout ?? '').trim(), stderr: (stderr ?? '').trim() });
75
+ });
76
+ }
77
+ catch (err) {
78
+ resolve({ ok: false, stdout: '', stderr: err.message ?? String(err) });
79
+ }
80
+ });
81
+ }
82
+ // Curated model lists baked into the daemon. The CLIs don't expose a
83
+ // stable "list models" command; the daemon reports whatever the
84
+ // installed daemon version knows about, so bumping the daemon refreshes
85
+ // the UI's dropdown.
86
+ const CLAUDE_MODELS = [
87
+ 'claude-opus-4-7',
88
+ 'claude-sonnet-4-6',
89
+ 'claude-haiku-4-5',
90
+ 'claude-opus-4-1',
91
+ 'claude-sonnet-4-5',
92
+ ];
93
+ const CODEX_MODELS = [
94
+ 'gpt-5',
95
+ 'gpt-5-mini',
96
+ 'o4',
97
+ 'o4-mini',
98
+ ];
99
+ const KIMI_MODELS = [
100
+ 'kimi-k2-instruct',
101
+ 'kimi-k2-base',
102
+ 'moonshot-v1-128k',
103
+ 'moonshot-v1-32k',
104
+ ];
105
+ const GEMINI_MODELS = [
106
+ 'gemini-2.5-flash',
107
+ 'gemini-2.5-pro',
108
+ 'gemini-2.0-flash',
109
+ 'gemini-2.0-pro',
110
+ ];
111
+ const GROK_MODELS = [
112
+ 'grok-4.5',
113
+ 'grok-composer-2.5-fast',
114
+ ];
115
+ async function probeClaude() {
116
+ const bin = (0, claude_binary_1.findClaudeBinary)();
117
+ if (!bin)
118
+ return { available: false, authed: false };
119
+ const ver = await runCmd(bin, ['--version']);
120
+ if (!ver.ok || !ver.stdout)
121
+ return { available: false, authed: false };
122
+ const version = ver.stdout.replace(/^claude\s+/i, '').trim() || undefined;
123
+ // `claude auth status` prints JSON describing the logged-in account.
124
+ // Example: {"loggedIn":true,"authMethod":"claude.ai","email":"a@b.c","subscriptionType":"max"}
125
+ const auth = await runCmd(bin, ['auth', 'status']);
126
+ let authShape = { authed: false };
127
+ if (auth.ok && auth.stdout) {
128
+ try {
129
+ const j = JSON.parse(auth.stdout);
130
+ authShape = {
131
+ authed: j.loggedIn === true,
132
+ account: typeof j.email === 'string' ? j.email : undefined,
133
+ accountKind: typeof j.authMethod === 'string' ? j.authMethod : undefined,
134
+ plan: typeof j.subscriptionType === 'string' ? j.subscriptionType : undefined,
135
+ };
136
+ }
137
+ catch { /* fall through unauthed */ }
138
+ }
139
+ return { available: true, version, models: CLAUDE_MODELS, ...authShape };
140
+ }
141
+ /** Codex's credential store: $CODEX_HOME (or ~/.codex) + /auth.json. */
142
+ function codexHome() {
143
+ return process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
144
+ }
145
+ /**
146
+ * Read auth state straight out of codex's credential file.
147
+ *
148
+ * `codex login status` cannot be trusted as the sole signal: on some
149
+ * installs it prints its "Logged in using ChatGPT" line only when stdio is
150
+ * a TTY and stays completely silent (exit 0, both streams empty) when the
151
+ * daemon runs it with pipes — so a logged-in node reported itself logged
152
+ * out forever, and remote logins "failed" verification after succeeding
153
+ * (live incident, Lennys-Mac-mini 2026-07-29). The file is written by the
154
+ * login flow itself, which makes it the fact rather than the commentary.
155
+ *
156
+ * Shape (values redacted):
157
+ * { auth_mode: 'chatgpt', OPENAI_API_KEY: null,
158
+ * tokens: { id_token, access_token, refresh_token, account_id },
159
+ * last_refresh }
160
+ *
161
+ * Never throws: an unreadable or malformed store simply isn't proof of a
162
+ * login. Same approach probeKimi already takes for its OAuth store.
163
+ */
164
+ function codexAuthFromStore(home) {
165
+ if (!home)
166
+ return { authed: false };
167
+ let parsed;
168
+ try {
169
+ parsed = JSON.parse(fs.readFileSync(path.join(home, 'auth.json'), 'utf8'));
170
+ }
171
+ catch {
172
+ return { authed: false };
173
+ }
174
+ const tokens = (parsed.tokens ?? null);
175
+ const nonEmpty = (v) => typeof v === 'string' && v.trim().length > 0;
176
+ if (tokens && (nonEmpty(tokens.access_token) || nonEmpty(tokens.refresh_token))) {
177
+ return { authed: true, accountKind: 'ChatGPT' };
178
+ }
179
+ if (nonEmpty(parsed.OPENAI_API_KEY))
180
+ return { authed: true, accountKind: 'api-key' };
181
+ return { authed: false };
182
+ }
183
+ async function probeCodex() {
184
+ // Resolve the real binary path — bare 'codex' relies on PATH search,
185
+ // which misses npm-global dirs under launchd and never finds .cmd shims
186
+ // on Windows.
187
+ const bin = (0, codex_binary_1.findCodexBinary)() ?? 'codex';
188
+ const ver = await runCmd(bin, ['--version']);
189
+ if (!ver.ok || !ver.stdout)
190
+ return { available: false, authed: false };
191
+ // `codex login status` is text-only AND writes to stderr (not stdout)
192
+ // when run headlessly. Logged-in output starts with "Logged in" and
193
+ // usually mentions how (e.g. "Logged in using ChatGPT"). When it speaks,
194
+ // it carries the nicest detail — how the account was authenticated — so
195
+ // prefer it, but never let its silence outvote the credential store.
196
+ const login = await runCmd(bin, ['login', 'status']);
197
+ const loginText = `${login.stdout}\n${login.stderr}`;
198
+ let authShape = { authed: false };
199
+ if (login.ok && /^\s*logged in/im.test(loginText)) {
200
+ const via = /using\s+(.+?)(?:[.\s]|$)/i.exec(loginText)?.[1]?.trim();
201
+ authShape = { authed: true, accountKind: via || 'codex' };
202
+ }
203
+ else {
204
+ authShape = codexAuthFromStore(codexHome());
205
+ }
206
+ return { available: true, version: ver.stdout, models: CODEX_MODELS, ...authShape };
207
+ }
208
+ async function probeKimi() {
209
+ const bin = (0, kimi_binary_1.findKimiBinary)();
210
+ if (!bin)
211
+ return { available: false, authed: false };
212
+ const ver = await runCmd(bin, ['--version']);
213
+ if (!ver.ok || !ver.stdout)
214
+ return { available: false, authed: false };
215
+ const version = ver.stdout.replace(/^kimi\s+/i, '').trim() || undefined;
216
+ // Auth signals, in order: env API key; kimi-code's OAuth credential
217
+ // store (~/.kimi-code/credentials/*.json with a refresh/access token —
218
+ // written by `kimi login`'s device-code flow); legacy ~/.kimi/config.
219
+ const envKey = process.env.MOONSHOT_API_KEY || process.env.KIMI_API_KEY;
220
+ let hasOauth = false;
221
+ try {
222
+ const credDir = path.join(os.homedir(), '.kimi-code', 'credentials');
223
+ for (const f of fs.readdirSync(credDir)) {
224
+ if (!f.endsWith('.json'))
225
+ continue;
226
+ const cred = JSON.parse(fs.readFileSync(path.join(credDir, f), 'utf-8'));
227
+ // Require a NON-EMPTY token, not just a present field — a logged-out
228
+ // CLI can leave the cred file with empty-string tokens.
229
+ const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
230
+ if (nonEmpty(cred.refresh_token) || nonEmpty(cred.access_token)) {
231
+ hasOauth = true;
232
+ break;
233
+ }
234
+ }
235
+ }
236
+ catch { /* no cred dir = not oauth-authed */ }
237
+ // Legacy ~/.kimi/config: the actual bug is an EMPTY/whitespace-only file
238
+ // (logged-out or half-installed CLI) showing the agent green and routing
239
+ // dispatch to a dead provider. Reject only that. Deliberately NOT
240
+ // keyword-gated: a working legacy login may store its token under any key
241
+ // (a bare `sk-…`, `"key":`, `"auth":`), and a keyword allowlist would flip
242
+ // a VALID config to unauthed and drop kimi off the ladder — a false-negative
243
+ // worse than the bug. A non-empty, non-empty-container file is trusted.
244
+ let hasConfig = false;
245
+ try {
246
+ const raw = fs.readFileSync(path.join(os.homedir(), '.kimi', 'config'), 'utf-8').trim();
247
+ hasConfig = raw.length > 0 && raw !== '{}' && raw !== '[]';
248
+ }
249
+ catch { /* missing / unreadable = not config-authed */ }
250
+ const authed = !!envKey || hasOauth || hasConfig;
251
+ return {
252
+ available: true, version, models: KIMI_MODELS,
253
+ authed,
254
+ accountKind: authed ? (envKey ? 'api-key' : hasOauth ? 'kimi-code' : 'config') : undefined,
255
+ };
256
+ }
257
+ async function probeGemini() {
258
+ const bin = (0, gemini_binary_1.findGeminiBinary)();
259
+ if (!bin)
260
+ return { available: false, authed: false };
261
+ const ver = await runCmd(bin, ['--version']);
262
+ if (!ver.ok || !ver.stdout)
263
+ return { available: false, authed: false };
264
+ const version = ver.stdout.replace(/^gemini\s+/i, '').trim() || undefined;
265
+ // Gemini CLI stores creds at ~/.gemini/oauth_creds.json and the
266
+ // active account email at ~/.gemini/google_accounts.json (`.active`).
267
+ const home = os.homedir();
268
+ const credsPath = path.join(home, '.gemini', 'oauth_creds.json');
269
+ const acctPath = path.join(home, '.gemini', 'google_accounts.json');
270
+ let authed = false;
271
+ let account;
272
+ let accountKind;
273
+ try {
274
+ if (fs.existsSync(credsPath)) {
275
+ const creds = JSON.parse(fs.readFileSync(credsPath, 'utf-8'));
276
+ const accessTok = typeof creds.access_token === 'string' ? creds.access_token : '';
277
+ const refreshTok = typeof creds.refresh_token === 'string' ? creds.refresh_token : '';
278
+ // Non-empty access token is the floor. Google oauth_creds carry an
279
+ // expiry_date (ms epoch); an expired access token is still usable IFF a
280
+ // refresh_token exists (the CLI refreshes transparently). Only downgrade
281
+ // to unauthed when expired AND there's nothing to refresh with. Absent /
282
+ // non-numeric expiry is treated as valid to avoid false-negatives on a
283
+ // working login.
284
+ const expiryMs = typeof creds.expiry_date === 'number' ? creds.expiry_date : undefined;
285
+ const expired = expiryMs !== undefined && expiryMs <= Date.now();
286
+ authed = accessTok.length > 0 && (!expired || refreshTok.length > 0);
287
+ if (authed)
288
+ accountKind = 'google';
289
+ }
290
+ if (fs.existsSync(acctPath)) {
291
+ const accts = JSON.parse(fs.readFileSync(acctPath, 'utf-8'));
292
+ if (typeof accts.active === 'string')
293
+ account = accts.active;
294
+ }
295
+ // API-key / Vertex logins live in ~/.gemini/.env (the CLI reads it).
296
+ if (!authed) {
297
+ const envPath = path.join(home, '.gemini', '.env');
298
+ if (fs.existsSync(envPath)) {
299
+ const env = fs.readFileSync(envPath, 'utf-8');
300
+ const has = (v) => new RegExp(`^${v}=.+`, 'm').test(env);
301
+ if (has('GOOGLE_API_KEY') && has('GOOGLE_GENAI_USE_VERTEXAI')) {
302
+ authed = true;
303
+ accountKind = 'vertex';
304
+ }
305
+ else if (has('GEMINI_API_KEY')) {
306
+ authed = true;
307
+ accountKind = 'api-key';
308
+ }
309
+ }
310
+ }
311
+ }
312
+ catch { /* unreadable creds = unauthed */ }
313
+ return {
314
+ available: true, version, models: GEMINI_MODELS,
315
+ authed, account, accountKind,
316
+ };
317
+ }
318
+ async function probeGrok() {
319
+ const bin = (0, grok_binary_1.findGrokBinary)();
320
+ if (!bin)
321
+ return { available: false, authed: false };
322
+ const ver = await runCmd(bin, ['--version']);
323
+ if (!ver.ok || !ver.stdout)
324
+ return { available: false, authed: false };
325
+ // "grok 0.2.101 (5bc4...) [stable]" → keep the semver.
326
+ const version = /(\d+\.\d+\.\d+)/.exec(ver.stdout)?.[1] ?? ver.stdout;
327
+ // The account lives in ~/.grok/auth.json, keyed by issuer::user-id. Each
328
+ // entry carries the OAuth token (`key`), a `refresh_token`, and the
329
+ // signed-in `email`. Presence of a refresh_token = the CLI can keep the
330
+ // session alive, so we treat that as authed (mirrors gemini's probe).
331
+ const authPath = path.join(os.homedir(), '.grok', 'auth.json');
332
+ let authShape = { authed: false };
333
+ try {
334
+ if (fs.existsSync(authPath)) {
335
+ const obj = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
336
+ // Require a NON-EMPTY token. A logged-out CLI can leave an entry whose
337
+ // refresh_token/key are empty strings, which the old typeof-string check
338
+ // accepted → agent shown green and the provider-ladder routed to a dead
339
+ // provider. refresh_token means the session can be kept alive; a bare
340
+ // key also counts.
341
+ const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
342
+ const entry = Object.values(obj).find(e => e && (nonEmpty(e.refresh_token) || nonEmpty(e.key)));
343
+ if (entry) {
344
+ authShape = {
345
+ authed: true,
346
+ account: typeof entry.email === 'string' ? entry.email : undefined,
347
+ accountKind: 'grok.com',
348
+ };
349
+ }
350
+ }
351
+ }
352
+ catch { /* unreadable creds = unauthed */ }
353
+ return { available: true, version, models: GROK_MODELS, ...authShape };
354
+ }
355
+ async function probeGit() {
356
+ const ver = await runCmd('git', ['--version']);
357
+ if (!ver.ok || !ver.stdout)
358
+ return { available: false, authed: false };
359
+ // Git creds live per-repo via helpers — there's no global "logged in"
360
+ // notion. Mark authed=true so the UI doesn't grey out a working git.
361
+ return {
362
+ available: true,
363
+ version: ver.stdout.replace(/^git version\s+/i, '').trim(),
364
+ authed: true,
365
+ };
366
+ }
367
+ async function probeCapabilities() {
368
+ // Binary lookups cache misses; a CLI installed mid-daemon-life (by the
369
+ // command runner or by hand) must show up on the next probe, not after
370
+ // a daemon restart.
371
+ (0, harness_registry_1.resetBinaryCaches)();
372
+ // Each probe is individually shielded: one throwing probe (e.g. a broken
373
+ // CLI install) must not reject the whole Promise.all and kill every
374
+ // heartbeat — that would report the runtime permanently offline.
375
+ const shield = (p, fallback) => p.catch(() => fallback);
376
+ const unavailable = { available: false, authed: false };
377
+ const [claude, codex, kimi, gemini, grok, git] = await Promise.all([
378
+ shield(probeClaude(), unavailable),
379
+ shield(probeCodex(), unavailable),
380
+ shield(probeKimi(), unavailable),
381
+ shield(probeGemini(), unavailable),
382
+ shield(probeGrok(), unavailable),
383
+ shield(probeGit(), unavailable),
384
+ ]);
385
+ // Decorate each harness with its registry facts (efforts, installability,
386
+ // login strategy) so Studio and the TUI render from one source of truth.
387
+ const deco = (id, probe) => ({
388
+ ...probe,
389
+ efforts: harness_registry_1.HARNESSES[id].efforts,
390
+ installable: (0, harness_registry_1.isInstallable)(id),
391
+ login_strategy: harness_registry_1.HARNESSES[id].login.strategy,
392
+ login_methods: [
393
+ harness_registry_1.HARNESSES[id].login.strategy,
394
+ ...(harness_registry_1.HARNESSES[id].extraLogins ?? []).map(m => m.method),
395
+ ],
396
+ });
397
+ return {
398
+ daemon_version: readDaemonVersion(),
399
+ claude: deco('claude', claude),
400
+ codex: deco('codex', codex),
401
+ kimi: deco('kimi', kimi),
402
+ gemini: deco('gemini', gemini),
403
+ grok: deco('grok', grok),
404
+ git,
405
+ };
406
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Find the Claude Code CLI binary. Mirrors the resolution strategy
3
+ * the desktop app uses, so the runtime spawns the same Claude the
4
+ * user already has installed.
5
+ *
6
+ * Returns `null` when claude isn't installed — callers MUST NOT spawn a
7
+ * bare `'claude'` literal, which throws an async ENOENT with no 'error'
8
+ * listener attached in time and takes the whole daemon down (observed on
9
+ * a Windows box with only grok installed: a self-improve/capture distil
10
+ * request tried to run the Haiku summarizer via claude → crash). Mirrors
11
+ * findCodexBinary/findGrokBinary, which already return `null`.
12
+ */
13
+ export declare function findClaudeBinary(): string | null;
14
+ /** Encode a working directory the same way Claude does for its
15
+ * per-project transcript folder (`~/.claude/projects/<encoded>/`). */
16
+ export declare function encodeProjectPath(dirPath: string): string;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.findClaudeBinary = findClaudeBinary;
37
+ exports.encodeProjectPath = encodeProjectPath;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const child_process_1 = require("child_process");
41
+ const IS_WINDOWS = process.platform === 'win32';
42
+ /**
43
+ * Find the Claude Code CLI binary. Mirrors the resolution strategy
44
+ * the desktop app uses, so the runtime spawns the same Claude the
45
+ * user already has installed.
46
+ *
47
+ * Returns `null` when claude isn't installed — callers MUST NOT spawn a
48
+ * bare `'claude'` literal, which throws an async ENOENT with no 'error'
49
+ * listener attached in time and takes the whole daemon down (observed on
50
+ * a Windows box with only grok installed: a self-improve/capture distil
51
+ * request tried to run the Haiku summarizer via claude → crash). Mirrors
52
+ * findCodexBinary/findGrokBinary, which already return `null`.
53
+ */
54
+ function findClaudeBinary() {
55
+ if (IS_WINDOWS) {
56
+ try {
57
+ // `where` lists every match; npm installs three shims side by side
58
+ // (extensionless bash script, .cmd, .ps1). Prefer a native .exe
59
+ // (installer builds), then .cmd — never the extensionless one,
60
+ // which CreateProcess can't run.
61
+ const lines = (0, child_process_1.execSync)('where claude', { encoding: 'utf-8', timeout: 5000 })
62
+ .split(/\r?\n/)
63
+ .map(l => l.trim())
64
+ .filter(Boolean);
65
+ const result = lines.find(l => /\.exe$/i.test(l)) ??
66
+ lines.find(l => /\.cmd$/i.test(l)) ??
67
+ lines.find(l => /\.bat$/i.test(l));
68
+ if (result && fs.existsSync(result))
69
+ return result;
70
+ }
71
+ catch { }
72
+ const appData = process.env.APPDATA || '';
73
+ const localAppData = process.env.LOCALAPPDATA || '';
74
+ const userProfile = process.env.USERPROFILE || '';
75
+ const candidates = [
76
+ path.join(userProfile, '.local', 'bin', 'claude.exe'), // native installer default
77
+ path.join(appData, 'npm', 'claude.cmd'),
78
+ path.join(localAppData, 'npm', 'claude.cmd'),
79
+ path.join(userProfile, '.npm-global', 'claude.cmd'),
80
+ path.join(userProfile, 'AppData', 'Roaming', 'npm', 'claude.cmd'),
81
+ ];
82
+ for (const p of candidates) {
83
+ try {
84
+ if (fs.existsSync(p))
85
+ return p;
86
+ }
87
+ catch { }
88
+ }
89
+ }
90
+ else {
91
+ const shell = process.env.SHELL || '/bin/zsh';
92
+ // Resolve through the user's shell. We try an *interactive* login shell
93
+ // (`-i -l`) so that a `claude` alias/function defined in the interactive
94
+ // section of ~/.zshrc is visible — a plain `-l -c` shell is
95
+ // non-interactive and never sources it, which is how the runtime ended up
96
+ // spawning the bare `claude` literal (ENOENT) on machines where claude is
97
+ // only reachable via an alias. `command -v` follows aliases/functions to
98
+ // their target path; fall back to `which` for older setups.
99
+ const probes = [
100
+ `${shell} -i -l -c "command -v claude" 2>/dev/null`,
101
+ `${shell} -l -c "command -v claude" 2>/dev/null`,
102
+ `${shell} -l -c "which claude"`,
103
+ ];
104
+ for (const cmd of probes) {
105
+ try {
106
+ const result = (0, child_process_1.execSync)(cmd, { encoding: 'utf-8', timeout: 5000 })
107
+ .trim()
108
+ .split('\n')
109
+ .pop()
110
+ .trim();
111
+ if (result && result.startsWith('/') && fs.existsSync(result))
112
+ return result;
113
+ }
114
+ catch { }
115
+ }
116
+ const home = process.env.HOME || '';
117
+ const candidates = [
118
+ `${home}/.local/bin/claude`, // native installer default
119
+ `${home}/.claude/local/claude`, // local / migrate-installer install
120
+ '/usr/local/bin/claude',
121
+ '/opt/homebrew/bin/claude',
122
+ `${home}/.npm-global/bin/claude`,
123
+ `${home}/.bun/bin/claude`,
124
+ `${home}/.volta/bin/claude`,
125
+ `${home}/.nvm/current/bin/claude`,
126
+ ];
127
+ for (const p of candidates) {
128
+ try {
129
+ if (fs.existsSync(p))
130
+ return p;
131
+ }
132
+ catch { }
133
+ }
134
+ }
135
+ // Not found by any probe. Return null (NOT a bare 'claude' literal): a
136
+ // bare spawn would ENOENT and crash the daemon. The old fallback never
137
+ // actually worked — if PATH could run 'claude', the probes above (same
138
+ // env) would have found it.
139
+ return null;
140
+ }
141
+ /** Encode a working directory the same way Claude does for its
142
+ * per-project transcript folder (`~/.claude/projects/<encoded>/`). */
143
+ function encodeProjectPath(dirPath) {
144
+ return dirPath.replace(/[^a-zA-Z0-9-]/g, '-');
145
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Make sure ~/.claude/settings.json has skipDangerousModePermissionPrompt: true.
3
+ * Idempotent: if the key is already true, no write.
4
+ */
5
+ export declare function ensureBypassPermissionsAccepted(): void;
6
+ /**
7
+ * Add an entry to ~/.claude.json `.projects[cwd]` marking the cwd as
8
+ * trusted and onboarding-seen, so claude TUI doesn't block on either
9
+ * dialog when launched in this directory. Existing fields are preserved.
10
+ */
11
+ export declare function preTrustProject(cwd: string): void;