@sechroom/cli 2026.9.2 → 2026.9.3

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 +118 -23
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -2260,6 +2260,7 @@ function resolveSemPathForRead(start = process.cwd()) {
2260
2260
  while (true) {
2261
2261
  const candidate = join6(dir, SEM_FILE);
2262
2262
  if (existsSync4(candidate)) return candidate;
2263
+ if (existsSync4(join6(dir, ".git"))) return void 0;
2263
2264
  const parent = dirname2(dir);
2264
2265
  if (parent === dir) return void 0;
2265
2266
  dir = parent;
@@ -8130,7 +8131,8 @@ function resolveLane(flagLane, cwd) {
8130
8131
  const env = process.env.SECHROOM_LANE;
8131
8132
  if (env) return env;
8132
8133
  const start = cwd ?? process.cwd();
8133
- const base = readSem(resolveSemPathForRead(start))?.values["code-lane"];
8134
+ const semPath = resolveSemPathForRead(start);
8135
+ const base = semPath ? readSem(semPath)?.values["code-lane"] : void 0;
8134
8136
  if (!base) return void 0;
8135
8137
  return applyWorktreeLaneSuffix(base, start);
8136
8138
  }
@@ -10725,6 +10727,64 @@ AGPL \u2014 no Herdr code is vendored here.`
10725
10727
 
10726
10728
  // src/commands/lane.ts
10727
10729
  var LANE_KEYS = ["code-lane", "design-lane"];
10730
+ function apiFlagsFrom(options) {
10731
+ return {
10732
+ baseUrl: typeof options.baseUrl === "string" ? options.baseUrl : void 0,
10733
+ tenant: typeof options.tenant === "string" ? options.tenant : void 0,
10734
+ account: typeof options.account === "string" ? options.account : void 0,
10735
+ binding: typeof options.binding === "string" ? options.binding : void 0
10736
+ };
10737
+ }
10738
+ function resolvedPinnedLanes(values) {
10739
+ const lanes = LANE_KEYS.flatMap((key) => {
10740
+ const lane = values[key];
10741
+ return lane ? [applyWorktreeLaneSuffix(lane)] : [];
10742
+ });
10743
+ return [...new Set(lanes)];
10744
+ }
10745
+ function reportRegistrationWarning(laneId, error) {
10746
+ const detail = formatFailureMessage(error);
10747
+ const message = `could not register source lane '${laneId}': ${detail}`;
10748
+ process.stderr.write(`${warn(`warning: ${message}`)}
10749
+ `);
10750
+ return message;
10751
+ }
10752
+ async function registerPinnedLanes(laneIds, flags) {
10753
+ if (laneIds.length === 0) return [];
10754
+ let client;
10755
+ try {
10756
+ client = await makeClient(resolveConfig(flags));
10757
+ } catch (error) {
10758
+ return laneIds.map((laneId) => ({
10759
+ laneId,
10760
+ registered: false,
10761
+ warning: reportRegistrationWarning(laneId, error)
10762
+ }));
10763
+ }
10764
+ const results = [];
10765
+ for (const laneId of laneIds) {
10766
+ try {
10767
+ const response = await client.POST("/identity/source-lanes", {
10768
+ body: { laneId }
10769
+ });
10770
+ const failure = response.error ?? (response.response && !response.response.ok ? `HTTP ${response.response.status} ${response.response.statusText}`.trim() : void 0);
10771
+ if (failure !== void 0) throw new Error(formatFailureMessage(failure));
10772
+ results.push({ laneId, registered: true });
10773
+ } catch (error) {
10774
+ results.push({
10775
+ laneId,
10776
+ registered: false,
10777
+ warning: reportRegistrationWarning(laneId, error)
10778
+ });
10779
+ }
10780
+ }
10781
+ return results;
10782
+ }
10783
+ function printRegistrationSummary(registrations) {
10784
+ const registered = registrations.filter((result) => result.registered).map((result) => result.laneId);
10785
+ if (registered.length > 0)
10786
+ console.log(style.dim(`Registered source lane${registered.length === 1 ? "" : "s"}: ${registered.join(", ")}`));
10787
+ }
10728
10788
  function showLane(json) {
10729
10789
  const found = readSem();
10730
10790
  if (!found) {
@@ -10746,35 +10806,57 @@ function showLane(json) {
10746
10806
  Object.entries(resolved).forEach(([k, v]) => console.log(" " + style.bold(k) + " = " + v));
10747
10807
  if (suffixed) console.log(style.dim(" (worktree -N suffix applied \u2014 non-primary git worktree)"));
10748
10808
  }
10749
- function setLane(opts) {
10809
+ async function setLane(opts) {
10750
10810
  if (!opts.codeLane && !opts.designLane) fail("Provide --code-lane and/or --design-lane.");
10751
10811
  const target = localSemPath();
10752
10812
  const values = readLocalSemValues();
10753
10813
  if (opts.codeLane) values["code-lane"] = opts.codeLane;
10754
10814
  if (opts.designLane) values["design-lane"] = opts.designLane;
10755
10815
  writeSem(values, target);
10756
- if (opts.json) return emit({ path: target, values }, true);
10816
+ const registrations = await registerPinnedLanes(
10817
+ resolvedPinnedLanes(values),
10818
+ opts.apiFlags ?? {}
10819
+ );
10820
+ if (opts.json) return emit({ path: target, values, registrations }, true);
10757
10821
  console.log(style.green(`Wrote lane pin \u2192 ${target} ${style.dim("(git-ignored)")}`));
10758
10822
  Object.entries(values).forEach(([k, v]) => console.log(" " + style.dim(k) + " = " + v));
10823
+ printRegistrationSummary(registrations);
10759
10824
  }
10760
10825
  function registerLane(program2) {
10761
10826
  const lane = program2.command("lane").description("Show this checkout's continuity lane pin (worktree-aware -N suffix applied)").option("--json", "machine output").action((opts, cmd) => showLane(Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json)));
10762
- lane.command("set").description("Write this checkout's lane pin to ./.sechroom/lane.json").option("--code-lane <id>", "code-surface lane id (e.g. claude-code-chris)").option("--design-lane <id>", "design / substrate-authoring lane id (e.g. claude-design-chris)").option("--json", "machine output").action(
10763
- (opts, cmd) => setLane({
10827
+ lane.command("set").description("Write this checkout's lane pin to ./.sechroom/lane.json").option("--code-lane <id>", "code-surface lane id (e.g. claude-code-chris)").option("--design-lane <id>", "design / substrate-authoring lane id (e.g. claude-design-chris)").option("--json", "machine output").action((opts, cmd) => {
10828
+ const globals = cmd.optsWithGlobals();
10829
+ return setLane({
10764
10830
  codeLane: opts.codeLane,
10765
10831
  designLane: opts.designLane,
10766
- json: Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json)
10767
- })
10768
- );
10832
+ json: Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json),
10833
+ apiFlags: apiFlagsFrom(globals)
10834
+ });
10835
+ });
10836
+ lane.command("register").description("Register the source lanes already pinned in this checkout").option("--json", "machine output").action(async (opts, cmd) => {
10837
+ const found = readSem();
10838
+ if (!found)
10839
+ fail("No ./.sechroom/lane.json pin in this checkout. Run 'sechroom lane set'.");
10840
+ const globals = cmd.optsWithGlobals();
10841
+ const registrations = await registerPinnedLanes(
10842
+ resolvedPinnedLanes(found.values),
10843
+ apiFlagsFrom(globals)
10844
+ );
10845
+ const json = Boolean(opts.json) || Boolean(globals.json);
10846
+ if (json) return emit({ path: found.path, registrations }, true);
10847
+ printRegistrationSummary(registrations);
10848
+ });
10769
10849
  lane.addHelpText(
10770
10850
  "after",
10771
10851
  `
10772
10852
  Examples:
10773
10853
  $ sechroom lane show the resolved lane(s)
10774
10854
  $ sechroom lane set --code-lane claude-code-chris --design-lane claude-design-chris
10855
+ $ sechroom lane register register existing pinned lane(s)
10775
10856
 
10776
10857
  In a non-primary git worktree the ratified concurrent-session -N suffix is auto-applied (SBC-1094).
10777
- (Aliases: 'sechroom skills lane' / 'skills set-lane' \u2014 kept for back-compat.)`
10858
+ Lane resolution stops at the nearest git worktree/repository root; a parent checkout's .sechroom/lane.json is never inherited.
10859
+ (Aliases: 'sechroom skills lane' / 'skills set-lane' \u2014 kept for back-compat; set-lane also registers.)`
10778
10860
  );
10779
10861
  }
10780
10862
 
@@ -12596,6 +12678,7 @@ function copyChoice(opts) {
12596
12678
  }
12597
12679
  async function maybeOfferCopies(cfg, setup, targets, keys, personalWorkspaceId, choice) {
12598
12680
  if (!personalWorkspaceId || choice === "no") return;
12681
+ let source;
12599
12682
  const seen = /* @__PURE__ */ new Set();
12600
12683
  for (const key of keys) {
12601
12684
  const instr = targets[key]?.instruction;
@@ -12624,8 +12707,8 @@ version, the shared template stays clean, and you can discard back anytime.
12624
12707
  make = await promptYesNo("Make a personal copy to customise?");
12625
12708
  }
12626
12709
  if (make) {
12627
- const source = resolveSourceLane(void 0);
12628
- await createOverride(cfg, resolved, personalWorkspaceId, source);
12710
+ const copySource = source ??= resolveSourceLane(void 0);
12711
+ await createOverride(cfg, resolved, personalWorkspaceId, copySource);
12629
12712
  process.stderr.write(
12630
12713
  `\u2713 personal copy created for ${instr.surfaceKey} \u2014 edit it on the Agent setup page or via the API.
12631
12714
  `
@@ -13880,6 +13963,10 @@ async function runRecurse(cfg, g, opts) {
13880
13963
  }
13881
13964
  summarizeFanout(results, { dryRun });
13882
13965
  }
13966
+ async function runOnboardCopyPhase(opts) {
13967
+ if (!opts.json && !opts.dryRun && !opts.check) await opts.ensureLanePin();
13968
+ if (!opts.dryRun && !opts.check) await opts.maybeOfferCopies();
13969
+ }
13883
13970
  function registerOnboard(program2) {
13884
13971
  program2.command("onboard").description(
13885
13972
  "Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project"
@@ -14097,16 +14184,20 @@ Try: ${style.cyan('sechroom memory search "..."')} or ${style.cyan("sechroom -
14097
14184
  codexScope: scope
14098
14185
  });
14099
14186
  const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
14100
- if (!dryRun && !check) {
14101
- await maybeOfferCopies(
14187
+ await runOnboardCopyPhase({
14188
+ json,
14189
+ dryRun,
14190
+ check,
14191
+ ensureLanePin: () => ensureLanePin(cfg, { yes, dryRun, clients: keys }),
14192
+ maybeOfferCopies: () => maybeOfferCopies(
14102
14193
  cfg,
14103
14194
  setup,
14104
14195
  targets,
14105
14196
  keys,
14106
14197
  personalWorkspaceId,
14107
14198
  copyChoice(opts)
14108
- );
14109
- }
14199
+ )
14200
+ });
14110
14201
  const writeMcp = wire === "full";
14111
14202
  const result = [];
14112
14203
  for (const key of keys) {
@@ -14129,9 +14220,6 @@ Try: ${style.cyan('sechroom memory search "..."')} or ${style.cyan("sechroom -
14129
14220
  });
14130
14221
  }
14131
14222
  const evalCounts = buildCheckReport(result).eval;
14132
- if (!json && !dryRun) {
14133
- await ensureLanePin(cfg, { yes, dryRun, clients: keys });
14134
- }
14135
14223
  if (!json && !dryRun) {
14136
14224
  for (const t of claudeTargets) {
14137
14225
  await maybeOfferSkills(cfg, personalWorkspaceId, {
@@ -14870,13 +14958,20 @@ clients select all; an unconfigured machine preserves the legacy Claude default.
14870
14958
  if (json) return emit(result, true);
14871
14959
  console.log(style.green("\u2713") + ` wrote ${style.bold(result.path)} ${style.dim(`(${result.bytes} bytes)`)}`);
14872
14960
  });
14873
- skills.command("set-lane").description("Alias of `sechroom lane set` (kept for back-compat) \u2014 write this checkout's lane pin").option("--code-lane <id>", "code-surface lane id (e.g. claude-code-chris)").option("--design-lane <id>", "design / substrate-authoring lane id (e.g. claude-design-chris)").option("--json", "machine output").action(
14874
- (opts, cmd) => setLane({
14961
+ skills.command("set-lane").description("Alias of `sechroom lane set` (kept for back-compat) \u2014 write this checkout's lane pin").option("--code-lane <id>", "code-surface lane id (e.g. claude-code-chris)").option("--design-lane <id>", "design / substrate-authoring lane id (e.g. claude-design-chris)").option("--json", "machine output").action((opts, cmd) => {
14962
+ const globals = cmd.optsWithGlobals();
14963
+ return setLane({
14875
14964
  codeLane: opts.codeLane,
14876
14965
  designLane: opts.designLane,
14877
- json: Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json)
14878
- })
14879
- );
14966
+ json: Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json),
14967
+ apiFlags: {
14968
+ baseUrl: globals.baseUrl,
14969
+ tenant: globals.tenant,
14970
+ account: globals.account,
14971
+ binding: globals.binding
14972
+ }
14973
+ });
14974
+ });
14880
14975
  skills.command("lane").description("Alias of `sechroom lane` (kept for back-compat) \u2014 show this checkout's lane pin").option("--json", "machine output").action((opts, cmd) => showLane(Boolean(opts.json) || Boolean(cmd.optsWithGlobals().json)));
14881
14976
  skills.command("set-workflow").description("Set your per-operator workflow defaults (server-side; follows you across tenants)").option("--default-code-lane <id>", "personal default code lane (e.g. claude-code-chris)").option("--default-design-lane <id>", "personal default design lane (e.g. claude-design-chris)").option("--handover-recipient <id>", "your daily-handover counterparty (e.g. andy)").option("--json", "machine output").action(async (opts, cmd) => {
14882
14977
  if (!opts.defaultCodeLane && !opts.defaultDesignLane && !opts.handoverRecipient)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.9.2",
3
+ "version": "2026.9.3",
4
4
  "description": "Command-line interface for Sechroom — sign in, wire your AI tools, and work with your sechroom from the terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -43,7 +43,7 @@
43
43
  "gen": "openapi-typescript \"${SECHROOM_OPENAPI_URL:-https://app.sechroom.ai/api/openapi/v1.json}\" -o src/generated/api.d.ts",
44
44
  "build": "tsup src/index.ts --format esm --target node20 --clean",
45
45
  "dev": "tsx src/index.ts",
46
- "test": "node --import tsx --test \"src/**/*.test.ts\"",
46
+ "test": "node scripts/run-tests.mjs",
47
47
  "check-types": "tsc --noEmit"
48
48
  }
49
49
  }