@sechroom/cli 2026.7.16 → 2026.7.18

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 (2) hide show
  1. package/dist/index.js +70 -12
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1140,7 +1140,10 @@ function resolveReferences(systemRows, personalRows, surface) {
1140
1140
  }
1141
1141
 
1142
1142
  // src/setup/skill-resolution-io.ts
1143
- var AGENT_TARGET = { "claude-code": "claude-agent" };
1143
+ var AGENT_TARGET = {
1144
+ "claude-code": "claude-agent",
1145
+ "gpt-codex": "gpt-codex-agent"
1146
+ };
1144
1147
  function agentTargetFor(surface) {
1145
1148
  return AGENT_TARGET[surface] ?? `${surface}-agent`;
1146
1149
  }
@@ -1205,12 +1208,58 @@ function writeSkills(dir, skills, surface) {
1205
1208
  if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
1206
1209
  return written;
1207
1210
  }
1211
+ function splitAgentFrontmatter(body) {
1212
+ body = body.replaceAll("\r\n", "\n");
1213
+ if (!body.startsWith("---\n")) return { instructions: body };
1214
+ const end = body.indexOf("\n---\n", 4);
1215
+ if (end < 0) return { instructions: body };
1216
+ const frontmatter = body.slice(4, end);
1217
+ const field = (key) => {
1218
+ const line = frontmatter.split("\n").find((candidate) => candidate.startsWith(`${key}:`));
1219
+ return line?.slice(key.length + 1).trim().replace(/^(["'])(.*)\1$/, "$2");
1220
+ };
1221
+ return { name: field("name"), description: field("description"), instructions: body.slice(end + 5).trimStart() };
1222
+ }
1223
+ function validateCodexSkills(skills) {
1224
+ for (const skill of skills) {
1225
+ const parsed = splitAgentFrontmatter(skill.body);
1226
+ if (parsed.name !== skill.name || !parsed.description) {
1227
+ throw new Error(
1228
+ `Codex skill '${skill.name}' must begin with YAML frontmatter containing matching name and a description.`
1229
+ );
1230
+ }
1231
+ }
1232
+ }
1233
+ var WORKER_NAMES = ["substrate-drafter", "substrate-miner", "substrate-verifier"];
1234
+ function validateCodexWorkerDependencies(skills, agents) {
1235
+ const available = new Set(agents.map((agent) => agent.name));
1236
+ const missing = WORKER_NAMES.filter(
1237
+ (worker) => skills.some((skill) => skill.body.includes(worker)) && !available.has(worker)
1238
+ );
1239
+ if (missing.length) {
1240
+ throw new Error(`Codex skills reference missing agent template(s): ${missing.join(", ")}.`);
1241
+ }
1242
+ }
1243
+ function codexAgentToml(agent) {
1244
+ const parsed = splitAgentFrontmatter(agent.body);
1245
+ if (!parsed.description) {
1246
+ throw new Error(`Codex agent '${agent.name}' requires a description in its leading YAML frontmatter.`);
1247
+ }
1248
+ return [
1249
+ `name = ${JSON.stringify(parsed.name || agent.name)}`,
1250
+ `description = ${JSON.stringify(parsed.description)}`,
1251
+ `developer_instructions = ${JSON.stringify(parsed.instructions.trimEnd())}`,
1252
+ ""
1253
+ ].join("\n");
1254
+ }
1208
1255
  function writeAgents(dir, agents, surface) {
1209
1256
  if (agents.length) mkdirSync3(dir, { recursive: true });
1210
1257
  const written = [];
1211
1258
  for (const a of agents) {
1212
- const file = `${a.name}.md`;
1213
- writeFileSync3(join4(dir, file), a.body.endsWith("\n") ? a.body : a.body + "\n");
1259
+ const codex = surface === CLIENT_SURFACE.codex;
1260
+ const file = `${a.name}.${codex ? "toml" : "md"}`;
1261
+ const body = codex ? codexAgentToml(a) : a.body.endsWith("\n") ? a.body : a.body + "\n";
1262
+ writeFileSync3(join4(dir, file), body);
1214
1263
  written.push(file);
1215
1264
  }
1216
1265
  if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
@@ -1239,7 +1288,7 @@ var AGENT_SPEC = {
1239
1288
  dir: agentsDir,
1240
1289
  resolve: resolveAgentSet,
1241
1290
  write: writeAgents,
1242
- supportsCodex: false
1291
+ supportsCodex: true
1243
1292
  };
1244
1293
  function scopeOf(opts) {
1245
1294
  return opts.local ? "project" : resolveScope(opts.scope);
@@ -1315,6 +1364,10 @@ async function runInstall(spec, cmd, opts) {
1315
1364
  const rows = await fetchTemplateRows(cfg, personalWorkspaceId);
1316
1365
  const results = targets.map((t) => {
1317
1366
  const items = spec.resolve(rows, t.surface);
1367
+ if (t.client === "codex" && spec.kind === "skill") {
1368
+ validateCodexSkills(items);
1369
+ validateCodexWorkerDependencies(items, resolveAgentSet(rows, t.surface));
1370
+ }
1318
1371
  const refs = spec.kind === "skill" ? resolveReferenceSet(rows, t.surface) : [];
1319
1372
  const written = dryRun ? items.map((i) => i.name) : spec.write(t.dir, items, t.surface);
1320
1373
  const refsWritten = dryRun ? refs.map((r) => r.name) : writeReferencesIntoSkillDirs(t.dir, items, refs);
@@ -1440,17 +1493,19 @@ function registerAgents(program2) {
1440
1493
  `
1441
1494
  Examples:
1442
1495
  $ sechroom agents install materialise your installed agents to ~/.claude/agents
1496
+ $ sechroom agents install --client codex materialise native TOML agents to ~/.codex/agents
1443
1497
  $ sechroom agents install --scope project write them to ./.claude/agents instead
1444
1498
  $ sechroom agents install --claude-config-dir ~/.claude-work target another instance
1445
1499
  $ sechroom agents list what's materialised on disk
1446
1500
  $ sechroom agents clean remove the materialised agent files
1447
1501
 
1448
- Agents are resolved from the agent target (target:claude-agent), the dispatchable
1449
- workers your loop skills call (e.g. find-prior-art \u2192 substrate-miner).`
1502
+ Agents are resolved from the client's agent target (target:claude-agent or
1503
+ target:gpt-codex-agent), the dispatchable workers your loop skills call
1504
+ (e.g. find-prior-art \u2192 substrate-miner).`
1450
1505
  );
1451
- agents.command("install").description("Materialise your installed subagents 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(AGENT_SPEC, cmd, opts));
1452
- agents.command("list").description("List the subagents 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(AGENT_SPEC, cmd, opts));
1453
- agents.command("clean [slug]").description(`Remove subagent 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(AGENT_SPEC, cmd, opts, slugArg));
1506
+ agents.command("install").description("Materialise your installed subagents 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("--client <client>", "claude, codex, or all").option("--json", "machine output").action((opts, cmd) => runInstall(AGENT_SPEC, cmd, opts));
1507
+ agents.command("list").description("List the subagents 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").option("--client <client>", "claude, codex, or all").action((opts, cmd) => runList(AGENT_SPEC, cmd, opts));
1508
+ agents.command("clean [slug]").description(`Remove subagent 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").option("--client <client>", "claude, codex, or all").action((slugArg, opts, cmd) => runClean(AGENT_SPEC, cmd, opts, slugArg));
1454
1509
  }
1455
1510
 
1456
1511
  // src/commands/channel.ts
@@ -3170,6 +3225,7 @@ function registerExecutor(program2) {
3170
3225
  "Register and operate a local Claude Code/Codex executor advertisement"
3171
3226
  );
3172
3227
  executor.command("install").description("Configure this checkout's harness to advertise itself as a WLP executor").option("--connector <id>", "Approved local-session ConnectorDefinition id").option("--instance-key <key>", "Stable executor identity (defaults to .sechroom/lane.json code-lane)").option("--runtime <kind>", "claude-code | codex").option("--surface <surface>", "claude | codex").option("--capability <key...>", "Capability operation keys claimed by this instance").option("--relay <id>", "Relay identity shared by sibling instances", "sechroom-cli-local").option("--subscription-name <name>", "SignalR delivery binding name", "executor-dispatch").option("--ttl <seconds>", "Advertisement TTL (30-600)", parseInteger, 600).option("--refresh-after <seconds>", "Minimum age before a hook refreshes", parseInteger, 40).option("-y, --yes", "Non-interactive: accept detected surface and lane defaults", false).option("--dry-run", "Show hook files without writing", false).action(async (opts, cmd) => {
3228
+ const globals = cmd.optsWithGlobals();
3173
3229
  const lane = readSem()?.values["code-lane"];
3174
3230
  const detected = detectHookSurfaces(process.cwd());
3175
3231
  let surface = opts.surface;
@@ -3214,9 +3270,11 @@ function registerExecutor(program2) {
3214
3270
  writeFileSync9(statePath, JSON.stringify(state, null, 2) + "\n");
3215
3271
  ensureStateDirIgnored(checkout);
3216
3272
  }
3217
- const surfaces = [surface];
3218
- for (const s of surfaces) {
3219
- const results = s === "claude" ? [installClaudeCommands(join11(checkout, ".claude"), CLAUDE_EXECUTOR_HOOKS, opts.dryRun)] : installCodexCommands(join11(checkout, ".codex"), CODEX_EXECUTOR_HOOKS, opts.dryRun);
3273
+ const configuredClaudeDirs = globals.claudeConfigDir || process.env.CLAUDE_CONFIG_DIR ? resolveClaudeTargets({ override: globals.claudeConfigDir }).map((target) => target.dir) : [join11(checkout, ".claude")];
3274
+ const configuredCodexHomes = globals.codexHome || process.env.CODEX_HOME ? resolveCodexHomes({ override: globals.codexHome }) : [join11(checkout, ".codex")];
3275
+ const hookTargets = surface === "claude" ? configuredClaudeDirs : configuredCodexHomes;
3276
+ for (const target of hookTargets) {
3277
+ const results = surface === "claude" ? [installClaudeCommands(target, CLAUDE_EXECUTOR_HOOKS, opts.dryRun)] : installCodexCommands(target, CODEX_EXECUTOR_HOOKS, opts.dryRun);
3220
3278
  for (const result of results) process.stderr.write(describe(result, opts.dryRun) + "\n");
3221
3279
  }
3222
3280
  warnIfSechroomNotOnPath();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.7.16",
3
+ "version": "2026.7.18",
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",