sessionpipe-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/control.js +352 -0
- package/dist/control.js.map +7 -0
- package/dist/index.js +600 -97
- package/dist/index.js.map +4 -4
- package/dist/schema/v1.d.ts +340 -53
- package/dist/schema/v1.d.ts.map +1 -1
- package/dist/schema.js +151 -24
- package/dist/schema.js.map +2 -2
- package/dist/src/adapters/antigravity.d.ts.map +1 -1
- package/dist/src/adapters/claude-code-control.d.ts +70 -0
- package/dist/src/adapters/claude-code-control.d.ts.map +1 -0
- package/dist/src/adapters/claude-code.d.ts +1 -1
- package/dist/src/adapters/claude-code.d.ts.map +1 -1
- package/dist/src/adapters/claude-shaped.d.ts +38 -2
- package/dist/src/adapters/claude-shaped.d.ts.map +1 -1
- package/dist/src/adapters/codex.d.ts.map +1 -1
- package/dist/src/adapters/gemini-cli.d.ts +5 -0
- package/dist/src/adapters/gemini-cli.d.ts.map +1 -1
- package/dist/src/adapters/index.d.ts +2 -0
- package/dist/src/adapters/index.d.ts.map +1 -1
- package/dist/src/adapters/types.d.ts +23 -7
- package/dist/src/adapters/types.d.ts.map +1 -1
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/control/index.d.ts +5 -0
- package/dist/src/control/index.d.ts.map +1 -0
- package/dist/src/control/verify.d.ts +171 -0
- package/dist/src/control/verify.d.ts.map +1 -0
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
// sessionpipe-core 0.
|
|
1
|
+
// sessionpipe-core 0.2.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
2
7
|
|
|
3
8
|
// src/adapters/antigravity.ts
|
|
4
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
existsSync as existsSync2,
|
|
11
|
+
mkdirSync as mkdirSync2,
|
|
12
|
+
readdirSync as readdirSync2,
|
|
13
|
+
readFileSync as readFileSync2,
|
|
14
|
+
realpathSync,
|
|
15
|
+
statSync as statSync2,
|
|
16
|
+
unlinkSync as unlinkSync2,
|
|
17
|
+
writeFileSync as writeFileSync2
|
|
18
|
+
} from "node:fs";
|
|
5
19
|
import path4 from "node:path";
|
|
6
20
|
|
|
7
21
|
// src/paths.ts
|
|
@@ -228,28 +242,67 @@ function readAntigravity(lines, from) {
|
|
|
228
242
|
}
|
|
229
243
|
|
|
230
244
|
// src/adapters/claude-shaped.ts
|
|
231
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
245
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
232
246
|
import path3 from "node:path";
|
|
233
247
|
var OURS = /sessionpipe[^"\n]*[\\/]hook\.js"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
|
|
234
248
|
var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
|
|
235
|
-
function
|
|
249
|
+
function readJsonFile(file) {
|
|
250
|
+
let raw;
|
|
236
251
|
try {
|
|
237
|
-
|
|
252
|
+
raw = readFileSync(file, "utf8");
|
|
238
253
|
} catch {
|
|
239
|
-
return {};
|
|
254
|
+
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
|
|
240
255
|
}
|
|
256
|
+
let value;
|
|
257
|
+
try {
|
|
258
|
+
value = JSON.parse(raw);
|
|
259
|
+
} catch {
|
|
260
|
+
const hint = /^\s*\/\/|\/\*/m.test(raw) ? "not plain JSON (it has comments)" : "not plain JSON";
|
|
261
|
+
return { ok: false, existed: true, reason: hint };
|
|
262
|
+
}
|
|
263
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
264
|
+
return { ok: false, existed: true, reason: "not a JSON object" };
|
|
265
|
+
const eol = raw.includes("\r\n") ? "\r\n" : "\n";
|
|
266
|
+
const m = /^( +|\t+)\S/m.exec(raw);
|
|
267
|
+
const indent = m?.[1] ?? " ";
|
|
268
|
+
return {
|
|
269
|
+
ok: true,
|
|
270
|
+
existed: true,
|
|
271
|
+
value,
|
|
272
|
+
raw,
|
|
273
|
+
indent,
|
|
274
|
+
eol,
|
|
275
|
+
finalNewline: /\r?\n$/.test(raw)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function formatJson(value, f) {
|
|
279
|
+
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
280
|
+
return f.finalNewline ? text + f.eol : text;
|
|
241
281
|
}
|
|
242
282
|
function entryFor(event, harness, cmd, o) {
|
|
243
283
|
const matcher = o.matcherFor(event);
|
|
244
284
|
return {
|
|
245
285
|
...matcher !== void 0 ? { matcher } : {},
|
|
246
|
-
hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeout }]
|
|
286
|
+
hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeoutFor?.(event) ?? o.timeout }]
|
|
247
287
|
};
|
|
248
288
|
}
|
|
249
289
|
function installClaudeShaped(file, harness, events, cmd, o) {
|
|
250
|
-
const
|
|
290
|
+
const f = readJsonFile(file);
|
|
291
|
+
if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
|
|
292
|
+
const settings2 = f.value;
|
|
251
293
|
const hooks = settings2.hooks && typeof settings2.hooks === "object" ? settings2.hooks : {};
|
|
252
294
|
let changed = false;
|
|
295
|
+
const want = new Set(events);
|
|
296
|
+
for (const ev of Object.keys(hooks)) {
|
|
297
|
+
if (want.has(ev)) continue;
|
|
298
|
+
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
299
|
+
const kept = list.filter((h) => !isOurs(h));
|
|
300
|
+
if (kept.length !== list.length) {
|
|
301
|
+
changed = true;
|
|
302
|
+
if (kept.length) hooks[ev] = kept;
|
|
303
|
+
else delete hooks[ev];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
253
306
|
for (const ev of events) {
|
|
254
307
|
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
255
308
|
const entry2 = entryFor(ev, harness, cmd, o);
|
|
@@ -268,19 +321,19 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
268
321
|
if (changed) {
|
|
269
322
|
settings2.hooks = hooks;
|
|
270
323
|
mkdirSync(path3.dirname(file), { recursive: true });
|
|
271
|
-
writeFileSync(file,
|
|
272
|
-
`);
|
|
324
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
273
325
|
}
|
|
274
|
-
return { file, changed };
|
|
326
|
+
return { file, changed, created: !f.existed && changed };
|
|
275
327
|
}
|
|
276
|
-
function uninstallClaudeShaped(file, events) {
|
|
328
|
+
function uninstallClaudeShaped(file, events, opts = {}) {
|
|
277
329
|
if (!existsSync(file)) return { file, changed: false };
|
|
278
|
-
const
|
|
279
|
-
|
|
330
|
+
const f = readJsonFile(file);
|
|
331
|
+
if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
|
|
332
|
+
const settings2 = f.value;
|
|
280
333
|
const hooks = settings2.hooks;
|
|
281
334
|
if (!hooks || typeof hooks !== "object") return { file, changed: false };
|
|
282
335
|
let changed = false;
|
|
283
|
-
for (const ev of events) {
|
|
336
|
+
for (const ev of [...events, ...Object.keys(hooks)]) {
|
|
284
337
|
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
285
338
|
const kept = list.filter((h) => !isOurs(h));
|
|
286
339
|
if (kept.length !== list.length) {
|
|
@@ -291,13 +344,17 @@ function uninstallClaudeShaped(file, events) {
|
|
|
291
344
|
}
|
|
292
345
|
if (!changed) return { file, changed: false };
|
|
293
346
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
347
|
+
if (opts.created && !Object.keys(settings2).length) {
|
|
348
|
+
unlinkSync(file);
|
|
349
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
350
|
+
}
|
|
351
|
+
const next = formatJson(settings2, f);
|
|
352
|
+
if (next !== f.raw) writeFileSync(file, next);
|
|
297
353
|
return { file, changed: true };
|
|
298
354
|
}
|
|
299
355
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
300
|
-
const
|
|
356
|
+
const f = readJsonFile(file);
|
|
357
|
+
const hooks = f.ok ? f.value.hooks ?? {} : {};
|
|
301
358
|
let present = 0;
|
|
302
359
|
let current = 0;
|
|
303
360
|
for (const ev of events) {
|
|
@@ -306,6 +363,9 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
|
306
363
|
if (mine.length) present++;
|
|
307
364
|
if (mine.length === 1 && JSON.stringify(mine[0]) === JSON.stringify(entryFor(ev, harness, cmd, o))) current++;
|
|
308
365
|
}
|
|
366
|
+
for (const ev of Object.keys(hooks))
|
|
367
|
+
if (!events.includes(ev) && (Array.isArray(hooks[ev]) ? hooks[ev] : []).some((h) => isOurs(h)))
|
|
368
|
+
return "stale";
|
|
309
369
|
if (!present) return "missing";
|
|
310
370
|
return current === events.length ? "current" : "stale";
|
|
311
371
|
}
|
|
@@ -320,10 +380,13 @@ var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
|
320
380
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
321
381
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
322
382
|
var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
323
|
-
function entry(cmd) {
|
|
383
|
+
function entry(cmd, lean) {
|
|
324
384
|
const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
|
|
325
385
|
const out = { enabled: true };
|
|
326
|
-
for (const e of AG_EVENTS)
|
|
386
|
+
for (const e of AG_EVENTS) {
|
|
387
|
+
if (lean && e === "PreToolUse") continue;
|
|
388
|
+
out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
389
|
+
}
|
|
327
390
|
return out;
|
|
328
391
|
}
|
|
329
392
|
var antigravity = {
|
|
@@ -331,34 +394,49 @@ var antigravity = {
|
|
|
331
394
|
events: AG_EVENTS,
|
|
332
395
|
detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
|
|
333
396
|
configFiles: (env = process.env) => [hooksFile(env)],
|
|
334
|
-
install(cmd, env = process.env) {
|
|
397
|
+
install(cmd, env = process.env, opts = {}) {
|
|
335
398
|
const file = hooksFile(env);
|
|
336
|
-
const
|
|
337
|
-
|
|
399
|
+
const f = readJsonFile(file);
|
|
400
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
401
|
+
const hooks = f.value;
|
|
402
|
+
const e = entry(cmd, opts.lean);
|
|
338
403
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
339
404
|
hooks[HOOK_NAME] = e;
|
|
340
405
|
mkdirSync2(path4.dirname(file), { recursive: true });
|
|
341
|
-
writeFileSync2(file,
|
|
342
|
-
`);
|
|
406
|
+
writeFileSync2(file, formatJson(hooks, f));
|
|
343
407
|
return [
|
|
344
|
-
{
|
|
408
|
+
{
|
|
409
|
+
file,
|
|
410
|
+
changed: true,
|
|
411
|
+
created: !f.existed,
|
|
412
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
413
|
+
}
|
|
345
414
|
];
|
|
346
415
|
},
|
|
347
|
-
uninstall(env = process.env) {
|
|
416
|
+
uninstall(env = process.env, opts = {}) {
|
|
348
417
|
const file = hooksFile(env);
|
|
349
418
|
if (!existsSync2(file)) return [{ file, changed: false }];
|
|
350
|
-
const
|
|
419
|
+
const f = readJsonFile(file);
|
|
420
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
421
|
+
const hooks = f.value;
|
|
351
422
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
352
423
|
delete hooks[HOOK_NAME];
|
|
353
|
-
|
|
354
|
-
|
|
424
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
425
|
+
unlinkSync2(file);
|
|
426
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
427
|
+
}
|
|
428
|
+
writeFileSync2(file, formatJson(hooks, f));
|
|
355
429
|
return [{ file, changed: true }];
|
|
356
430
|
},
|
|
357
|
-
installed(cmd, env = process.env) {
|
|
431
|
+
installed(cmd, env = process.env, opts = {}) {
|
|
358
432
|
const file = hooksFile(env);
|
|
359
|
-
const
|
|
433
|
+
const f = readJsonFile(file);
|
|
434
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
360
435
|
return [
|
|
361
|
-
{
|
|
436
|
+
{
|
|
437
|
+
file,
|
|
438
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd, opts.lean)) ? "current" : "stale"
|
|
439
|
+
}
|
|
362
440
|
];
|
|
363
441
|
},
|
|
364
442
|
fromHook(input) {
|
|
@@ -624,9 +702,18 @@ var CLAUDE_EVENTS = [
|
|
|
624
702
|
"SubagentStop",
|
|
625
703
|
"PreCompact",
|
|
626
704
|
"PostCompact",
|
|
627
|
-
"SessionEnd"
|
|
705
|
+
"SessionEnd",
|
|
706
|
+
"StopFailure"
|
|
628
707
|
];
|
|
629
|
-
var
|
|
708
|
+
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
709
|
+
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
710
|
+
var OPTS = {
|
|
711
|
+
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
712
|
+
timeout: 5,
|
|
713
|
+
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
714
|
+
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
715
|
+
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
716
|
+
};
|
|
630
717
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
631
718
|
function claudeDirs(env = process.env, home = HOME) {
|
|
632
719
|
const out = [];
|
|
@@ -657,11 +744,13 @@ var claudeCode = {
|
|
|
657
744
|
events: CLAUDE_EVENTS,
|
|
658
745
|
detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
|
|
659
746
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
660
|
-
install: (cmd, env = process.env) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2,
|
|
661
|
-
uninstall: (env = process.env) => claudeDirs(env).map(
|
|
662
|
-
|
|
747
|
+
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
|
|
748
|
+
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
|
|
749
|
+
(d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
|
|
750
|
+
),
|
|
751
|
+
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
663
752
|
file: settingsIn(d),
|
|
664
|
-
state: installedClaudeShaped(settingsIn(d), NAME2,
|
|
753
|
+
state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
|
|
665
754
|
})),
|
|
666
755
|
fromHook(input) {
|
|
667
756
|
const event = input.argv[1] ?? "";
|
|
@@ -752,6 +841,15 @@ var claudeCode = {
|
|
|
752
841
|
case "Stop":
|
|
753
842
|
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
754
843
|
break;
|
|
844
|
+
case "StopFailure":
|
|
845
|
+
events.push(
|
|
846
|
+
ev("turn.ended", {
|
|
847
|
+
turn_id,
|
|
848
|
+
reason: "error",
|
|
849
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
850
|
+
})
|
|
851
|
+
);
|
|
852
|
+
break;
|
|
755
853
|
case "SubagentStart":
|
|
756
854
|
events.push(
|
|
757
855
|
ev("subagent.started", {
|
|
@@ -1263,6 +1361,7 @@ var CODEX_EVENTS = [
|
|
|
1263
1361
|
"SessionEnd"
|
|
1264
1362
|
];
|
|
1265
1363
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1364
|
+
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1266
1365
|
var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
|
|
1267
1366
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1268
1367
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
@@ -1311,7 +1410,8 @@ function installNotify(cmd, env) {
|
|
|
1311
1410
|
return {
|
|
1312
1411
|
file,
|
|
1313
1412
|
changed: false,
|
|
1314
|
-
|
|
1413
|
+
skipped: true,
|
|
1414
|
+
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. Codex will report nothing until you approve the hooks: start \`codex\` and accept the hook review`
|
|
1315
1415
|
};
|
|
1316
1416
|
}
|
|
1317
1417
|
write(withNotify(text, line));
|
|
@@ -1368,14 +1468,21 @@ var codex = {
|
|
|
1368
1468
|
events: CODEX_EVENTS,
|
|
1369
1469
|
detect: (env = process.env) => existsSync4(codexHome(env)),
|
|
1370
1470
|
configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
|
|
1371
|
-
install: (cmd, env = process.env) => [
|
|
1372
|
-
installClaudeShaped(hooksFile2(env), NAME3,
|
|
1471
|
+
install: (cmd, env = process.env, opts = {}) => [
|
|
1472
|
+
installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1373
1473
|
installNotify(cmd, env)
|
|
1374
1474
|
],
|
|
1375
|
-
uninstall: (env = process.env) => [
|
|
1376
|
-
|
|
1475
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1476
|
+
uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
|
|
1477
|
+
uninstallNotify(env)
|
|
1478
|
+
],
|
|
1479
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1377
1480
|
const out = [
|
|
1378
|
-
{
|
|
1481
|
+
{
|
|
1482
|
+
file: hooksFile2(env),
|
|
1483
|
+
state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1484
|
+
note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
|
|
1485
|
+
}
|
|
1379
1486
|
];
|
|
1380
1487
|
let text = "";
|
|
1381
1488
|
try {
|
|
@@ -1563,6 +1670,7 @@ function codexFacts(file) {
|
|
|
1563
1670
|
}
|
|
1564
1671
|
|
|
1565
1672
|
// src/adapters/gemini-cli.ts
|
|
1673
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1566
1674
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1567
1675
|
import path7 from "node:path";
|
|
1568
1676
|
var NAME4 = "gemini-cli";
|
|
@@ -1580,17 +1688,61 @@ var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
|
1580
1688
|
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
|
|
1581
1689
|
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
1582
1690
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1691
|
+
var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1692
|
+
function geminiHooksActive(settingsFile) {
|
|
1693
|
+
let tools;
|
|
1694
|
+
try {
|
|
1695
|
+
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
1696
|
+
} catch {
|
|
1697
|
+
}
|
|
1698
|
+
if (tools?.enableHooks === true) return { active: true };
|
|
1699
|
+
if (tools?.enableHooks === false)
|
|
1700
|
+
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1701
|
+
let version = "";
|
|
1702
|
+
try {
|
|
1703
|
+
version = execFileSync3("gemini", ["--version"], {
|
|
1704
|
+
encoding: "utf8",
|
|
1705
|
+
timeout: 4e3,
|
|
1706
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1707
|
+
}).trim();
|
|
1708
|
+
} catch {
|
|
1709
|
+
return {
|
|
1710
|
+
active: true,
|
|
1711
|
+
note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
const m = /(\d+)\.(\d+)/.exec(version);
|
|
1715
|
+
if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
|
|
1716
|
+
return {
|
|
1717
|
+
active: false,
|
|
1718
|
+
note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1583
1721
|
var geminiCli = {
|
|
1584
1722
|
name: NAME4,
|
|
1585
1723
|
events: GEMINI_EVENTS,
|
|
1586
1724
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1587
1725
|
detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
|
|
1588
1726
|
configFiles: (env = process.env) => [settings(env)],
|
|
1589
|
-
install: (cmd, env = process.env
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1727
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1728
|
+
const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1729
|
+
const a = geminiHooksActive(settings(env));
|
|
1730
|
+
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1731
|
+
},
|
|
1732
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1733
|
+
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1593
1734
|
],
|
|
1735
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1736
|
+
const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1737
|
+
const a = geminiHooksActive(settings(env));
|
|
1738
|
+
return [
|
|
1739
|
+
{
|
|
1740
|
+
file: settings(env),
|
|
1741
|
+
state: state === "current" && !a.active ? "inactive" : state,
|
|
1742
|
+
...a.note ? { note: a.note } : {}
|
|
1743
|
+
}
|
|
1744
|
+
];
|
|
1745
|
+
},
|
|
1594
1746
|
fromHook(input) {
|
|
1595
1747
|
const event = input.argv[1] ?? "";
|
|
1596
1748
|
let s = {};
|
|
@@ -1702,17 +1854,366 @@ function prune4(o) {
|
|
|
1702
1854
|
return o;
|
|
1703
1855
|
}
|
|
1704
1856
|
|
|
1857
|
+
// src/adapters/claude-code-control.ts
|
|
1858
|
+
var claude_code_control_exports = {};
|
|
1859
|
+
__export(claude_code_control_exports, {
|
|
1860
|
+
JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
|
|
1861
|
+
claudeEnv: () => claudeEnv,
|
|
1862
|
+
detectCaps: () => detectCaps,
|
|
1863
|
+
findClaude: () => findClaude,
|
|
1864
|
+
findTranscript: () => findTranscript,
|
|
1865
|
+
isUuid: () => isUuid,
|
|
1866
|
+
liveProcess: () => liveProcess,
|
|
1867
|
+
modeFlags: () => modeFlags,
|
|
1868
|
+
parseCaps: () => parseCaps,
|
|
1869
|
+
parseResult: () => parseResult,
|
|
1870
|
+
recentlyWritten: () => recentlyWritten,
|
|
1871
|
+
runClaude: () => runClaude,
|
|
1872
|
+
sessionFolder: () => sessionFolder
|
|
1873
|
+
});
|
|
1874
|
+
import { execFileSync as execFileSync4, spawn } from "node:child_process";
|
|
1875
|
+
import { accessSync, closeSync as closeSync2, constants, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync6, readSync as readSync2, statSync as statSync4 } from "node:fs";
|
|
1876
|
+
import os2 from "node:os";
|
|
1877
|
+
import path8 from "node:path";
|
|
1878
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1879
|
+
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1880
|
+
var OUT_CAP = 1024 * 1024;
|
|
1881
|
+
var LIVE_WRITE_MS = 9e4;
|
|
1882
|
+
function isExe(p) {
|
|
1883
|
+
try {
|
|
1884
|
+
if (!statSync4(p).isFile()) return false;
|
|
1885
|
+
accessSync(p, constants.X_OK);
|
|
1886
|
+
return true;
|
|
1887
|
+
} catch {
|
|
1888
|
+
return false;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
var cmpVersion = (a, b) => {
|
|
1892
|
+
const pa = a.split(".").map(Number);
|
|
1893
|
+
const pb = b.split(".").map(Number);
|
|
1894
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++)
|
|
1895
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1896
|
+
return 0;
|
|
1897
|
+
};
|
|
1898
|
+
function findClaude(env = process.env, home = os2.homedir()) {
|
|
1899
|
+
if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
|
|
1900
|
+
const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
|
|
1901
|
+
for (const d of String(env.PATH || "").split(path8.delimiter)) {
|
|
1902
|
+
if (!d || !path8.isAbsolute(d)) continue;
|
|
1903
|
+
for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
|
|
1904
|
+
}
|
|
1905
|
+
for (const p of [
|
|
1906
|
+
path8.join(home, ".claude", "local", "claude"),
|
|
1907
|
+
path8.join(home, ".local", "bin", "claude"),
|
|
1908
|
+
"/opt/homebrew/bin/claude",
|
|
1909
|
+
"/usr/local/bin/claude"
|
|
1910
|
+
])
|
|
1911
|
+
if (isExe(p)) return p;
|
|
1912
|
+
const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
1913
|
+
let versions = [];
|
|
1914
|
+
try {
|
|
1915
|
+
versions = readdirSync5(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
for (const v of versions) {
|
|
1919
|
+
const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
|
|
1920
|
+
if (isExe(p)) return p;
|
|
1921
|
+
}
|
|
1922
|
+
return null;
|
|
1923
|
+
}
|
|
1924
|
+
function optionBlock(help, flag) {
|
|
1925
|
+
const lines = help.split("\n");
|
|
1926
|
+
const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag.replace(/-/g, "\\-")}(?![\\w-])`);
|
|
1927
|
+
const start = lines.findIndex((l) => re.test(l));
|
|
1928
|
+
if (start < 0) return null;
|
|
1929
|
+
const out = [lines[start]];
|
|
1930
|
+
for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
|
|
1931
|
+
out.push(lines[i]);
|
|
1932
|
+
return out.join(" ");
|
|
1933
|
+
}
|
|
1934
|
+
function parseCaps(help) {
|
|
1935
|
+
const pm = optionBlock(help, "--permission-mode");
|
|
1936
|
+
const pp = optionBlock(help, "--permission-prompts");
|
|
1937
|
+
return {
|
|
1938
|
+
hasMode: !!pm,
|
|
1939
|
+
modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
|
|
1940
|
+
promptsNone: !!pp && /"none"/.test(pp),
|
|
1941
|
+
fork: !!optionBlock(help, "--fork-session")
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
function modeFlags(mode, caps) {
|
|
1945
|
+
if (mode === "auto") {
|
|
1946
|
+
if (!caps.modes.includes("auto"))
|
|
1947
|
+
return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
|
|
1948
|
+
return { args: ["--permission-mode", "auto"] };
|
|
1949
|
+
}
|
|
1950
|
+
if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
|
|
1951
|
+
const args = [];
|
|
1952
|
+
if (caps.hasMode)
|
|
1953
|
+
args.push(
|
|
1954
|
+
"--permission-mode",
|
|
1955
|
+
caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
|
|
1956
|
+
);
|
|
1957
|
+
if (caps.promptsNone) args.push("--permission-prompts", "none");
|
|
1958
|
+
return { args };
|
|
1959
|
+
}
|
|
1960
|
+
function claudeEnv(configDir, env = process.env) {
|
|
1961
|
+
const e = { ...env };
|
|
1962
|
+
for (const k of [
|
|
1963
|
+
"CLAUDECODE",
|
|
1964
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
1965
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
1966
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
1967
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
1968
|
+
"CLAUDE_CODE_SSE_PORT"
|
|
1969
|
+
])
|
|
1970
|
+
delete e[k];
|
|
1971
|
+
e.SESSIONPIPE_CONTROL_JOB = "1";
|
|
1972
|
+
if (configDir !== void 0) {
|
|
1973
|
+
if (path8.resolve(configDir) === path8.resolve(os2.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
|
|
1974
|
+
else e.CLAUDE_CONFIG_DIR = configDir;
|
|
1975
|
+
}
|
|
1976
|
+
return e;
|
|
1977
|
+
}
|
|
1978
|
+
function detectCaps(bin) {
|
|
1979
|
+
let help = "";
|
|
1980
|
+
try {
|
|
1981
|
+
help = execFileSync4(bin, ["--help"], {
|
|
1982
|
+
encoding: "utf8",
|
|
1983
|
+
timeout: 3e4,
|
|
1984
|
+
env: claudeEnv(),
|
|
1985
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1986
|
+
maxBuffer: 4 * 1024 * 1024
|
|
1987
|
+
});
|
|
1988
|
+
} catch (e) {
|
|
1989
|
+
help = String(e?.stdout || "");
|
|
1990
|
+
}
|
|
1991
|
+
return parseCaps(help);
|
|
1992
|
+
}
|
|
1993
|
+
function findTranscript(session, dirs) {
|
|
1994
|
+
if (!UUID_RE.test(session)) return null;
|
|
1995
|
+
for (const dir of dirs) {
|
|
1996
|
+
let projects = [];
|
|
1997
|
+
try {
|
|
1998
|
+
projects = readdirSync5(path8.join(dir, "projects"), { withFileTypes: true });
|
|
1999
|
+
} catch {
|
|
2000
|
+
continue;
|
|
2001
|
+
}
|
|
2002
|
+
for (const p of projects) {
|
|
2003
|
+
if (!p.isDirectory()) continue;
|
|
2004
|
+
const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
|
|
2005
|
+
try {
|
|
2006
|
+
if (statSync4(file).isFile()) return { file, configDir: dir, project: p.name };
|
|
2007
|
+
} catch {
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
return null;
|
|
2012
|
+
}
|
|
2013
|
+
var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
2014
|
+
function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
|
|
2015
|
+
let first = null;
|
|
2016
|
+
let count = 0;
|
|
2017
|
+
let fd;
|
|
2018
|
+
const look = (line) => {
|
|
2019
|
+
if (line.indexOf('"cwd"') < 0) return null;
|
|
2020
|
+
let j;
|
|
2021
|
+
try {
|
|
2022
|
+
j = JSON.parse(line.toString("utf8"));
|
|
2023
|
+
} catch {
|
|
2024
|
+
return null;
|
|
2025
|
+
}
|
|
2026
|
+
if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
|
|
2027
|
+
if (projectName(j.cwd) === t.project) return j.cwd;
|
|
2028
|
+
first ??= j.cwd;
|
|
2029
|
+
count++;
|
|
2030
|
+
return null;
|
|
2031
|
+
};
|
|
2032
|
+
try {
|
|
2033
|
+
fd = openSync2(t.file, "r");
|
|
2034
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
2035
|
+
let pos = 0;
|
|
2036
|
+
let rest = Buffer.alloc(0);
|
|
2037
|
+
let skipping = false;
|
|
2038
|
+
while (pos < cap2 && count < 50) {
|
|
2039
|
+
const n = readSync2(fd, buf, 0, buf.length, pos);
|
|
2040
|
+
if (!n) {
|
|
2041
|
+
if (!skipping && rest.length) {
|
|
2042
|
+
const hit = look(rest);
|
|
2043
|
+
if (hit) return hit;
|
|
2044
|
+
}
|
|
2045
|
+
break;
|
|
2046
|
+
}
|
|
2047
|
+
pos += n;
|
|
2048
|
+
let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
|
|
2049
|
+
let nl = chunk.indexOf(10);
|
|
2050
|
+
while (nl >= 0) {
|
|
2051
|
+
const line = chunk.subarray(0, nl);
|
|
2052
|
+
chunk = chunk.subarray(nl + 1);
|
|
2053
|
+
if (skipping) skipping = false;
|
|
2054
|
+
else {
|
|
2055
|
+
const hit = look(line);
|
|
2056
|
+
if (hit) return hit;
|
|
2057
|
+
}
|
|
2058
|
+
nl = chunk.indexOf(10);
|
|
2059
|
+
}
|
|
2060
|
+
if (chunk.length > 2 * 1024 * 1024) {
|
|
2061
|
+
skipping = true;
|
|
2062
|
+
rest = Buffer.alloc(0);
|
|
2063
|
+
} else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
|
|
2064
|
+
}
|
|
2065
|
+
} catch {
|
|
2066
|
+
} finally {
|
|
2067
|
+
if (fd !== void 0)
|
|
2068
|
+
try {
|
|
2069
|
+
closeSync2(fd);
|
|
2070
|
+
} catch {
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
return first;
|
|
2074
|
+
}
|
|
2075
|
+
function pidAlive(pid) {
|
|
2076
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) return false;
|
|
2077
|
+
try {
|
|
2078
|
+
process.kill(pid, 0);
|
|
2079
|
+
return true;
|
|
2080
|
+
} catch (e) {
|
|
2081
|
+
return e.code === "EPERM";
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function liveProcess(session, configDir) {
|
|
2085
|
+
let names = [];
|
|
2086
|
+
try {
|
|
2087
|
+
names = readdirSync5(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
|
|
2088
|
+
} catch {
|
|
2089
|
+
return false;
|
|
2090
|
+
}
|
|
2091
|
+
for (const n of names) {
|
|
2092
|
+
try {
|
|
2093
|
+
const j = JSON.parse(readFileSync6(path8.join(configDir, "sessions", n), "utf8"));
|
|
2094
|
+
if (String(j.sessionId) !== session) continue;
|
|
2095
|
+
if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
|
|
2096
|
+
} catch {
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
return false;
|
|
2100
|
+
}
|
|
2101
|
+
function recentlyWritten(file, ourLastEnd, now = Date.now()) {
|
|
2102
|
+
let m;
|
|
2103
|
+
try {
|
|
2104
|
+
m = statSync4(file).mtimeMs;
|
|
2105
|
+
} catch {
|
|
2106
|
+
return false;
|
|
2107
|
+
}
|
|
2108
|
+
if (now - m > LIVE_WRITE_MS) return false;
|
|
2109
|
+
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
2110
|
+
}
|
|
2111
|
+
function runClaude(bin, args, o) {
|
|
2112
|
+
return new Promise((resolve) => {
|
|
2113
|
+
let out = [];
|
|
2114
|
+
let outLen = 0;
|
|
2115
|
+
let tooBig = false;
|
|
2116
|
+
let errTail = "";
|
|
2117
|
+
let timedOut = false;
|
|
2118
|
+
let settled = false;
|
|
2119
|
+
let child;
|
|
2120
|
+
try {
|
|
2121
|
+
child = spawn(bin, args, {
|
|
2122
|
+
cwd: o.cwd,
|
|
2123
|
+
env: o.env,
|
|
2124
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2125
|
+
detached: process.platform !== "win32",
|
|
2126
|
+
windowsHide: true
|
|
2127
|
+
});
|
|
2128
|
+
} catch (e) {
|
|
2129
|
+
resolve({ error: e.message });
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
const kill = (sig) => {
|
|
2133
|
+
try {
|
|
2134
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
|
|
2135
|
+
else child.kill(sig);
|
|
2136
|
+
} catch {
|
|
2137
|
+
try {
|
|
2138
|
+
child.kill(sig);
|
|
2139
|
+
} catch {
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
child.stdout?.on("data", (b) => {
|
|
2144
|
+
if (tooBig) return;
|
|
2145
|
+
if (outLen + b.length > OUT_CAP) {
|
|
2146
|
+
tooBig = true;
|
|
2147
|
+
out = [];
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
out.push(b);
|
|
2151
|
+
outLen += b.length;
|
|
2152
|
+
});
|
|
2153
|
+
child.stderr?.on("data", (b) => {
|
|
2154
|
+
errTail = (errTail + b.toString("utf8")).slice(-4e3);
|
|
2155
|
+
});
|
|
2156
|
+
const timer = setTimeout(() => {
|
|
2157
|
+
timedOut = true;
|
|
2158
|
+
kill("SIGTERM");
|
|
2159
|
+
setTimeout(() => kill("SIGKILL"), 1e4).unref();
|
|
2160
|
+
}, o.timeoutMs ?? JOB_TIMEOUT_MS);
|
|
2161
|
+
const result = (code, signal) => ({
|
|
2162
|
+
code,
|
|
2163
|
+
signal,
|
|
2164
|
+
stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
|
|
2165
|
+
tooBig,
|
|
2166
|
+
stderr: errTail,
|
|
2167
|
+
timedOut
|
|
2168
|
+
});
|
|
2169
|
+
const finish = (r) => {
|
|
2170
|
+
if (settled) return;
|
|
2171
|
+
settled = true;
|
|
2172
|
+
clearTimeout(timer);
|
|
2173
|
+
resolve(r);
|
|
2174
|
+
};
|
|
2175
|
+
child.on("error", (e) => finish({ error: e.message }));
|
|
2176
|
+
child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
|
|
2177
|
+
child.on("close", (code, signal) => finish(result(code, signal)));
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
function parseResult(stdout) {
|
|
2181
|
+
const s = String(stdout || "").trim();
|
|
2182
|
+
if (!s) return null;
|
|
2183
|
+
const pick = (j) => {
|
|
2184
|
+
if (Array.isArray(j)) {
|
|
2185
|
+
for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
|
|
2186
|
+
return null;
|
|
2187
|
+
}
|
|
2188
|
+
return j && typeof j === "object" ? j : null;
|
|
2189
|
+
};
|
|
2190
|
+
try {
|
|
2191
|
+
return pick(JSON.parse(s));
|
|
2192
|
+
} catch {
|
|
2193
|
+
}
|
|
2194
|
+
const lines = s.split("\n");
|
|
2195
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2196
|
+
try {
|
|
2197
|
+
const j = pick(JSON.parse(lines[i]));
|
|
2198
|
+
if (j && (j.type === "result" || "result" in j)) return j;
|
|
2199
|
+
} catch {
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
2205
|
+
|
|
1705
2206
|
// src/adapters/index.ts
|
|
1706
2207
|
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
1707
2208
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1708
2209
|
|
|
1709
2210
|
// src/config.ts
|
|
1710
|
-
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as
|
|
1711
|
-
import
|
|
2211
|
+
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2212
|
+
import path9 from "node:path";
|
|
1712
2213
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
1713
2214
|
function readConfig(file = configFile()) {
|
|
1714
2215
|
try {
|
|
1715
|
-
const j = JSON.parse(
|
|
2216
|
+
const j = JSON.parse(readFileSync7(file, "utf8"));
|
|
1716
2217
|
return {
|
|
1717
2218
|
...DEFAULT_CONFIG,
|
|
1718
2219
|
...j,
|
|
@@ -1724,7 +2225,7 @@ function readConfig(file = configFile()) {
|
|
|
1724
2225
|
}
|
|
1725
2226
|
}
|
|
1726
2227
|
function writeConfig(c, file = configFile()) {
|
|
1727
|
-
mkdirSync4(
|
|
2228
|
+
mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
|
|
1728
2229
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
1729
2230
|
`, { mode: 384 });
|
|
1730
2231
|
if (process.platform !== "win32" && existsSync6(file)) {
|
|
@@ -1793,32 +2294,32 @@ function makeEvent(a, ctx) {
|
|
|
1793
2294
|
// src/outbox.ts
|
|
1794
2295
|
import {
|
|
1795
2296
|
appendFileSync,
|
|
1796
|
-
closeSync as
|
|
2297
|
+
closeSync as closeSync3,
|
|
1797
2298
|
existsSync as existsSync7,
|
|
1798
2299
|
mkdirSync as mkdirSync5,
|
|
1799
|
-
openSync as
|
|
1800
|
-
readdirSync as
|
|
1801
|
-
readFileSync as
|
|
1802
|
-
readSync as
|
|
2300
|
+
openSync as openSync3,
|
|
2301
|
+
readdirSync as readdirSync6,
|
|
2302
|
+
readFileSync as readFileSync8,
|
|
2303
|
+
readSync as readSync3,
|
|
1803
2304
|
renameSync,
|
|
1804
|
-
statSync as
|
|
1805
|
-
unlinkSync,
|
|
2305
|
+
statSync as statSync5,
|
|
2306
|
+
unlinkSync as unlinkSync3,
|
|
1806
2307
|
writeFileSync as writeFileSync5
|
|
1807
2308
|
} from "node:fs";
|
|
1808
|
-
import
|
|
2309
|
+
import path10 from "node:path";
|
|
1809
2310
|
var Outbox = class {
|
|
1810
2311
|
constructor(root) {
|
|
1811
2312
|
this.root = root;
|
|
1812
2313
|
}
|
|
1813
2314
|
root;
|
|
1814
2315
|
dir(harness) {
|
|
1815
|
-
return
|
|
2316
|
+
return path10.join(this.root, "outbox", safeId(harness));
|
|
1816
2317
|
}
|
|
1817
2318
|
file(ref) {
|
|
1818
|
-
return
|
|
2319
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
1819
2320
|
}
|
|
1820
2321
|
seqFile(ref) {
|
|
1821
|
-
return
|
|
2322
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
1822
2323
|
}
|
|
1823
2324
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
1824
2325
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -1826,18 +2327,18 @@ var Outbox = class {
|
|
|
1826
2327
|
const f = this.seqFile(ref);
|
|
1827
2328
|
let n = 0;
|
|
1828
2329
|
try {
|
|
1829
|
-
n = Number(
|
|
2330
|
+
n = Number(readFileSync8(f, "utf8")) || 0;
|
|
1830
2331
|
} catch {
|
|
1831
2332
|
n = this.lastSeqInFile(ref) + 1;
|
|
1832
2333
|
}
|
|
1833
|
-
mkdirSync5(
|
|
1834
|
-
writeFileSync5(f, String(n + 1));
|
|
2334
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2335
|
+
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
1835
2336
|
return n;
|
|
1836
2337
|
}
|
|
1837
2338
|
lastSeqInFile(ref) {
|
|
1838
2339
|
const f = this.file(ref);
|
|
1839
2340
|
if (!existsSync7(f)) return -1;
|
|
1840
|
-
const lines =
|
|
2341
|
+
const lines = readFileSync8(f, "utf8").trim().split("\n");
|
|
1841
2342
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1842
2343
|
try {
|
|
1843
2344
|
const j = JSON.parse(lines[i] ?? "");
|
|
@@ -1849,26 +2350,26 @@ var Outbox = class {
|
|
|
1849
2350
|
}
|
|
1850
2351
|
append(ref, events) {
|
|
1851
2352
|
if (!events.length) return;
|
|
1852
|
-
mkdirSync5(this.dir(ref.harness), { recursive: true });
|
|
2353
|
+
mkdirSync5(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
1853
2354
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
1854
|
-
|
|
2355
|
+
`, { mode: 384 });
|
|
1855
2356
|
}
|
|
1856
2357
|
/** Every session file, newest first. */
|
|
1857
2358
|
sessions() {
|
|
1858
2359
|
const out = [];
|
|
1859
|
-
const base =
|
|
2360
|
+
const base = path10.join(this.root, "outbox");
|
|
1860
2361
|
if (!existsSync7(base)) return [];
|
|
1861
|
-
for (const h of
|
|
1862
|
-
const d =
|
|
2362
|
+
for (const h of readdirSync6(base)) {
|
|
2363
|
+
const d = path10.join(base, h);
|
|
1863
2364
|
let names = [];
|
|
1864
2365
|
try {
|
|
1865
|
-
names =
|
|
2366
|
+
names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
|
|
1866
2367
|
} catch {
|
|
1867
2368
|
continue;
|
|
1868
2369
|
}
|
|
1869
2370
|
for (const n of names) {
|
|
1870
2371
|
try {
|
|
1871
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
2372
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync5(path10.join(d, n)).mtimeMs });
|
|
1872
2373
|
} catch {
|
|
1873
2374
|
}
|
|
1874
2375
|
}
|
|
@@ -1880,12 +2381,12 @@ var Outbox = class {
|
|
|
1880
2381
|
read(ref, from, maxEvents = 500) {
|
|
1881
2382
|
const f = this.file(ref);
|
|
1882
2383
|
if (!existsSync7(f)) return { events: [], offset: from };
|
|
1883
|
-
const size =
|
|
2384
|
+
const size = statSync5(f).size;
|
|
1884
2385
|
if (from >= size) return { events: [], offset: from };
|
|
1885
|
-
const fd =
|
|
2386
|
+
const fd = openSync3(f, "r");
|
|
1886
2387
|
try {
|
|
1887
2388
|
const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
|
|
1888
|
-
const n =
|
|
2389
|
+
const n = readSync3(fd, buf, 0, buf.length, from);
|
|
1889
2390
|
const text = buf.subarray(0, n).toString("utf8");
|
|
1890
2391
|
const events = [];
|
|
1891
2392
|
let offset = from;
|
|
@@ -1904,7 +2405,7 @@ var Outbox = class {
|
|
|
1904
2405
|
}
|
|
1905
2406
|
return { events, offset };
|
|
1906
2407
|
} finally {
|
|
1907
|
-
|
|
2408
|
+
closeSync3(fd);
|
|
1908
2409
|
}
|
|
1909
2410
|
}
|
|
1910
2411
|
/** Files older than `days` are pruned; the cursor files with them. */
|
|
@@ -1914,10 +2415,10 @@ var Outbox = class {
|
|
|
1914
2415
|
for (const ref of this.sessions()) {
|
|
1915
2416
|
const f = this.file(ref);
|
|
1916
2417
|
try {
|
|
1917
|
-
if (
|
|
1918
|
-
|
|
2418
|
+
if (statSync5(f).mtimeMs < cutoff) {
|
|
2419
|
+
unlinkSync3(f);
|
|
1919
2420
|
try {
|
|
1920
|
-
|
|
2421
|
+
unlinkSync3(this.seqFile(ref));
|
|
1921
2422
|
} catch {
|
|
1922
2423
|
}
|
|
1923
2424
|
n++;
|
|
@@ -1930,7 +2431,7 @@ var Outbox = class {
|
|
|
1930
2431
|
delete(ref) {
|
|
1931
2432
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
1932
2433
|
try {
|
|
1933
|
-
|
|
2434
|
+
unlinkSync3(f);
|
|
1934
2435
|
} catch {
|
|
1935
2436
|
}
|
|
1936
2437
|
}
|
|
@@ -1944,20 +2445,20 @@ var Cursors = class {
|
|
|
1944
2445
|
root;
|
|
1945
2446
|
sink;
|
|
1946
2447
|
file() {
|
|
1947
|
-
return
|
|
2448
|
+
return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
1948
2449
|
}
|
|
1949
2450
|
read() {
|
|
1950
2451
|
try {
|
|
1951
|
-
return JSON.parse(
|
|
2452
|
+
return JSON.parse(readFileSync8(this.file(), "utf8"));
|
|
1952
2453
|
} catch {
|
|
1953
2454
|
return {};
|
|
1954
2455
|
}
|
|
1955
2456
|
}
|
|
1956
2457
|
write(c) {
|
|
1957
2458
|
const f = this.file();
|
|
1958
|
-
mkdirSync5(
|
|
2459
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
1959
2460
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
1960
|
-
writeFileSync5(tmp, JSON.stringify(c));
|
|
2461
|
+
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
1961
2462
|
renameSync(tmp, f);
|
|
1962
2463
|
}
|
|
1963
2464
|
key(ref) {
|
|
@@ -1965,11 +2466,11 @@ var Cursors = class {
|
|
|
1965
2466
|
}
|
|
1966
2467
|
};
|
|
1967
2468
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
1968
|
-
const dir =
|
|
1969
|
-
mkdirSync5(dir, { recursive: true });
|
|
1970
|
-
const f =
|
|
2469
|
+
const dir = path10.join(root, "locks");
|
|
2470
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2471
|
+
const f = path10.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
|
|
1971
2472
|
try {
|
|
1972
|
-
if (Date.now() -
|
|
2473
|
+
if (Date.now() - statSync5(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
1973
2474
|
else return null;
|
|
1974
2475
|
} catch {
|
|
1975
2476
|
}
|
|
@@ -1980,7 +2481,7 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
1980
2481
|
}
|
|
1981
2482
|
return () => {
|
|
1982
2483
|
try {
|
|
1983
|
-
|
|
2484
|
+
unlinkSync3(f);
|
|
1984
2485
|
} catch {
|
|
1985
2486
|
}
|
|
1986
2487
|
};
|
|
@@ -2116,7 +2617,7 @@ function redactAssignments(text) {
|
|
|
2116
2617
|
function isSecretName(name) {
|
|
2117
2618
|
if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
|
|
2118
2619
|
const flat = name.toLowerCase().replace(/[_-]/g, "");
|
|
2119
|
-
return /(?:pass(?:word|wd|phrase|code)
|
|
2620
|
+
return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
|
|
2120
2621
|
flat
|
|
2121
2622
|
);
|
|
2122
2623
|
}
|
|
@@ -2208,7 +2709,7 @@ var DATA_ALLOW = {
|
|
|
2208
2709
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2209
2710
|
"session.forgotten": { 0: [] },
|
|
2210
2711
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2211
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"] },
|
|
2712
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2212
2713
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2213
2714
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2214
2715
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
@@ -2286,7 +2787,7 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2286
2787
|
|
|
2287
2788
|
// src/sinks/file.ts
|
|
2288
2789
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2289
|
-
import
|
|
2790
|
+
import path11 from "node:path";
|
|
2290
2791
|
var FileSink = class {
|
|
2291
2792
|
constructor(name, file, tier, pii) {
|
|
2292
2793
|
this.name = name;
|
|
@@ -2300,7 +2801,7 @@ var FileSink = class {
|
|
|
2300
2801
|
pii;
|
|
2301
2802
|
async send(events) {
|
|
2302
2803
|
try {
|
|
2303
|
-
mkdirSync6(
|
|
2804
|
+
mkdirSync6(path11.dirname(this.file), { recursive: true });
|
|
2304
2805
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2305
2806
|
`);
|
|
2306
2807
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2347,6 +2848,8 @@ export {
|
|
|
2347
2848
|
adapterByName,
|
|
2348
2849
|
antigravity,
|
|
2349
2850
|
claudeCode,
|
|
2851
|
+
claude_code_control_exports as claudeControl,
|
|
2852
|
+
claudeDirs,
|
|
2350
2853
|
codex,
|
|
2351
2854
|
configFile,
|
|
2352
2855
|
filterEvent,
|