@sechroom/cli 2026.7.12 → 2026.7.14
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/dist/index.js +492 -67
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -439,6 +439,9 @@ var quiet = false;
|
|
|
439
439
|
function setQuiet(q) {
|
|
440
440
|
quiet = q;
|
|
441
441
|
}
|
|
442
|
+
function isQuiet() {
|
|
443
|
+
return quiet;
|
|
444
|
+
}
|
|
442
445
|
function colorOn() {
|
|
443
446
|
return !quiet && !process.env.NO_COLOR && process.env.FORCE_COLOR !== "0" && Boolean(process.stdout.isTTY);
|
|
444
447
|
}
|
|
@@ -615,6 +618,22 @@ function emitAction(summary, data, json) {
|
|
|
615
618
|
process.stdout.write(`${ok("\u2713")} ${summary}
|
|
616
619
|
`);
|
|
617
620
|
}
|
|
621
|
+
var GOVERNANCE_QUEUED_PROBLEM_TYPE = "https://sechroom.dev/problems/governance-review-queued";
|
|
622
|
+
function isGovernanceQueued(body) {
|
|
623
|
+
return typeof body === "object" && body !== null && "type" in body && body.type === GOVERNANCE_QUEUED_PROBLEM_TYPE;
|
|
624
|
+
}
|
|
625
|
+
function formatQueuedForApproval(body, json) {
|
|
626
|
+
if (json) return JSON.stringify(body) + "\n";
|
|
627
|
+
const b = body ?? {};
|
|
628
|
+
const detail = typeof b.detail === "string" ? b.detail : void 0;
|
|
629
|
+
const requestId = typeof b.requestId === "string" ? b.requestId : void 0;
|
|
630
|
+
const message = detail ?? "This change requires operator approval before it takes effect.";
|
|
631
|
+
let out = `${warn("\u29D7")} Pending approval \u2014 ${message}
|
|
632
|
+
`;
|
|
633
|
+
if (requestId) out += ` ${style.dim(`request: ${requestId}`)}
|
|
634
|
+
`;
|
|
635
|
+
return out;
|
|
636
|
+
}
|
|
618
637
|
async function runApi(label, fn) {
|
|
619
638
|
const s = spinner(label);
|
|
620
639
|
let res;
|
|
@@ -624,6 +643,12 @@ async function runApi(label, fn) {
|
|
|
624
643
|
s.fail();
|
|
625
644
|
fail(err2);
|
|
626
645
|
}
|
|
646
|
+
const queuedBody = res.data ?? res.error;
|
|
647
|
+
if (res.response?.status === 202 && isGovernanceQueued(queuedBody)) {
|
|
648
|
+
s.stop();
|
|
649
|
+
process.stdout.write(formatQueuedForApproval(queuedBody, isQuiet()));
|
|
650
|
+
process.exit(0);
|
|
651
|
+
}
|
|
627
652
|
const httpFailed = res.response !== void 0 && !res.response.ok;
|
|
628
653
|
if (res.error !== void 0 && res.error !== null || httpFailed) {
|
|
629
654
|
s.fail();
|
|
@@ -1076,14 +1101,23 @@ function bodyOf(row) {
|
|
|
1076
1101
|
const m = row?.item ?? row;
|
|
1077
1102
|
return m?.text ?? m?.Text ?? "";
|
|
1078
1103
|
}
|
|
1104
|
+
function idOf(row) {
|
|
1105
|
+
const m = row?.item ?? row;
|
|
1106
|
+
return String(m?.id ?? m?.memoryId ?? "unknown");
|
|
1107
|
+
}
|
|
1079
1108
|
function entriesFromRows(rows, surface, source, roleTag, namePrefix) {
|
|
1080
1109
|
const out = /* @__PURE__ */ new Map();
|
|
1081
1110
|
for (const row of rows ?? []) {
|
|
1082
1111
|
const tags = tagsOf(row);
|
|
1083
1112
|
if (!tags.includes(roleTag)) continue;
|
|
1084
|
-
if (
|
|
1113
|
+
if (!tags.includes(`target:${surface}`)) continue;
|
|
1085
1114
|
const name = tagValue(tags, namePrefix);
|
|
1086
1115
|
if (!name) continue;
|
|
1116
|
+
if (out.has(name)) {
|
|
1117
|
+
throw new Error(
|
|
1118
|
+
`Ambiguous ${source} ${roleTag} '${name}' for target:${surface}: ${idOf(row)} duplicates another eligible component. Resolve the duplicate before installing.`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1087
1121
|
out.set(name, { name, body: bodyOf(row), source });
|
|
1088
1122
|
}
|
|
1089
1123
|
return out;
|
|
@@ -1111,20 +1145,33 @@ function agentTargetFor(surface) {
|
|
|
1111
1145
|
return AGENT_TARGET[surface] ?? `${surface}-agent`;
|
|
1112
1146
|
}
|
|
1113
1147
|
async function fetchFeedRows(cfg, workspaceId) {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1148
|
+
const client = await makeClient(cfg);
|
|
1149
|
+
const rows = [];
|
|
1150
|
+
let cursor;
|
|
1151
|
+
do {
|
|
1152
|
+
const { data, error, response } = await client.GET("/workspaces/{workspaceId}/memories/feed", {
|
|
1117
1153
|
params: {
|
|
1118
1154
|
path: { workspaceId },
|
|
1119
1155
|
// cascadeWorkspaces: skills land in an "Operator Skills" SUB-workspace;
|
|
1120
1156
|
// includeText: the feed omits bodies by default, we need them for SKILL.md.
|
|
1121
|
-
query: {
|
|
1157
|
+
query: {
|
|
1158
|
+
limit: 200,
|
|
1159
|
+
cascadeWorkspaces: true,
|
|
1160
|
+
includeText: true,
|
|
1161
|
+
...cursor ? { cursor } : {}
|
|
1162
|
+
}
|
|
1122
1163
|
}
|
|
1123
|
-
})
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1164
|
+
});
|
|
1165
|
+
if (error || !response.ok || !data) {
|
|
1166
|
+
throw new Error(
|
|
1167
|
+
`Could not read operator-skill catalogue from ${workspaceId}: HTTP ${response.status}.`
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
const feed = data;
|
|
1171
|
+
rows.push(...feed.results ?? feed.Results ?? []);
|
|
1172
|
+
cursor = feed.nextCursor ?? feed.NextCursor ?? void 0;
|
|
1173
|
+
} while (cursor);
|
|
1174
|
+
return rows;
|
|
1128
1175
|
}
|
|
1129
1176
|
async function fetchTemplateRows(cfg, personalWorkspaceId) {
|
|
1130
1177
|
const [systemRows, personalRows] = await Promise.all([
|
|
@@ -1144,7 +1191,10 @@ function resolveReferenceSet(rows, surface) {
|
|
|
1144
1191
|
}
|
|
1145
1192
|
|
|
1146
1193
|
// src/setup/materialise.ts
|
|
1147
|
-
var CLIENT_SURFACE =
|
|
1194
|
+
var CLIENT_SURFACE = {
|
|
1195
|
+
claude: "claude-code",
|
|
1196
|
+
codex: "gpt-codex"
|
|
1197
|
+
};
|
|
1148
1198
|
function writeSkills(dir, skills, surface) {
|
|
1149
1199
|
const written = [];
|
|
1150
1200
|
for (const s of skills) {
|
|
@@ -1177,11 +1227,71 @@ function writeReferencesIntoSkillDirs(dir, skills, refs) {
|
|
|
1177
1227
|
}
|
|
1178
1228
|
return refs.map((r) => r.name);
|
|
1179
1229
|
}
|
|
1180
|
-
var SKILL_SPEC = {
|
|
1181
|
-
|
|
1230
|
+
var SKILL_SPEC = {
|
|
1231
|
+
kind: "skill",
|
|
1232
|
+
dir: skillsDir,
|
|
1233
|
+
resolve: resolveSkillSet,
|
|
1234
|
+
write: writeSkills,
|
|
1235
|
+
supportsCodex: true
|
|
1236
|
+
};
|
|
1237
|
+
var AGENT_SPEC = {
|
|
1238
|
+
kind: "agent",
|
|
1239
|
+
dir: agentsDir,
|
|
1240
|
+
resolve: resolveAgentSet,
|
|
1241
|
+
write: writeAgents,
|
|
1242
|
+
supportsCodex: false
|
|
1243
|
+
};
|
|
1182
1244
|
function scopeOf(opts) {
|
|
1183
1245
|
return opts.local ? "project" : resolveScope(opts.scope);
|
|
1184
1246
|
}
|
|
1247
|
+
function parseClient(value) {
|
|
1248
|
+
if (value == null) return void 0;
|
|
1249
|
+
if (value === "claude" || value === "codex" || value === "all") return value;
|
|
1250
|
+
throw new Error(`--client must be 'claude', 'codex', or 'all' (got '${value}')`);
|
|
1251
|
+
}
|
|
1252
|
+
function clientSelection(spec, g, raw) {
|
|
1253
|
+
if (!spec.supportsCodex) return "claude";
|
|
1254
|
+
const explicit = parseClient(raw);
|
|
1255
|
+
if (explicit) return explicit;
|
|
1256
|
+
const claudeConfigured = Boolean(g.claudeConfigDir || process.env.CLAUDE_CONFIG_DIR);
|
|
1257
|
+
const codexConfigured = Boolean(g.codexHome || process.env.CODEX_HOME);
|
|
1258
|
+
if (claudeConfigured || codexConfigured) {
|
|
1259
|
+
if (claudeConfigured && codexConfigured) return "all";
|
|
1260
|
+
return codexConfigured ? "codex" : "claude";
|
|
1261
|
+
}
|
|
1262
|
+
const claudeDetected = resolveClaudeTargets({})[0]?.dir;
|
|
1263
|
+
const codexDetected = resolveCodexHomes({})[0];
|
|
1264
|
+
const hasClaude = Boolean(claudeDetected && existsSync3(claudeDetected));
|
|
1265
|
+
const hasCodex = Boolean(codexDetected && existsSync3(codexDetected));
|
|
1266
|
+
if (hasClaude && hasCodex) return "all";
|
|
1267
|
+
if (hasCodex) return "codex";
|
|
1268
|
+
return "claude";
|
|
1269
|
+
}
|
|
1270
|
+
function targetsFor(spec, g, scope, selection) {
|
|
1271
|
+
const clients = selection === "all" ? ["claude", "codex"] : [selection];
|
|
1272
|
+
if (scope === "project" && clients.includes("codex")) {
|
|
1273
|
+
throw new Error("Codex skills have no supported project scope; use --scope global or --client claude.");
|
|
1274
|
+
}
|
|
1275
|
+
const targets = [];
|
|
1276
|
+
for (const client of clients) {
|
|
1277
|
+
if (client === "claude") {
|
|
1278
|
+
targets.push(...resolveClaudeTargets({ override: g.claudeConfigDir, scope, cwd: process.cwd() }).map((target) => ({
|
|
1279
|
+
client,
|
|
1280
|
+
surface: CLIENT_SURFACE[client],
|
|
1281
|
+
dir: spec.dir(target.dir),
|
|
1282
|
+
label: target.label
|
|
1283
|
+
})));
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
targets.push(...resolveCodexHomes({ override: g.codexHome, scope }).map((home) => ({
|
|
1287
|
+
client,
|
|
1288
|
+
surface: CLIENT_SURFACE[client],
|
|
1289
|
+
dir: spec.dir(home),
|
|
1290
|
+
label: home
|
|
1291
|
+
})));
|
|
1292
|
+
}
|
|
1293
|
+
return targets;
|
|
1294
|
+
}
|
|
1185
1295
|
async function runInstall(spec, cmd, opts) {
|
|
1186
1296
|
const g = cmd.optsWithGlobals();
|
|
1187
1297
|
let scope;
|
|
@@ -1193,31 +1303,48 @@ async function runInstall(spec, cmd, opts) {
|
|
|
1193
1303
|
const cfg = resolveConfig(g);
|
|
1194
1304
|
const dryRun = Boolean(opts.dryRun);
|
|
1195
1305
|
const json = Boolean(g.json || opts.json);
|
|
1196
|
-
|
|
1306
|
+
let selection;
|
|
1307
|
+
let targets;
|
|
1308
|
+
try {
|
|
1309
|
+
selection = clientSelection(spec, g, opts.client);
|
|
1310
|
+
targets = targetsFor(spec, g, scope, selection);
|
|
1311
|
+
} catch (err2) {
|
|
1312
|
+
return fail(err2.message);
|
|
1313
|
+
}
|
|
1197
1314
|
const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
|
|
1198
1315
|
const rows = await fetchTemplateRows(cfg, personalWorkspaceId);
|
|
1199
|
-
const items = spec.resolve(rows, CLIENT_SURFACE);
|
|
1200
|
-
const refs = spec.kind === "skill" ? resolveReferenceSet(rows, CLIENT_SURFACE) : [];
|
|
1201
1316
|
const results = targets.map((t) => {
|
|
1202
|
-
const
|
|
1203
|
-
const
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1317
|
+
const items = spec.resolve(rows, t.surface);
|
|
1318
|
+
const refs = spec.kind === "skill" ? resolveReferenceSet(rows, t.surface) : [];
|
|
1319
|
+
const written = dryRun ? items.map((i) => i.name) : spec.write(t.dir, items, t.surface);
|
|
1320
|
+
const refsWritten = dryRun ? refs.map((r) => r.name) : writeReferencesIntoSkillDirs(t.dir, items, refs);
|
|
1321
|
+
return {
|
|
1322
|
+
client: t.client,
|
|
1323
|
+
surface: t.surface,
|
|
1324
|
+
dir: t.dir,
|
|
1325
|
+
label: t.label,
|
|
1326
|
+
available: items.length,
|
|
1327
|
+
references: refs.length,
|
|
1328
|
+
items: items.map(({ name, source }) => ({ name, source })),
|
|
1329
|
+
referenceItems: refs.map(({ name, source }) => ({ name, source })),
|
|
1330
|
+
written,
|
|
1331
|
+
refsWritten
|
|
1332
|
+
};
|
|
1206
1333
|
});
|
|
1207
|
-
if (json) return emit({ kind: spec.kind,
|
|
1208
|
-
if (
|
|
1334
|
+
if (json) return emit({ kind: spec.kind, client: selection, dryRun, targets: results }, true);
|
|
1335
|
+
if (results.every((r) => r.available === 0)) {
|
|
1209
1336
|
console.log(style.dim(`No ${spec.kind}s available to install \u2014 is the bundle installed for your account?`));
|
|
1210
1337
|
return;
|
|
1211
1338
|
}
|
|
1212
1339
|
for (const r of results) {
|
|
1213
1340
|
console.log(
|
|
1214
|
-
`${dryRun ? "" : style.green("\u2713 ")}${dryRun ? "would write" : "wrote"} ${r.written.length} ${spec.kind}(s) ${style.dim("\u2192")} ${r.dir}`
|
|
1341
|
+
`${dryRun ? "" : style.green("\u2713 ")}${dryRun ? "would write" : "wrote"} ${r.written.length} ${spec.kind}(s) for ${r.client} ${style.dim("\u2192")} ${r.dir}`
|
|
1215
1342
|
);
|
|
1216
1343
|
if (r.refsWritten.length)
|
|
1217
1344
|
console.log(
|
|
1218
1345
|
`${dryRun ? "" : style.green("\u2713 ")}${dryRun ? "would write" : "wrote"} ${r.refsWritten.length} reference(s) into each skill ${style.dim("\u2192")} ${r.dir}/<skill>/references`
|
|
1219
1346
|
);
|
|
1220
|
-
if (dryRun) for (const i of items) console.log(` ${i.name} ${style.dim(`[${i.source}]`)}`);
|
|
1347
|
+
if (dryRun) for (const i of r.items) console.log(` ${i.name} ${style.dim(`[${i.source}]`)}`);
|
|
1221
1348
|
}
|
|
1222
1349
|
}
|
|
1223
1350
|
function runList(spec, cmd, opts) {
|
|
@@ -1229,16 +1356,22 @@ function runList(spec, cmd, opts) {
|
|
|
1229
1356
|
return fail(err2.message);
|
|
1230
1357
|
}
|
|
1231
1358
|
const json = Boolean(g.json || opts.json);
|
|
1232
|
-
|
|
1359
|
+
let selection;
|
|
1360
|
+
let targets;
|
|
1361
|
+
try {
|
|
1362
|
+
selection = clientSelection(spec, g, opts.client);
|
|
1363
|
+
targets = targetsFor(spec, g, scope, selection);
|
|
1364
|
+
} catch (err2) {
|
|
1365
|
+
return fail(err2.message);
|
|
1366
|
+
}
|
|
1233
1367
|
const out = targets.map((t) => {
|
|
1234
|
-
const
|
|
1235
|
-
const lock = readSkillsLock(dir);
|
|
1368
|
+
const lock = readSkillsLock(t.dir);
|
|
1236
1369
|
const entries = Object.entries(lock).flatMap(
|
|
1237
|
-
([slug, e]) => (e.skills ?? []).map((name) => ({ slug, name, present: existsSync3(join4(dir, name)) }))
|
|
1370
|
+
([slug, e]) => (e.skills ?? []).map((name) => ({ slug, name, present: existsSync3(join4(t.dir, name)) }))
|
|
1238
1371
|
);
|
|
1239
|
-
return { dir, label: t.label, entries };
|
|
1372
|
+
return { client: t.client, surface: t.surface, dir: t.dir, label: t.label, entries };
|
|
1240
1373
|
});
|
|
1241
|
-
if (json) return emit({ kind: spec.kind, targets: out }, true);
|
|
1374
|
+
if (json) return emit({ kind: spec.kind, client: selection, targets: out }, true);
|
|
1242
1375
|
let any = false;
|
|
1243
1376
|
for (const t of out) {
|
|
1244
1377
|
if (t.entries.length === 0) continue;
|
|
@@ -1261,33 +1394,39 @@ function runClean(spec, cmd, opts, slugArg) {
|
|
|
1261
1394
|
return fail(err2.message);
|
|
1262
1395
|
}
|
|
1263
1396
|
const json = Boolean(g.json || opts.json);
|
|
1264
|
-
|
|
1397
|
+
let selection;
|
|
1398
|
+
let targets;
|
|
1399
|
+
try {
|
|
1400
|
+
selection = clientSelection(spec, g, opts.client);
|
|
1401
|
+
targets = targetsFor(spec, g, scope, selection);
|
|
1402
|
+
} catch (err2) {
|
|
1403
|
+
return fail(err2.message);
|
|
1404
|
+
}
|
|
1265
1405
|
const cleaned = [];
|
|
1266
1406
|
const missing = [];
|
|
1267
1407
|
for (const t of targets) {
|
|
1268
|
-
const
|
|
1269
|
-
const lock = readSkillsLock(dir);
|
|
1408
|
+
const lock = readSkillsLock(t.dir);
|
|
1270
1409
|
const entry = lock[slug];
|
|
1271
1410
|
if (!entry) {
|
|
1272
|
-
missing.push(join4(dir, SKILLS_LOCK));
|
|
1411
|
+
missing.push(join4(t.dir, SKILLS_LOCK));
|
|
1273
1412
|
continue;
|
|
1274
1413
|
}
|
|
1275
1414
|
const removed = [];
|
|
1276
1415
|
for (const name of entry.skills) {
|
|
1277
|
-
const p = join4(dir, name);
|
|
1416
|
+
const p = join4(t.dir, name);
|
|
1278
1417
|
if (existsSync3(p)) {
|
|
1279
1418
|
rmSync2(p, { recursive: true, force: true });
|
|
1280
1419
|
removed.push(name);
|
|
1281
1420
|
}
|
|
1282
1421
|
}
|
|
1283
1422
|
delete lock[slug];
|
|
1284
|
-
writeSkillsLock(dir, lock);
|
|
1285
|
-
cleaned.push({ dir, removed });
|
|
1423
|
+
writeSkillsLock(t.dir, lock);
|
|
1424
|
+
cleaned.push({ client: t.client, surface: t.surface, dir: t.dir, removed });
|
|
1286
1425
|
}
|
|
1287
1426
|
if (cleaned.length === 0) {
|
|
1288
1427
|
return fail(`No materialised ${spec.kind}s recorded for '${slug}' in ${missing.join(", ")}.`);
|
|
1289
1428
|
}
|
|
1290
|
-
if (json) return emit({ kind: spec.kind, slug, cleaned, missing }, true);
|
|
1429
|
+
if (json) return emit({ kind: spec.kind, client: selection, slug, cleaned, missing }, true);
|
|
1291
1430
|
for (const c of cleaned) {
|
|
1292
1431
|
console.log(style.green(`Removed ${c.removed.length} ${spec.kind}(s) for ${slug} from ${c.dir}`));
|
|
1293
1432
|
}
|
|
@@ -1403,15 +1542,24 @@ var CLAUDE_HOOK_COMMANDS = {
|
|
|
1403
1542
|
SessionStart: "sechroom hook session-start",
|
|
1404
1543
|
PreCompact: "sechroom hook pre-compact",
|
|
1405
1544
|
SessionEnd: "sechroom hook session-end",
|
|
1406
|
-
// WLP telemetry tap (D-WLP-10) — per-turn executor self-report.
|
|
1407
|
-
//
|
|
1408
|
-
//
|
|
1409
|
-
|
|
1545
|
+
// WLP telemetry tap (D-WLP-10 + FR-352 Tier 1) — per-turn executor self-report. The one
|
|
1546
|
+
// `telemetry hook` verb dispatches on hook_event_name: Stop/SubagentStop → parsed (token/context) +
|
|
1547
|
+
// terminal (turn end), Notification/PermissionDenied → approval. No-op (exit 0) unless this checkout
|
|
1548
|
+
// is bound via `sechroom telemetry bind`, so it's safe to wire for every Claude install; an event a
|
|
1549
|
+
// given Claude Code version doesn't know is inert (never fires). Claude-only.
|
|
1550
|
+
Stop: "sechroom telemetry hook",
|
|
1551
|
+
SubagentStop: "sechroom telemetry hook",
|
|
1552
|
+
Notification: "sechroom telemetry hook",
|
|
1553
|
+
PermissionDenied: "sechroom telemetry hook"
|
|
1410
1554
|
};
|
|
1411
1555
|
var CODEX_HOOK_COMMANDS = {
|
|
1412
1556
|
SessionStart: "sechroom hook session-start",
|
|
1557
|
+
PreCompact: "sechroom hook pre-compact",
|
|
1413
1558
|
Stop: "sechroom hook session-end --debounce-minutes 10"
|
|
1414
1559
|
};
|
|
1560
|
+
function hookCommandsForSurface(surface) {
|
|
1561
|
+
return surface === "claude" ? CLAUDE_HOOK_COMMANDS : CODEX_HOOK_COMMANDS;
|
|
1562
|
+
}
|
|
1415
1563
|
function hasHookCommand(config2, event, command) {
|
|
1416
1564
|
const groups = config2.hooks?.[event] ?? [];
|
|
1417
1565
|
return groups.some((g) => (g.hooks ?? []).some((h) => h.type === "command" && h.command === command));
|
|
@@ -1549,7 +1697,10 @@ function registerChannel(program2) {
|
|
|
1549
1697
|
"--tag <tag...>",
|
|
1550
1698
|
"Tag(s) the event must carry to match (repeatable). Default targets WLP task dispatches.",
|
|
1551
1699
|
["kind:task"]
|
|
1552
|
-
).option("--workspace <wsp...>", "Restrict to these workspace id(s)")
|
|
1700
|
+
).option("--workspace <wsp...>", "Restrict to these workspace id(s)").option(
|
|
1701
|
+
"--executor-instance <id>",
|
|
1702
|
+
"Join the exact executor-instance SignalR group"
|
|
1703
|
+
);
|
|
1553
1704
|
withFilterOpts(
|
|
1554
1705
|
channel.command("connect").description(
|
|
1555
1706
|
"Register a SignalR subscription and stream matched events to stdout"
|
|
@@ -1567,7 +1718,7 @@ function registerChannel(program2) {
|
|
|
1567
1718
|
(typeof payload === "string" ? payload : JSON.stringify(payload)) + "\n"
|
|
1568
1719
|
)
|
|
1569
1720
|
);
|
|
1570
|
-
const conn = await openConnection(cfg, deliver);
|
|
1721
|
+
const conn = await openConnection(cfg, deliver, opts.executorInstance);
|
|
1571
1722
|
await reconcile(cfg, filter, deliver);
|
|
1572
1723
|
if (json) {
|
|
1573
1724
|
emit(
|
|
@@ -1616,7 +1767,7 @@ function registerChannel(program2) {
|
|
|
1616
1767
|
`))
|
|
1617
1768
|
);
|
|
1618
1769
|
});
|
|
1619
|
-
const conn = await openConnection(cfg, deliver);
|
|
1770
|
+
const conn = await openConnection(cfg, deliver, opts.executorInstance);
|
|
1620
1771
|
await reconcile(cfg, filter, deliver);
|
|
1621
1772
|
process.stderr.write(
|
|
1622
1773
|
style.dim(
|
|
@@ -1727,8 +1878,9 @@ async function ensureSubscription(cfg, name, filter) {
|
|
|
1727
1878
|
);
|
|
1728
1879
|
return await resp.json();
|
|
1729
1880
|
}
|
|
1730
|
-
async function openConnection(cfg, onEvent) {
|
|
1731
|
-
const
|
|
1881
|
+
async function openConnection(cfg, onEvent, executorInstanceId) {
|
|
1882
|
+
const query = executorInstanceId ? `?executorInstanceId=${encodeURIComponent(executorInstanceId)}` : "";
|
|
1883
|
+
const conn = new HubConnectionBuilder().withUrl(`${cfg.baseUrl}/notifications/${cfg.tenant}${query}`, {
|
|
1732
1884
|
transport: HttpTransportType.LongPolling,
|
|
1733
1885
|
accessTokenFactory: () => requireToken(cfg)
|
|
1734
1886
|
}).withAutomaticReconnect().build();
|
|
@@ -2431,6 +2583,12 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
|
|
|
2431
2583
|
const surfaceResults = installHookSurfaces(["codex"], { dryRun, claudeDir: "", codexHome })[0].results;
|
|
2432
2584
|
process.stdout.write(`${HOOK_SURFACE_LABEL.codex}:
|
|
2433
2585
|
`);
|
|
2586
|
+
if (dryRun) {
|
|
2587
|
+
for (const [event, command] of Object.entries(hookCommandsForSurface("codex"))) {
|
|
2588
|
+
process.stdout.write(` ${event}: ${command}
|
|
2589
|
+
`);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2434
2592
|
for (const r of surfaceResults) {
|
|
2435
2593
|
results.push(r);
|
|
2436
2594
|
process.stdout.write(describe(r, dryRun) + "\n");
|
|
@@ -2891,6 +3049,7 @@ function registerDecomposition(program2) {
|
|
|
2891
3049
|
Examples:
|
|
2892
3050
|
$ sechroom decomposition decompose mem_XXXX
|
|
2893
3051
|
$ sechroom decomposition execute sug_XXXX
|
|
3052
|
+
$ sechroom decomposition publish-run sug_XXXX
|
|
2894
3053
|
$ sechroom decomposition accept sug_XXXX
|
|
2895
3054
|
$ sechroom decomposition reject sug_XXXX --reason "wrong shape"`
|
|
2896
3055
|
);
|
|
@@ -2928,6 +3087,23 @@ Examples:
|
|
|
2928
3087
|
cmd.optsWithGlobals().json
|
|
2929
3088
|
);
|
|
2930
3089
|
});
|
|
3090
|
+
decomposition.command("publish-run <decompositionId>").description(
|
|
3091
|
+
"Publish an accepted decomposition's context pack on demand (POST /decompositions/{id}/publish-run)"
|
|
3092
|
+
).action(async (decompositionId, _opts, cmd) => {
|
|
3093
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3094
|
+
const data = await runApi("Publishing context pack", async () => {
|
|
3095
|
+
const client = await makeClient(cfg);
|
|
3096
|
+
return client.POST("/decompositions/{id}/publish-run", {
|
|
3097
|
+
params: { path: { id: decompositionId } },
|
|
3098
|
+
body: {}
|
|
3099
|
+
});
|
|
3100
|
+
});
|
|
3101
|
+
emitAction(
|
|
3102
|
+
`published ${style.bold(decompositionId)} \u2192 run ${style.bold(data.runRecordId)} (${data.outcome})`,
|
|
3103
|
+
data,
|
|
3104
|
+
cmd.optsWithGlobals().json
|
|
3105
|
+
);
|
|
3106
|
+
});
|
|
2931
3107
|
decomposition.command("accept <decompositionId>").description(
|
|
2932
3108
|
"Accept a Pending decomposition \u2014 promote + ratify its Tasks (POST /decompositions/{id}/accept)"
|
|
2933
3109
|
).action(async (decompositionId, _opts, cmd) => {
|
|
@@ -2964,6 +3140,205 @@ Examples:
|
|
|
2964
3140
|
});
|
|
2965
3141
|
}
|
|
2966
3142
|
|
|
3143
|
+
// src/commands/executor.ts
|
|
3144
|
+
function registerExecutor(program2) {
|
|
3145
|
+
const executor = program2.command("executor").description(
|
|
3146
|
+
"Register and operate a local Claude Code/Codex executor advertisement"
|
|
3147
|
+
);
|
|
3148
|
+
executor.command("submit-connector").description(
|
|
3149
|
+
"Submit a local-session connector definition for governed approval"
|
|
3150
|
+
).requiredOption("--slug <slug>", "Unique connector definition slug").requiredOption("--display-name <name>", "Human-readable connector name").requiredOption("--transport <kind>", "push | pull").option("--profile <profile...>", "Advertised runtime profiles", [
|
|
3151
|
+
"base",
|
|
3152
|
+
"dotnet-10"
|
|
3153
|
+
]).action(async (opts, cmd) => {
|
|
3154
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3155
|
+
const data = await api(cfg, "/connectors/definitions", {
|
|
3156
|
+
method: "POST",
|
|
3157
|
+
body: JSON.stringify({
|
|
3158
|
+
slug: opts.slug,
|
|
3159
|
+
displayName: opts.displayName,
|
|
3160
|
+
runtimeProfiles: opts.profile,
|
|
3161
|
+
connectorKind: "ExecutionRuntime",
|
|
3162
|
+
providerKind: "local-session",
|
|
3163
|
+
dispatchTransport: parseTransport(opts.transport)
|
|
3164
|
+
})
|
|
3165
|
+
});
|
|
3166
|
+
emit(data, Boolean(cmd.optsWithGlobals().json));
|
|
3167
|
+
if (!cmd.optsWithGlobals().json) {
|
|
3168
|
+
process.stderr.write(
|
|
3169
|
+
style.dim(
|
|
3170
|
+
"approve this connector definition before registering executors\n"
|
|
3171
|
+
)
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
executor.command("register").description(
|
|
3176
|
+
"Create/reuse a SignalR binding and register this local executor instance"
|
|
3177
|
+
).requiredOption(
|
|
3178
|
+
"--instance-key <key>",
|
|
3179
|
+
"Stable key for this concrete session/lane"
|
|
3180
|
+
).requiredOption(
|
|
3181
|
+
"--connector <id>",
|
|
3182
|
+
"Approved local-session ConnectorDefinition id"
|
|
3183
|
+
).option("--runtime <kind>", "claude-code | codex", "claude-code").option(
|
|
3184
|
+
"--relay <id>",
|
|
3185
|
+
"Relay identity shared by sibling instances",
|
|
3186
|
+
"sechroom-cli-local"
|
|
3187
|
+
).option(
|
|
3188
|
+
"--subscription-name <name>",
|
|
3189
|
+
"SignalR delivery binding name",
|
|
3190
|
+
"executor-dispatch"
|
|
3191
|
+
).option(
|
|
3192
|
+
"--capability <key...>",
|
|
3193
|
+
"Capability operation keys claimed by this instance"
|
|
3194
|
+
).option("--tool-set-ref <ref>", "Optional governed tool-set reference").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).action(async (opts, cmd) => {
|
|
3195
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3196
|
+
if (!cfg.workspaceId)
|
|
3197
|
+
fail("executor register requires a configured workspaceId");
|
|
3198
|
+
const subscription = await api(
|
|
3199
|
+
cfg,
|
|
3200
|
+
"/me/delivery-subscriptions/signalr",
|
|
3201
|
+
{
|
|
3202
|
+
method: "POST",
|
|
3203
|
+
body: JSON.stringify({
|
|
3204
|
+
name: opts.subscriptionName,
|
|
3205
|
+
enabled: true,
|
|
3206
|
+
filter: { tags: ["kind:task"], workspaceScope: [cfg.workspaceId] }
|
|
3207
|
+
})
|
|
3208
|
+
}
|
|
3209
|
+
);
|
|
3210
|
+
const data = await api(
|
|
3211
|
+
cfg,
|
|
3212
|
+
"/me/executor-instances",
|
|
3213
|
+
{
|
|
3214
|
+
method: "POST",
|
|
3215
|
+
body: JSON.stringify({
|
|
3216
|
+
relayId: opts.relay,
|
|
3217
|
+
instanceKey: opts.instanceKey,
|
|
3218
|
+
runtimeKind: parseRuntimeKind(opts.runtime),
|
|
3219
|
+
activationMode: "Attached",
|
|
3220
|
+
deliverySubscriptionId: subscription.id,
|
|
3221
|
+
connectorId: opts.connector,
|
|
3222
|
+
claimedCapabilityKeys: opts.capability ?? [],
|
|
3223
|
+
toolSetRef: opts.toolSetRef ?? null,
|
|
3224
|
+
ttlSeconds: opts.ttl
|
|
3225
|
+
})
|
|
3226
|
+
}
|
|
3227
|
+
);
|
|
3228
|
+
emit(data, Boolean(cmd.optsWithGlobals().json));
|
|
3229
|
+
if (!cmd.optsWithGlobals().json)
|
|
3230
|
+
process.stderr.write(
|
|
3231
|
+
style.dim(`refresh with: sechroom executor heartbeat ${data.id}
|
|
3232
|
+
`)
|
|
3233
|
+
);
|
|
3234
|
+
});
|
|
3235
|
+
executor.command("refresh <id>").description("Refresh one advertisement lease once").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).action(async (id, opts, cmd) => {
|
|
3236
|
+
const data = await refresh(
|
|
3237
|
+
resolveConfig(cmd.optsWithGlobals()),
|
|
3238
|
+
id,
|
|
3239
|
+
opts.ttl
|
|
3240
|
+
);
|
|
3241
|
+
emit(data, Boolean(cmd.optsWithGlobals().json));
|
|
3242
|
+
});
|
|
3243
|
+
executor.command("heartbeat <id>").description("Keep an advertisement alive until interrupted").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 120).option("--interval <seconds>", "Refresh interval", parseInteger, 40).action(async (id, opts, cmd) => {
|
|
3244
|
+
if (opts.interval >= opts.ttl)
|
|
3245
|
+
fail("heartbeat interval must be shorter than the TTL");
|
|
3246
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3247
|
+
await refresh(cfg, id, opts.ttl);
|
|
3248
|
+
process.stderr.write(
|
|
3249
|
+
style.green("executor heartbeat active") + style.dim(` \u2014 ${id}
|
|
3250
|
+
`)
|
|
3251
|
+
);
|
|
3252
|
+
await holdHeartbeat(async () => {
|
|
3253
|
+
await refresh(cfg, id, opts.ttl);
|
|
3254
|
+
}, opts.interval * 1e3);
|
|
3255
|
+
});
|
|
3256
|
+
executor.command("offers <id>").description("List live dispatch offers addressed to this exact instance").action(async (id, _opts, cmd) => {
|
|
3257
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3258
|
+
const data = await api(
|
|
3259
|
+
cfg,
|
|
3260
|
+
`/me/executor-instances/${encodeURIComponent(id)}/dispatch-offers`
|
|
3261
|
+
);
|
|
3262
|
+
emit(data, Boolean(cmd.optsWithGlobals().json));
|
|
3263
|
+
});
|
|
3264
|
+
executor.command("deregister <id>").description("Stop advertising this executor instance").action(async (id, _opts, cmd) => {
|
|
3265
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
3266
|
+
const data = await api(
|
|
3267
|
+
cfg,
|
|
3268
|
+
`/me/executor-instances/${encodeURIComponent(id)}`,
|
|
3269
|
+
{
|
|
3270
|
+
method: "DELETE",
|
|
3271
|
+
body: JSON.stringify({})
|
|
3272
|
+
}
|
|
3273
|
+
);
|
|
3274
|
+
emit(data, Boolean(cmd.optsWithGlobals().json));
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
function parseRuntimeKind(value) {
|
|
3278
|
+
switch (value.trim().toLowerCase()) {
|
|
3279
|
+
case "claude":
|
|
3280
|
+
case "claude-code":
|
|
3281
|
+
return "ClaudeCode";
|
|
3282
|
+
case "codex":
|
|
3283
|
+
return "Codex";
|
|
3284
|
+
default:
|
|
3285
|
+
return fail("runtime must be claude-code or codex");
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
function parseTransport(value) {
|
|
3289
|
+
switch (value.trim().toLowerCase()) {
|
|
3290
|
+
case "push":
|
|
3291
|
+
return "Push";
|
|
3292
|
+
case "pull":
|
|
3293
|
+
return "Pull";
|
|
3294
|
+
default:
|
|
3295
|
+
return fail("transport must be push or pull");
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
async function refresh(cfg, id, ttlSeconds) {
|
|
3299
|
+
return api(
|
|
3300
|
+
cfg,
|
|
3301
|
+
`/me/executor-instances/${encodeURIComponent(id)}/refresh`,
|
|
3302
|
+
{
|
|
3303
|
+
method: "POST",
|
|
3304
|
+
body: JSON.stringify({ ttlSeconds })
|
|
3305
|
+
}
|
|
3306
|
+
);
|
|
3307
|
+
}
|
|
3308
|
+
async function api(cfg, path, init) {
|
|
3309
|
+
const token = await requireToken(cfg);
|
|
3310
|
+
const response = await fetch(`${cfg.baseUrl}${path}`, {
|
|
3311
|
+
...init,
|
|
3312
|
+
headers: {
|
|
3313
|
+
authorization: `Bearer ${token}`,
|
|
3314
|
+
tenant: cfg.tenant,
|
|
3315
|
+
"content-type": "application/json",
|
|
3316
|
+
"x-sechroom-surface": "cli"
|
|
3317
|
+
}
|
|
3318
|
+
});
|
|
3319
|
+
if (!response.ok)
|
|
3320
|
+
fail(
|
|
3321
|
+
`${init?.method ?? "GET"} ${path} failed (${response.status}): ${await response.text()}`
|
|
3322
|
+
);
|
|
3323
|
+
return response.json();
|
|
3324
|
+
}
|
|
3325
|
+
function parseInteger(value) {
|
|
3326
|
+
const parsed = Number.parseInt(value, 10);
|
|
3327
|
+
if (!Number.isFinite(parsed)) fail(`expected an integer, got '${value}'`);
|
|
3328
|
+
return parsed;
|
|
3329
|
+
}
|
|
3330
|
+
function holdHeartbeat(tick, intervalMs) {
|
|
3331
|
+
return new Promise((resolve3, reject) => {
|
|
3332
|
+
const timer = setInterval(() => void tick().catch(reject), intervalMs);
|
|
3333
|
+
const stop = () => {
|
|
3334
|
+
clearInterval(timer);
|
|
3335
|
+
resolve3();
|
|
3336
|
+
};
|
|
3337
|
+
process.once("SIGINT", stop);
|
|
3338
|
+
process.once("SIGTERM", stop);
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
|
|
2967
3342
|
// src/commands/filing.ts
|
|
2968
3343
|
function registerFiling(program2) {
|
|
2969
3344
|
const filing = program2.command("filing").description("Review and act on filing suggestions");
|
|
@@ -5421,7 +5796,9 @@ function registerSkills(program2) {
|
|
|
5421
5796
|
"after",
|
|
5422
5797
|
`
|
|
5423
5798
|
Examples:
|
|
5424
|
-
$ sechroom skills install
|
|
5799
|
+
$ sechroom skills install --client claude materialise target:claude-code skills to ~/.claude/skills
|
|
5800
|
+
$ sechroom skills install --client codex materialise target:gpt-codex skills to ~/.codex/skills
|
|
5801
|
+
$ sechroom skills install --client all keep both global clients in sync
|
|
5425
5802
|
$ sechroom skills install --scope project write them to ./.claude/skills instead
|
|
5426
5803
|
$ sechroom skills list what's materialised on disk
|
|
5427
5804
|
$ sechroom skills clean remove the materialised skill files
|
|
@@ -5430,11 +5807,15 @@ Examples:
|
|
|
5430
5807
|
$ sechroom skills package --from-source --workspace wsp_abc -o ./dist zip a draft from source
|
|
5431
5808
|
$ sechroom skills set-lane --code-lane claude-code-chris --design-lane claude-design-chris
|
|
5432
5809
|
|
|
5810
|
+
When --client is omitted, configured client flags/environment variables win;
|
|
5811
|
+
otherwise existing default client homes are detected. Both configured/detected
|
|
5812
|
+
clients select all; an unconfigured machine preserves the legacy Claude default.
|
|
5813
|
+
|
|
5433
5814
|
`
|
|
5434
5815
|
);
|
|
5435
|
-
skills.command("install").description("Materialise your installed skills to disk (the already-installed bundle \u2014 no server install)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--dry-run", "print what would be written; write nothing").option("--json", "machine output").action((opts, cmd) => runInstall(SKILL_SPEC, cmd, opts));
|
|
5436
|
-
skills.command("list").description("List the skills materialised on disk (per resolved config dir)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--json", "machine output").action((opts, cmd) => runList(SKILL_SPEC, cmd, opts));
|
|
5437
|
-
skills.command("clean [slug]").description(`Remove skill files materialised to disk (default ${DEFAULT_SKILLS_SLUG})`).option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--json", "machine output").action((slugArg, opts, cmd) => runClean(SKILL_SPEC, cmd, opts, slugArg));
|
|
5816
|
+
skills.command("install").description("Materialise your installed skills to disk (the already-installed bundle \u2014 no server install)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--client <client>", "claude, codex, or all (default: configured/detected clients)").option("--dry-run", "print what would be written; write nothing").option("--json", "machine output").action((opts, cmd) => runInstall(SKILL_SPEC, cmd, opts));
|
|
5817
|
+
skills.command("list").description("List the skills materialised on disk (per resolved config dir)").option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--client <client>", "claude, codex, or all (default: configured/detected clients)").option("--json", "machine output").action((opts, cmd) => runList(SKILL_SPEC, cmd, opts));
|
|
5818
|
+
skills.command("clean [slug]").description(`Remove skill files materialised to disk (default ${DEFAULT_SKILLS_SLUG})`).option("--scope <scope>", "global (config dir / CLAUDE_CONFIG_DIR) or project (<cwd>/.claude) \u2014 default global").option("--local", "alias for --scope project").option("--client <client>", "claude, codex, or all (default: configured/detected clients)").option("--json", "machine output").action((slugArg, opts, cmd) => runClean(SKILL_SPEC, cmd, opts, slugArg));
|
|
5438
5819
|
skills.command("preview").description("Render a workspace's draft bundle from source (no publish/install) and report the components").requiredOption("--workspace <id>", "workspace holding the draft bundle sources (wsp_\u2026)").option("--slug <slug>", "override the derived bundle slug").option("--title <title>", "override the derived bundle title").option("--version <version>", "override the derived bundle version").option("--default-install-parent <path>", "override the derived default install parent").option("--json", "machine output (the full RenderBundlePreviewResponse)").action(async (opts, cmd) => {
|
|
5439
5820
|
const json = Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json);
|
|
5440
5821
|
const client = await makeClient(resolveConfig(cmd.optsWithGlobals()));
|
|
@@ -5788,7 +6169,7 @@ function registerTelemetry(program2) {
|
|
|
5788
6169
|
);
|
|
5789
6170
|
});
|
|
5790
6171
|
telemetry.command("hook").description(
|
|
5791
|
-
"Per-turn telemetry self-report for
|
|
6172
|
+
"Per-turn telemetry self-report for Claude Code hooks \u2014 Stop/SubagentStop \u2192 parsed + terminal, Notification/PermissionDenied \u2192 approval (reads stdin; no-op unless bound). Fail-soft."
|
|
5792
6173
|
).action(async (_opts, cmd) => {
|
|
5793
6174
|
try {
|
|
5794
6175
|
const raw = await readStdin2();
|
|
@@ -5797,21 +6178,10 @@ function registerTelemetry(program2) {
|
|
|
5797
6178
|
const binding = findBinding(cwd);
|
|
5798
6179
|
if (!binding) return process.exit(0);
|
|
5799
6180
|
const usage = input.transcript_path ? parseTranscript(input.transcript_path) : null;
|
|
5800
|
-
|
|
6181
|
+
const events = buildHookEvents(input, usage, binding.taskId);
|
|
6182
|
+
if (events.length === 0) return process.exit(0);
|
|
5801
6183
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
5802
|
-
await postTelemetry(cfg, binding.decompositionId,
|
|
5803
|
-
{
|
|
5804
|
-
taskId: binding.taskId,
|
|
5805
|
-
kind: "Parsed",
|
|
5806
|
-
tokensIn: usage.tokensIn,
|
|
5807
|
-
tokensOut: usage.tokensOut,
|
|
5808
|
-
contextUsed: usage.contextUsed,
|
|
5809
|
-
contextWindow: usage.contextWindow,
|
|
5810
|
-
text: null,
|
|
5811
|
-
approvalState: null,
|
|
5812
|
-
verdict: null
|
|
5813
|
-
}
|
|
5814
|
-
]);
|
|
6184
|
+
await postTelemetry(cfg, binding.decompositionId, events);
|
|
5815
6185
|
return process.exit(0);
|
|
5816
6186
|
} catch {
|
|
5817
6187
|
return process.exit(0);
|
|
@@ -5839,7 +6209,12 @@ function registerTelemetry(program2) {
|
|
|
5839
6209
|
scope,
|
|
5840
6210
|
cwd
|
|
5841
6211
|
});
|
|
5842
|
-
const commands = {
|
|
6212
|
+
const commands = {
|
|
6213
|
+
Stop: "sechroom telemetry hook",
|
|
6214
|
+
SubagentStop: "sechroom telemetry hook",
|
|
6215
|
+
Notification: "sechroom telemetry hook",
|
|
6216
|
+
PermissionDenied: "sechroom telemetry hook"
|
|
6217
|
+
};
|
|
5843
6218
|
try {
|
|
5844
6219
|
const multi = targets.length > 1;
|
|
5845
6220
|
const results = targets.map((t) => {
|
|
@@ -5940,6 +6315,55 @@ function windowFor(model, contextUsed = 0) {
|
|
|
5940
6315
|
if (m.includes("[1m]") || m.includes("-1m")) return 1e6;
|
|
5941
6316
|
return contextUsed > 2e5 ? 1e6 : 2e5;
|
|
5942
6317
|
}
|
|
6318
|
+
function buildHookEvents(input, usage, taskId) {
|
|
6319
|
+
const events = [];
|
|
6320
|
+
const base = (kind, over) => ({
|
|
6321
|
+
taskId,
|
|
6322
|
+
kind,
|
|
6323
|
+
tokensIn: null,
|
|
6324
|
+
tokensOut: null,
|
|
6325
|
+
contextUsed: null,
|
|
6326
|
+
contextWindow: null,
|
|
6327
|
+
text: null,
|
|
6328
|
+
approvalState: null,
|
|
6329
|
+
verdict: null,
|
|
6330
|
+
...over
|
|
6331
|
+
});
|
|
6332
|
+
if (usage) {
|
|
6333
|
+
events.push(
|
|
6334
|
+
base("Parsed", {
|
|
6335
|
+
tokensIn: usage.tokensIn,
|
|
6336
|
+
tokensOut: usage.tokensOut,
|
|
6337
|
+
contextUsed: usage.contextUsed,
|
|
6338
|
+
contextWindow: usage.contextWindow
|
|
6339
|
+
})
|
|
6340
|
+
);
|
|
6341
|
+
}
|
|
6342
|
+
switch (input.hook_event_name) {
|
|
6343
|
+
case "PermissionDenied":
|
|
6344
|
+
events.push(
|
|
6345
|
+
base("Approval", {
|
|
6346
|
+
approvalState: "denied",
|
|
6347
|
+
text: input.tool_name ?? input.message ?? null
|
|
6348
|
+
})
|
|
6349
|
+
);
|
|
6350
|
+
break;
|
|
6351
|
+
case "Notification":
|
|
6352
|
+
if (isPermissionNotification(input))
|
|
6353
|
+
events.push(base("Approval", { text: input.message ?? null }));
|
|
6354
|
+
break;
|
|
6355
|
+
case "Stop":
|
|
6356
|
+
case "SubagentStop":
|
|
6357
|
+
events.push(base("Terminal", { text: input.last_assistant_message ?? null }));
|
|
6358
|
+
break;
|
|
6359
|
+
}
|
|
6360
|
+
return events;
|
|
6361
|
+
}
|
|
6362
|
+
function isPermissionNotification(input) {
|
|
6363
|
+
const t = (input.notification_type ?? input.type ?? "").toLowerCase();
|
|
6364
|
+
if (t) return t.includes("permission");
|
|
6365
|
+
return (input.message ?? "").toLowerCase().includes("permission");
|
|
6366
|
+
}
|
|
5943
6367
|
async function readStdin2() {
|
|
5944
6368
|
if (process.stdin.isTTY) return "";
|
|
5945
6369
|
const chunks = [];
|
|
@@ -6333,6 +6757,7 @@ registerRelationships(program);
|
|
|
6333
6757
|
registerWorkspace(program);
|
|
6334
6758
|
registerProject(program);
|
|
6335
6759
|
registerDecomposition(program);
|
|
6760
|
+
registerExecutor(program);
|
|
6336
6761
|
registerClose(program);
|
|
6337
6762
|
registerFiling(program);
|
|
6338
6763
|
registerContinuity(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sechroom/cli",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.14",
|
|
4
4
|
"description": "Sechroom CLI — a thin, generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -36,12 +36,14 @@
|
|
|
36
36
|
"openapi-typescript": "^7.4.0",
|
|
37
37
|
"tsup": "^8.3.0",
|
|
38
38
|
"tsx": "^4.19.0",
|
|
39
|
-
"typescript": "^5.6.0"
|
|
39
|
+
"typescript": "^5.6.0",
|
|
40
|
+
"typescript-7": "npm:typescript@^7.0.2"
|
|
40
41
|
},
|
|
41
42
|
"scripts": {
|
|
42
43
|
"gen": "openapi-typescript \"${SECHROOM_OPENAPI_URL:-https://app.sechroom.ai/api/openapi/v1.json}\" -o src/generated/api.d.ts",
|
|
43
44
|
"build": "tsup src/index.ts --format esm --target node20 --clean",
|
|
44
45
|
"dev": "tsx src/index.ts",
|
|
46
|
+
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
45
47
|
"check-types": "tsc --noEmit"
|
|
46
48
|
}
|
|
47
49
|
}
|