@sechroom/cli 2026.6.28 → 2026.6.29-rc.692f8a8f

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 +44 -260
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16,14 +16,12 @@ import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from "fs";
16
16
  var CONFIG_DIR = join(homedir(), ".config", "sechroom");
17
17
  var CONFIG_FILE = join(CONFIG_DIR, "config.json");
18
18
  var TOKEN_FILE = join(CONFIG_DIR, "token.json");
19
- var ACCOUNTS_FILE = join(CONFIG_DIR, "accounts.json");
20
- var DEFAULT_ACCOUNT = "default";
21
19
  var STATE_DIR_NAME = ".sechroom";
22
20
  var BASELINE_CONFIG_NAME = ".sechroom.json";
23
21
  var OVERRIDE_CONFIG_NAME = join(STATE_DIR_NAME, "config.json");
24
- var BINDING_FIELDS = ["schemaVersion", "baseUrl", "tenant", "workspaceId", "defaultProjectId", "workspaces"];
22
+ var BINDING_FIELDS = ["schemaVersion", "baseUrl", "tenant", "workspaceId", "defaultProjectId"];
25
23
  var DEFAULT_BASE_URL = "https://app.sechroom.ai/api";
26
- var LOCAL_CONFIG_SCHEMA_VERSION = 3;
24
+ var LOCAL_CONFIG_SCHEMA_VERSION = 2;
27
25
  function ensureDir() {
28
26
  if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
29
27
  }
@@ -46,87 +44,23 @@ function writeDcrClientId(baseUrl, clientId) {
46
44
  const p = readPersisted();
47
45
  writePersisted({ clientIds: { ...p.clientIds ?? {}, [baseUrl]: clientId } });
48
46
  }
49
- function readAccountsFile() {
50
- try {
51
- const parsed = JSON.parse(readFileSync(ACCOUNTS_FILE, "utf8"));
52
- if (parsed && typeof parsed === "object" && parsed.accounts) return parsed;
53
- } catch {
54
- }
47
+ function readToken() {
48
+ const envTok = process.env.SECHROOM_TOKEN;
49
+ if (envTok) return { accessToken: envTok };
55
50
  try {
56
- const legacy = JSON.parse(readFileSync(TOKEN_FILE, "utf8"));
57
- if (legacy?.accessToken) {
58
- const migrated = {
59
- accounts: {
60
- [DEFAULT_ACCOUNT]: { ...legacy, baseUrl: readPersisted().baseUrl ?? DEFAULT_BASE_URL }
61
- },
62
- default: DEFAULT_ACCOUNT
63
- };
64
- writeAccountsFile(migrated);
65
- rmSync(TOKEN_FILE, { force: true });
66
- return migrated;
67
- }
51
+ return JSON.parse(readFileSync(TOKEN_FILE, "utf8"));
68
52
  } catch {
53
+ return null;
69
54
  }
70
- return { accounts: {} };
71
55
  }
72
- function writeAccountsFile(file) {
56
+ function writeToken(tok) {
73
57
  ensureDir();
74
- writeFileSync(ACCOUNTS_FILE, JSON.stringify(file, null, 2), { mode: 384 });
75
- }
76
- function readAccount(alias) {
77
- return readAccountsFile().accounts[alias] ?? null;
78
- }
79
- function writeAccount(alias, entry) {
80
- const file = readAccountsFile();
81
- file.accounts[alias] = entry;
82
- if (!file.default) file.default = alias;
83
- writeAccountsFile(file);
84
- }
85
- function removeAccount(alias) {
86
- const file = readAccountsFile();
87
- if (!(alias in file.accounts)) return false;
88
- delete file.accounts[alias];
89
- if (file.default === alias) file.default = Object.keys(file.accounts)[0];
90
- writeAccountsFile(file);
91
- return true;
92
- }
93
- function listAccounts() {
94
- const file = readAccountsFile();
95
- return Object.entries(file.accounts).map(([alias, entry]) => ({
96
- alias,
97
- entry,
98
- isDefault: file.default === alias
99
- }));
100
- }
101
- function setDefaultAccount(alias) {
102
- const file = readAccountsFile();
103
- if (!(alias in file.accounts)) throw new Error(`No account "${alias}". Run \`sechroom login --account ${alias}\` first.`);
104
- file.default = alias;
105
- writeAccountsFile(file);
106
- }
107
- function resolveAccountAlias(flagAccount) {
108
- return flagAccount ?? process.env.SECHROOM_ACCOUNT ?? readLocalConfig().account ?? readAccountsFile().default ?? DEFAULT_ACCOUNT;
109
- }
110
- function writeLocalAccount(alias) {
111
- const home = findConfigHome() ?? process.cwd();
112
- const overridePath = join(home, OVERRIDE_CONFIG_NAME);
113
- mkdirSync(dirname(overridePath), { recursive: true });
114
- const current = readJsonConfig(overridePath) ?? {};
115
- writeFileSync(
116
- overridePath,
117
- JSON.stringify({ ...current, account: alias }, null, 2),
118
- { mode: 384 }
119
- );
120
- return overridePath;
121
- }
122
- function readToken() {
123
- const envTok = process.env.SECHROOM_TOKEN;
124
- if (envTok) return { accessToken: envTok };
125
- return readAccount(resolveAccountAlias());
58
+ writeFileSync(TOKEN_FILE, JSON.stringify(tok, null, 2), { mode: 384 });
126
59
  }
127
60
  function clearToken() {
128
- const alias = resolveAccountAlias();
129
- return removeAccount(alias) ? `${ACCOUNTS_FILE} (account "${alias}")` : void 0;
61
+ if (!existsSync(TOKEN_FILE)) return void 0;
62
+ rmSync(TOKEN_FILE);
63
+ return TOKEN_FILE;
130
64
  }
131
65
  function clearPersisted() {
132
66
  if (!existsSync(CONFIG_FILE)) return void 0;
@@ -161,14 +95,11 @@ function readLocalConfig() {
161
95
  tenant: merged.tenant,
162
96
  workspaceId: merged.workspaceId,
163
97
  defaultProjectId: merged.defaultProjectId,
164
- workspaces: merged.workspaces,
165
- account: merged.account,
166
- path: existsSync(baselinePath) ? baselinePath : overridePath,
167
- home
98
+ path: existsSync(baselinePath) ? baselinePath : overridePath
168
99
  };
169
100
  }
170
- function writeLocalConfig(patch, opts) {
171
- const home = (opts?.here ? process.cwd() : findConfigHome()) ?? process.cwd();
101
+ function writeLocalConfig(patch) {
102
+ const home = findConfigHome() ?? process.cwd();
172
103
  const baselinePath = join(home, BASELINE_CONFIG_NAME);
173
104
  const overridePath = join(home, OVERRIDE_CONFIG_NAME);
174
105
  const current = readJsonConfig(baselinePath) ?? {};
@@ -186,30 +117,6 @@ function committedBindingPath(dir) {
186
117
  const p = join(dir, BASELINE_CONFIG_NAME);
187
118
  return existsSync(p) ? p : void 0;
188
119
  }
189
- function selectWorkspaceBinding(local, explicitName) {
190
- const bindings = local.workspaces ?? [];
191
- if (explicitName) {
192
- const hit = bindings.find((b) => b.name === explicitName);
193
- if (!hit)
194
- throw new Error(
195
- `No workspace binding named "${explicitName}" in ${local.home ?? "this repo"}'s .sechroom.json` + (bindings.length > 0 ? ` (have: ${bindings.map((b) => b.name).join(", ")})` : "") + ". Add one with `sechroom workspace bind`."
196
- );
197
- return hit;
198
- }
199
- if (!local.home || bindings.length === 0) return void 0;
200
- const rel = process.cwd().startsWith(local.home) ? process.cwd().slice(local.home.length).replace(/^[/\\]/, "") : "";
201
- const norm = (p) => p.replace(/\/\*\*$/, "").replace(/[/\\]+$/, "");
202
- let best;
203
- for (const b of bindings) {
204
- for (const raw of b.paths ?? []) {
205
- const prefix = norm(raw);
206
- if (prefix.length === 0) continue;
207
- const matches = rel === prefix || rel.startsWith(prefix + "/");
208
- if (matches && (!best || prefix.length > best.len)) best = { binding: b, len: prefix.length };
209
- }
210
- }
211
- return best?.binding;
212
- }
213
120
  function resolveConfig(flags) {
214
121
  const local = readLocalConfig();
215
122
  const persisted = readPersisted();
@@ -220,11 +127,9 @@ function resolveConfig(flags) {
220
127
  "No tenant set. The Sechroom API rejects untenanted requests (HTTP 400). Pass --tenant <id>, set SECHROOM_TENANT, run `sechroom config set tenant <id>`, or `sechroom config set --local tenant <id>` for this directory."
221
128
  );
222
129
  }
223
- const binding = selectWorkspaceBinding(local, flags.binding ?? process.env.SECHROOM_BINDING);
224
- const workspaceId = process.env.SECHROOM_WORKSPACE ?? binding?.workspaceId ?? local.workspaceId ?? persisted.workspaceId ?? void 0;
225
- const defaultProjectId = (binding ? binding.defaultProjectId : void 0) ?? local.defaultProjectId ?? persisted.defaultProjectId ?? void 0;
226
- const account = resolveAccountAlias(flags.account);
227
- return { baseUrl: baseUrl.replace(/\/$/, ""), tenant, account, workspaceId, defaultProjectId, clientId: persisted.clientId };
130
+ const workspaceId = process.env.SECHROOM_WORKSPACE ?? local.workspaceId ?? persisted.workspaceId ?? void 0;
131
+ const defaultProjectId = local.defaultProjectId ?? persisted.defaultProjectId ?? void 0;
132
+ return { baseUrl: baseUrl.replace(/\/$/, ""), tenant, workspaceId, defaultProjectId, clientId: persisted.clientId };
228
133
  }
229
134
  function describeConfig(flags) {
230
135
  const local = readLocalConfig();
@@ -239,17 +144,10 @@ function describeConfig(flags) {
239
144
  return { value: void 0, source: "unset" };
240
145
  };
241
146
  const baseUrl = pick(flags.baseUrl, process.env.SECHROOM_BASE_URL, local.baseUrl, g.baseUrl, DEFAULT_BASE_URL);
242
- let binding;
243
- try {
244
- binding = selectWorkspaceBinding(local, flags.binding ?? process.env.SECHROOM_BINDING);
245
- } catch {
246
- binding = void 0;
247
- }
248
- const workspaceId = process.env.SECHROOM_WORKSPACE ? { value: process.env.SECHROOM_WORKSPACE, source: "env" } : binding ? { value: binding.workspaceId, source: `binding "${binding.name}"${localTag !== "local" ? ` (${local.path})` : ""}` } : pick(void 0, void 0, local.workspaceId, g.workspaceId);
249
147
  return {
250
148
  baseUrl: { value: baseUrl.value, source: baseUrl.source },
251
149
  tenant: pick(flags.tenant, process.env.SECHROOM_TENANT, local.tenant, g.tenant),
252
- workspaceId,
150
+ workspaceId: pick(void 0, process.env.SECHROOM_WORKSPACE, local.workspaceId, g.workspaceId),
253
151
  localPath: local.path
254
152
  };
255
153
  }
@@ -347,25 +245,14 @@ async function exchange(meta, clientId, code, verifier, redirectUri) {
347
245
  })
348
246
  });
349
247
  if (!res.ok) throw new Error(`Token exchange failed (${res.status}): ${await res.text()}`);
350
- return await res.json();
248
+ persistTokenResponse(await res.json());
351
249
  }
352
- function decodeEmail(accessToken) {
353
- try {
354
- const payload = accessToken.split(".")[1];
355
- if (!payload) return void 0;
356
- const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
357
- return typeof claims.email === "string" ? claims.email : void 0;
358
- } catch {
359
- return void 0;
360
- }
361
- }
362
- function persistTokenResponse(account, baseUrl, tok) {
363
- writeAccount(account, {
364
- baseUrl,
250
+ function persistTokenResponse(json) {
251
+ const tok = json;
252
+ writeToken({
365
253
  accessToken: tok.access_token,
366
254
  refreshToken: tok.refresh_token,
367
- expiresAt: tok.expires_in ? Date.now() + tok.expires_in * 1e3 : void 0,
368
- email: decodeEmail(tok.access_token)
255
+ expiresAt: tok.expires_in ? Date.now() + tok.expires_in * 1e3 : void 0
369
256
  });
370
257
  }
371
258
  async function login(cfg) {
@@ -389,25 +276,14 @@ ${authUrl}
389
276
  `);
390
277
  await open(authUrl.toString());
391
278
  const code = await loopback.code;
392
- const tok = await exchange(meta, clientId, code, verifier, loopback.redirectUri);
393
- persistTokenResponse(cfg.account, cfg.baseUrl, tok);
394
- const email = decodeEmail(tok.access_token);
395
- process.stderr.write(`Signed in${email ? ` as ${email}` : ""} (account "${cfg.account}"). Token cached.
396
- `);
279
+ await exchange(meta, clientId, code, verifier, loopback.redirectUri);
280
+ process.stderr.write("Signed in. Token cached.\n");
397
281
  }
398
282
  async function requireToken(cfg) {
399
283
  if (process.env.SECHROOM_TOKEN) return process.env.SECHROOM_TOKEN;
400
- const cached = readAccount(cfg.account);
284
+ const cached = readToken();
401
285
  if (!cached?.accessToken) {
402
- throw new Error(
403
- `Not signed in (account "${cfg.account}"). Run \`sechroom login --account ${cfg.account}\` (or set SECHROOM_TOKEN for headless use).`
404
- );
405
- }
406
- if (cached.baseUrl !== cfg.baseUrl) {
407
- const matching = listAccounts().filter((a) => a.entry.baseUrl === cfg.baseUrl).map((a) => a.alias);
408
- throw new Error(
409
- `Account "${cfg.account}" is signed in to ${cached.baseUrl}, but this call targets ${cfg.baseUrl}. ` + (matching.length > 0 ? `Use --account ${matching[0]} (signed in there)` + (matching.length > 1 ? ` \u2014 or one of: ${matching.join(", ")}` : "") + ", or " : "") + `run \`sechroom login --account ${cfg.account}\` against it.`
410
- );
286
+ throw new Error("Not signed in. Run `sechroom login` (or set SECHROOM_TOKEN for headless use).");
411
287
  }
412
288
  const nearExpiry = cached.expiresAt !== void 0 && Date.now() > cached.expiresAt - 6e4;
413
289
  if (nearExpiry && cached.refreshToken) {
@@ -421,10 +297,10 @@ async function requireToken(cfg) {
421
297
  })
422
298
  });
423
299
  if (res.ok) {
424
- persistTokenResponse(cfg.account, cfg.baseUrl, await res.json());
425
- return readAccount(cfg.account).accessToken;
300
+ persistTokenResponse(await res.json());
301
+ return readToken().accessToken;
426
302
  }
427
- throw new Error(`Session expired and refresh failed. Run \`sechroom login --account ${cfg.account}\` again.`);
303
+ throw new Error("Session expired and refresh failed. Run `sechroom login` again.");
428
304
  }
429
305
  return cached.accessToken;
430
306
  }
@@ -669,62 +545,6 @@ function fail(error) {
669
545
  process.exit(1);
670
546
  }
671
547
 
672
- // src/commands/accounts.ts
673
- function registerAccounts(program2) {
674
- const accounts = program2.command("accounts").description("Manage signed-in identities (multi-account)");
675
- accounts.addHelpText(
676
- "after",
677
- `
678
- Examples:
679
- $ sechroom login --account work sign in a second identity under the "work" alias
680
- $ sechroom accounts list see every signed-in identity + which is active here
681
- $ sechroom accounts use work make "work" the machine default
682
- $ sechroom accounts use work --local pin "work" for THIS directory only (gitignored)
683
- $ sechroom logout --account work sign the "work" identity out`
684
- );
685
- accounts.command("list").description("List signed-in accounts (alias, email, server, expiry)").action((_opts, cmd) => {
686
- const active2 = resolveAccountAlias(cmd.optsWithGlobals().account);
687
- const rows = listAccounts().map(({ alias, entry, isDefault }) => ({
688
- alias,
689
- email: entry.email,
690
- baseUrl: entry.baseUrl,
691
- expiresAt: entry.expiresAt ? new Date(entry.expiresAt).toISOString() : void 0,
692
- isDefault,
693
- isActiveHere: alias === active2
694
- }));
695
- if (cmd.optsWithGlobals().json) return emit({ accounts: rows, active: active2 }, true);
696
- if (rows.length === 0) {
697
- console.log(style.dim("No accounts signed in. Run `sechroom login` (optionally --account <alias>)."));
698
- return;
699
- }
700
- for (const r of rows) {
701
- const markers = [r.isActiveHere ? style.green("active") : "", r.isDefault ? style.dim("default") : ""].filter(Boolean).join(" ");
702
- console.log(
703
- `${r.alias.padEnd(16)} ${(r.email ?? "\u2014").padEnd(28)} ${r.baseUrl} ${markers}`
704
- );
705
- }
706
- });
707
- accounts.command("use <alias>").description("Select the account used for calls (machine default, or --local for this directory)").option("--local", "pin for this directory only (gitignored .sechroom/config.json)").action((alias, opts, cmd) => {
708
- const json = cmd.optsWithGlobals().json;
709
- if (opts.local) {
710
- const path = writeLocalAccount(alias);
711
- if (json) return emit({ account: alias, scope: "local", path }, true);
712
- console.log(style.green(`Account "${alias}" pinned for this directory (${path}).`));
713
- return;
714
- }
715
- setDefaultAccount(alias);
716
- if (json) return emit({ account: alias, scope: "global" }, true);
717
- console.log(style.green(`Account "${alias}" is now the machine default.`));
718
- });
719
- accounts.command("remove <alias>").description("Forget an account's cached token (local removal \u2014 does not revoke at the server)").action((alias, _opts, cmd) => {
720
- const removed = removeAccount(alias);
721
- if (cmd.optsWithGlobals().json) return emit({ removed: removed ? [alias] : [] }, true);
722
- console.log(
723
- removed ? style.green(`Account "${alias}" removed.`) : style.dim(`No account "${alias}".`)
724
- );
725
- });
726
- }
727
-
728
548
  // src/commands/memory.ts
729
549
  function registerMemory(program2) {
730
550
  const memory = program2.command("memory").description("Create, read, and search memories");
@@ -1202,26 +1022,6 @@ Examples:
1202
1022
  // src/commands/workspace.ts
1203
1023
  function registerWorkspace(program2) {
1204
1024
  const workspace = program2.command("workspace").description("Create, browse, and manage workspaces");
1205
- workspace.command("bind <workspaceId>").description("Bind a workspace to this repo (.sechroom.json). With --name it becomes a NAMED v3 binding (multi-workspace); --paths scopes it to subtrees.").option("--name <name>", "binding name (enables multi-workspace; selectable via --binding / path match)").option("--paths <csv>", "comma-separated directory prefixes (relative to the repo root) this binding covers, e.g. frontend,docs").option("--default-project <id>", "informational default project for this binding").option("--here", "write the binding file at THIS directory even when a parent already carries one").action((workspaceId, opts, cmd) => {
1206
- const json = cmd.optsWithGlobals().json;
1207
- if (!opts.name) {
1208
- const path2 = writeLocalConfig({ workspaceId, ...opts.defaultProject ? { defaultProjectId: opts.defaultProject } : {} }, { here: Boolean(opts.here) });
1209
- if (json) return emit({ workspaceId, path: path2 }, true);
1210
- console.log(style.green(`Workspace ${workspaceId} bound (${path2}).`));
1211
- return;
1212
- }
1213
- const local = readLocalConfig();
1214
- const bindings = (local.workspaces ?? []).filter((b) => b.name !== opts.name);
1215
- bindings.push({
1216
- name: opts.name,
1217
- workspaceId,
1218
- ...opts.defaultProject ? { defaultProjectId: opts.defaultProject } : {},
1219
- ...opts.paths ? { paths: String(opts.paths).split(",").map((p) => p.trim()).filter(Boolean) } : {}
1220
- });
1221
- const path = writeLocalConfig({ workspaces: bindings }, { here: Boolean(opts.here) });
1222
- if (json) return emit({ binding: opts.name, workspaceId, paths: opts.paths ?? null, path }, true);
1223
- console.log(style.green(`Binding "${opts.name}" \u2192 ${workspaceId} saved (${path}).`));
1224
- });
1225
1025
  workspace.addHelpText(
1226
1026
  "after",
1227
1027
  `
@@ -3379,7 +3179,7 @@ async function ensureTenant(baseUrl, g, opts) {
3379
3179
  const local = readLocalConfig();
3380
3180
  let tenant = g.tenant ?? process.env.SECHROOM_TENANT ?? local.tenant ?? persisted.tenant ?? "";
3381
3181
  if (!tenant) {
3382
- const client = await makeClient({ baseUrl, tenant: "", account: resolveAccountAlias(), clientId: persisted.clientId });
3182
+ const client = await makeClient({ baseUrl, tenant: "", clientId: persisted.clientId });
3383
3183
  const { data, error } = await client.GET("/auth/me/tenants", {});
3384
3184
  if (error) {
3385
3185
  fail(`Couldn't list your tenants: ${JSON.stringify(error)}. Pass --tenant <id> to skip this.`);
@@ -3414,7 +3214,7 @@ async function ensureTenant(baseUrl, g, opts) {
3414
3214
  }
3415
3215
  }
3416
3216
  const existingWorkspace = local.workspaceId ?? persisted.workspaceId ?? void 0;
3417
- const wsClient = await makeClient({ baseUrl, tenant, account: resolveAccountAlias(), clientId: persisted.clientId });
3217
+ const wsClient = await makeClient({ baseUrl, tenant, clientId: persisted.clientId });
3418
3218
  const workspaceId = await resolveWorkspaceBinding(wsClient, existingWorkspace, {
3419
3219
  yes: opts.yes,
3420
3220
  json: opts.json,
@@ -3436,7 +3236,7 @@ async function ensureTenant(baseUrl, g, opts) {
3436
3236
  if (opts.persist !== false) {
3437
3237
  const patch = { baseUrl, tenant, ...workspaceId ? { workspaceId } : {} };
3438
3238
  if (storeLocal) {
3439
- const path = writeLocalConfig(patch, { here: Boolean(opts.here) });
3239
+ const path = writeLocalConfig(patch);
3440
3240
  if (!opts.json) process.stderr.write(`${ok("\u2713")} config saved to ${path} (directory-local)
3441
3241
  `);
3442
3242
  } else {
@@ -3449,7 +3249,7 @@ async function ensureTenant(baseUrl, g, opts) {
3449
3249
  `);
3450
3250
  }
3451
3251
  }
3452
- return { baseUrl, tenant, account: resolveAccountAlias(), workspaceId, defaultProjectId, clientId: persisted.clientId };
3252
+ return { baseUrl, tenant, workspaceId, defaultProjectId, clientId: persisted.clientId };
3453
3253
  }
3454
3254
  async function ensureAuth(cfg, yes) {
3455
3255
  if (process.env.SECHROOM_TOKEN) return;
@@ -3606,7 +3406,7 @@ async function runRecurse(cfg, g, opts) {
3606
3406
  summarizeFanout(results, { dryRun });
3607
3407
  }
3608
3408
  function registerOnboard(program2) {
3609
- 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("--here", "with --local: write the binding at THIS directory even when a parent already carries one \u2014 binds a subtree (e.g. a monorepo's frontend/) to its own workspace", 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(
3610
3410
  "after",
3611
3411
  `
3612
3412
  Examples:
@@ -3632,13 +3432,13 @@ Examples:
3632
3432
  if (opts.designLane) process.env.SECHROOM_DESIGN_LANE = opts.designLane;
3633
3433
  if (opts.recurse) {
3634
3434
  const baseUrl2 = resolveBaseUrl(g);
3635
- await ensureAuth({ baseUrl: baseUrl2, tenant: "", account: resolveAccountAlias(), clientId: readPersisted().clientId }, yes);
3435
+ await ensureAuth({ baseUrl: baseUrl2, tenant: "", clientId: readPersisted().clientId }, yes);
3636
3436
  const cfg2 = await ensureTenant(baseUrl2, g, { yes: true, json, persist: false });
3637
3437
  await runRecurse(cfg2, g, { yes, dryRun, json, lane: opts.lane, designLane: opts.designLane });
3638
3438
  return;
3639
3439
  }
3640
3440
  const baseUrl = resolveBaseUrl(g);
3641
- await ensureAuth({ baseUrl, tenant: "", account: resolveAccountAlias(), clientId: readPersisted().clientId }, yes);
3441
+ await ensureAuth({ baseUrl, tenant: "", clientId: readPersisted().clientId }, yes);
3642
3442
  const cfg = await ensureTenant(baseUrl, g, { yes, json, local: Boolean(opts.local), workspace: opts.workspace, persist: !check });
3643
3443
  const tz = await ensureTimezone(cfg, { yes, dryRun: dryRun || check });
3644
3444
  if (!json && tz.action !== "already-set") {
@@ -4135,23 +3935,9 @@ function removeMaterialisedSkills(dir) {
4135
3935
  return removed;
4136
3936
  }
4137
3937
  function registerReset(program2) {
4138
- program2.command("logout").description("Sign out \u2014 remove the active account's cached token (--account <alias> for a specific one, --all for every account)").option("--account <alias>", "sign a specific account out").option("--all", "sign every account out").action((opts, cmd) => {
4139
- const json = cmd.optsWithGlobals().json;
4140
- if (opts.all) {
4141
- const removed2 = listAccounts().map((a) => a.alias);
4142
- for (const alias of removed2) removeAccount(alias);
4143
- if (json) return emit({ removed: removed2 }, true);
4144
- console.log(removed2.length > 0 ? style.green(`Signed out ${removed2.length} account(s): ${removed2.join(", ")}.`) : style.dim("Already signed out (no accounts)."));
4145
- return;
4146
- }
4147
- if (opts.account) {
4148
- const ok2 = removeAccount(opts.account);
4149
- if (json) return emit({ removed: ok2 ? [opts.account] : [] }, true);
4150
- console.log(ok2 ? style.green(`Signed out account "${opts.account}".`) : style.dim(`No account "${opts.account}".`));
4151
- return;
4152
- }
3938
+ program2.command("logout").description("Sign out \u2014 remove the cached (global) auth token").action((_opts, cmd) => {
4153
3939
  const removed = clearToken();
4154
- if (json) return emit({ removed: removed ? [removed] : [] }, true);
3940
+ if (cmd.optsWithGlobals().json) return emit({ removed: removed ? [removed] : [] }, true);
4155
3941
  console.log(
4156
3942
  removed ? style.green("Signed out \u2014 auth token removed.") : style.dim("Already signed out (no token).")
4157
3943
  );
@@ -4213,7 +3999,7 @@ function resolveVersion() {
4213
3999
  }
4214
4000
  }
4215
4001
  var program = new Command();
4216
- program.name("sechroom").description("Sechroom CLI \u2014 thin generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.").version(resolveVersion()).option("--base-url <url>", "API base URL (overrides config / SECHROOM_BASE_URL)").option("--tenant <tenant>", "Tenant id (required by the API; overrides config / SECHROOM_TENANT)").option("--binding <name>", "Named workspace binding from .sechroom.json `workspaces` (overrides path auto-selection / SECHROOM_BINDING)").option("--json", "Emit compact JSON (for scripts and agents)", false);
4002
+ program.name("sechroom").description("Sechroom CLI \u2014 thin generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.").version(resolveVersion()).option("--base-url <url>", "API base URL (overrides config / SECHROOM_BASE_URL)").option("--tenant <tenant>", "Tenant id (required by the API; overrides config / SECHROOM_TENANT)").option("--json", "Emit compact JSON (for scripts and agents)", false);
4217
4003
  program.addHelpText(
4218
4004
  "after",
4219
4005
  `
@@ -4246,12 +4032,11 @@ Examples:
4246
4032
  $ sechroom login sign in to the configured base URL + tenant
4247
4033
  $ sechroom login --base-url https://staging.app.sechroom.ai/api
4248
4034
  $ export SECHROOM_TOKEN=<bearer> headless: skip login entirely (CI / agents)`
4249
- ).option("--account <alias>", "sign in under a named account alias (multi-account; default: the active alias)").action(async (opts, cmd) => {
4035
+ ).action(async (_opts, cmd) => {
4250
4036
  const g = cmd.optsWithGlobals();
4251
4037
  const persisted = readPersisted();
4252
4038
  const baseUrl = g.baseUrl ?? process.env.SECHROOM_BASE_URL ?? persisted.baseUrl ?? "https://app.sechroom.ai/api";
4253
- const account = resolveAccountAlias(opts.account);
4254
- await login({ baseUrl: baseUrl.replace(/\/$/, ""), tenant: g.tenant ?? "", account });
4039
+ await login({ baseUrl: baseUrl.replace(/\/$/, ""), tenant: g.tenant ?? "" });
4255
4040
  });
4256
4041
  var config = program.command("config").description("Manage persisted CLI config");
4257
4042
  config.addHelpText(
@@ -4287,7 +4072,7 @@ config.command("set <key> <value>").description("Set baseUrl | tenant | workspac
4287
4072
  });
4288
4073
  config.command("show").description("Print resolved config + sources (flag > env > local > global > default)").action((_opts, cmd) => {
4289
4074
  const g = cmd.optsWithGlobals();
4290
- const d = describeConfig({ baseUrl: g.baseUrl, tenant: g.tenant, binding: g.binding });
4075
+ const d = describeConfig({ baseUrl: g.baseUrl, tenant: g.tenant });
4291
4076
  if (g.json) {
4292
4077
  process.stdout.write(
4293
4078
  JSON.stringify({
@@ -4308,7 +4093,6 @@ local: ${d.localPath ?? "(none)"} ${JSON.stringify(readLocalConfig())}
4308
4093
  `
4309
4094
  );
4310
4095
  });
4311
- registerAccounts(program);
4312
4096
  registerMemory(program);
4313
4097
  registerWorklog(program);
4314
4098
  registerLookup(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.28",
3
+ "version": "2026.6.29-rc.692f8a8f",
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",