@sechroom/cli 2026.6.25 → 2026.6.27

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 +55 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2681,6 +2681,24 @@ function codeLanePrefix(clients) {
2681
2681
  for (const c of CLIENT_PRIORITY) if (clients.includes(c)) return CODE_LANE_PREFIX_BY_CLIENT[c];
2682
2682
  return "claude-code";
2683
2683
  }
2684
+ async function inferLanes(cfg, clients) {
2685
+ let wf;
2686
+ let profile;
2687
+ try {
2688
+ const client = await makeClient(cfg);
2689
+ [wf, profile] = await Promise.all([
2690
+ client.GET("/me/workflow-preferences", {}).then((r) => r.data).catch(() => void 0),
2691
+ client.GET("/me/profile", {}).then((r) => r.data).catch(() => void 0)
2692
+ ]);
2693
+ } catch {
2694
+ }
2695
+ const handle = handleFromDisplayName(profile?.effectiveDisplayName);
2696
+ const prefix = codeLanePrefix(clients ?? ["claude-code"]);
2697
+ return {
2698
+ code: process.env.SECHROOM_CODE_LANE ?? wf?.defaultCodeLane ?? (handle ? `${prefix}-${handle}` : void 0),
2699
+ design: process.env.SECHROOM_DESIGN_LANE ?? wf?.defaultDesignLane ?? (handle ? `claude-design-${handle}` : void 0)
2700
+ };
2701
+ }
2684
2702
  function writePin(code, design) {
2685
2703
  const values = {};
2686
2704
  if (code) values["code-lane"] = code;
@@ -2693,20 +2711,7 @@ function writePin(code, design) {
2693
2711
  async function ensureLanePin(cfg, opts) {
2694
2712
  if (opts.dryRun) return;
2695
2713
  if (readSem()) return;
2696
- let wf;
2697
- let profile;
2698
- try {
2699
- const client = await makeClient(cfg);
2700
- [wf, profile] = await Promise.all([
2701
- client.GET("/me/workflow-preferences", {}).then((r) => r.data).catch(() => void 0),
2702
- client.GET("/me/profile", {}).then((r) => r.data).catch(() => void 0)
2703
- ]);
2704
- } catch {
2705
- }
2706
- const handle = handleFromDisplayName(profile?.effectiveDisplayName);
2707
- const prefix = codeLanePrefix(opts.clients ?? ["claude-code"]);
2708
- const codeGuess = wf?.defaultCodeLane ?? (handle ? `${prefix}-${handle}` : void 0);
2709
- const designGuess = wf?.defaultDesignLane ?? (handle ? `claude-design-${handle}` : void 0);
2714
+ const { code: codeGuess, design: designGuess } = await inferLanes(cfg, opts.clients);
2710
2715
  if (!canPrompt() || opts.yes) {
2711
2716
  if (opts.yes && (codeGuess || designGuess)) writePin(codeGuess, designGuess);
2712
2717
  return;
@@ -3342,6 +3347,34 @@ ${style.bold(entry.path)} ${style.dim("is not bound yet.")}
3342
3347
  reason: `unbound \u2014 bind to ${ws}`
3343
3348
  };
3344
3349
  }
3350
+ async function resolveFanoutLane(cfg, opts) {
3351
+ let code = opts.lane ?? process.env.SECHROOM_CODE_LANE;
3352
+ let design = opts.designLane ?? process.env.SECHROOM_DESIGN_LANE;
3353
+ if (!code || !design) {
3354
+ const clients = detectInstalledClients(process.cwd());
3355
+ const inferred = await inferLanes(cfg, clients.length ? clients : void 0);
3356
+ code = code ?? inferred.code;
3357
+ design = design ?? inferred.design;
3358
+ }
3359
+ if (!opts.lane && !opts.yes && !opts.dryRun && canPrompt() && (code || design)) {
3360
+ process.stderr.write(`
3361
+ This fan-out will pin the same lane in every repo:
3362
+ `);
3363
+ if (code) process.stderr.write(` ${style.dim("code-lane")} = ${style.cyan(code)}
3364
+ `);
3365
+ if (design) process.stderr.write(` ${style.dim("design-lane")} = ${style.cyan(design)}
3366
+ `);
3367
+ if (!await promptYesNo("Use this lane for all repos?")) {
3368
+ code = await promptText("Code-lane id (blank = let each repo infer)?", code ?? "") || void 0;
3369
+ design = await promptText("Design-lane id (blank = skip)?", design ?? "") || void 0;
3370
+ }
3371
+ }
3372
+ if (code) process.env.SECHROOM_CODE_LANE = code;
3373
+ else delete process.env.SECHROOM_CODE_LANE;
3374
+ if (design) process.env.SECHROOM_DESIGN_LANE = design;
3375
+ else delete process.env.SECHROOM_DESIGN_LANE;
3376
+ return { code, design };
3377
+ }
3345
3378
  async function runRecurse(cfg, g, opts) {
3346
3379
  const { yes, dryRun, json } = opts;
3347
3380
  const root = process.cwd();
@@ -3359,6 +3392,9 @@ async function runRecurse(cfg, g, opts) {
3359
3392
  process.stderr.write(`${style.bold("onboard --recurse")} ${style.dim(`(${entries.length} repo${entries.length === 1 ? "" : "s"} from ${sourceLabel})`)}
3360
3393
  `);
3361
3394
  }
3395
+ const lane = await resolveFanoutLane(cfg, { lane: opts.lane, designLane: opts.designLane, yes, dryRun });
3396
+ if (!json && lane.code) process.stderr.write(`${ok("\u2713")} lane ${style.cyan(lane.code)}${lane.design ? ` ${style.dim(`/ ${lane.design}`)}` : ""} for every repo
3397
+ `);
3362
3398
  const client = await makeClient(cfg);
3363
3399
  const plans = [];
3364
3400
  for (const entry of entries) plans.push(await planRecurseChild(entry, root, client, { yes, dryRun }));
@@ -3370,7 +3406,7 @@ async function runRecurse(cfg, g, opts) {
3370
3406
  summarizeFanout(results, { dryRun });
3371
3407
  }
3372
3408
  function registerOnboard(program2) {
3373
- program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--recurse", "orchestration-root mode: onboard every child repo under this dir (auto-discovered, or from ./.sechroom/repos.json) \u2014 refreshes bound repos, prompts a workspace per new one", false).option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).option("--local", "save the binding (tenant + base URL + workspace) to a committed .sechroom.json in this repo instead of the global config", false).option("--workspace <id>", "bind this directory to a workspace (skips the interactive workspace pick)").option("--cli-only", "configure the CLI only \u2014 don't wire any AI client (no MCP config, no agent files)", false).option("--no-mcp", "skip the MCP server config (.mcp.json etc.); still write the agent instruction files").option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--dry-run", "walk through without writing files or changing the profile", false).option("--refresh", "re-fetch descriptors and refresh any out-of-date managed blocks (local edits preserved to .proposed)", false).option("--force", "rewrite every managed block, overwriting local edits inside the markers (content outside untouched)", false).option("--check", "report whether anything would change and exit (0 = all current, 1 = stale/drift/absent); writes nothing", false).option("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients, global config, full wire)", false).addHelpText(
3409
+ program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--recurse", "orchestration-root mode: onboard every child repo under this dir (auto-discovered, or from ./.sechroom/repos.json) \u2014 refreshes bound repos, prompts a workspace per new one", false).option("--lane <id>", "set the code-lane (substrate source identity) explicitly instead of inferring it; with --recurse it's used for every child repo").option("--design-lane <id>", "set the design-lane explicitly (substrate-authoring identity); with --recurse applies to every child").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).option("--local", "save the binding (tenant + base URL + workspace) to a committed .sechroom.json in this repo instead of the global config", false).option("--workspace <id>", "bind this directory to a workspace (skips the interactive workspace pick)").option("--cli-only", "configure the CLI only \u2014 don't wire any AI client (no MCP config, no agent files)", false).option("--no-mcp", "skip the MCP server config (.mcp.json etc.); still write the agent instruction files").option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--dry-run", "walk through without writing files or changing the profile", false).option("--refresh", "re-fetch descriptors and refresh any out-of-date managed blocks (local edits preserved to .proposed)", false).option("--force", "rewrite every managed block, overwriting local edits inside the markers (content outside untouched)", false).option("--check", "report whether anything would change and exit (0 = all current, 1 = stale/drift/absent); writes nothing", false).option("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients, global config, full wire)", false).addHelpText(
3374
3410
  "after",
3375
3411
  `
3376
3412
  Examples:
@@ -3380,6 +3416,7 @@ Examples:
3380
3416
  $ sechroom onboard --local save tenant + base URL to a committed ./.sechroom.json
3381
3417
  $ sechroom onboard --workspace wsp_XX bind this directory to a workspace (no pick prompt)
3382
3418
  $ sechroom onboard --recurse orchestration root: onboard every child repo under this dir
3419
+ $ sechroom onboard --recurse --lane claude-code-you pin one lane across every repo in the tree
3383
3420
  $ sechroom onboard --refresh refresh out-of-date instruction blocks in place
3384
3421
  $ sechroom onboard --check CI/pre-commit: nonzero exit if instructions are out of date
3385
3422
  $ sechroom onboard --yes non-interactive: defaults + global config + full wire
@@ -3391,11 +3428,13 @@ Examples:
3391
3428
  const mode = opts.check ? "check" : opts.force ? "force" : "apply";
3392
3429
  const check = mode === "check";
3393
3430
  const yes = Boolean(opts.yes) || check;
3431
+ if (opts.lane) process.env.SECHROOM_CODE_LANE = opts.lane;
3432
+ if (opts.designLane) process.env.SECHROOM_DESIGN_LANE = opts.designLane;
3394
3433
  if (opts.recurse) {
3395
3434
  const baseUrl2 = resolveBaseUrl(g);
3396
3435
  await ensureAuth({ baseUrl: baseUrl2, tenant: "", clientId: readPersisted().clientId }, yes);
3397
3436
  const cfg2 = await ensureTenant(baseUrl2, g, { yes: true, json, persist: false });
3398
- await runRecurse(cfg2, g, { yes, dryRun, json });
3437
+ await runRecurse(cfg2, g, { yes, dryRun, json, lane: opts.lane, designLane: opts.designLane });
3399
3438
  return;
3400
3439
  }
3401
3440
  const baseUrl = resolveBaseUrl(g);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.25",
3
+ "version": "2026.6.27",
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",