@sechroom/cli 2026.6.6 → 2026.6.7

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 (3) hide show
  1. package/README.md +29 -0
  2. package/dist/index.js +226 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -100,6 +100,13 @@ sechroom lookup sechroom:mem_XXXX --json # namespaced form also resolves
100
100
  sechroom --json memory get mem_XXXX # agent-friendly
101
101
  ```
102
102
 
103
+ **Output shape.** Mutating commands (`memory create`, `worklog append`) print a concise confirmation line — id + view URL — the way the MCP tools hand an LLM a result, instead of dumping the raw envelope. Pass `--json` for the full response body (the machine channel) on any command. Output is lightly colorized on a TTY and auto-plain when piped, under `--json`, or when `NO_COLOR` is set.
104
+
105
+ ```bash
106
+ sechroom memory create --text "a note" --title "Note"
107
+ # ✓ created memory mem_XXXX "Note" → https://sechroom.yi.ocd.codes/view/mem_XXXX
108
+ ```
109
+
103
110
  **Per-directory config.** A project dir can pin its own `tenant` + `baseUrl` in a local `.sechroom.json`, discovered by walking **up** from cwd (nearest wins, so any subdir inherits it). It overrides the global config — precedence: `--flag` > env > directory-local > global > default. `clientId` / auth state stays global.
104
111
 
105
112
  ```bash
@@ -137,6 +144,7 @@ Codex TOML table replace; instruction files use a managed marker block).
137
144
  sechroom init # Claude Code (default): .mcp.json + CLAUDE.md
138
145
  sechroom init --client all # claude-code, claude-desktop, codex, cursor
139
146
  sechroom init --client codex,cursor # a subset
147
+ sechroom init --mcp-only # just the MCP config (skip agent files)
140
148
  sechroom init --dry-run --json # preview the writes, no changes
141
149
 
142
150
  # granular pieces init orchestrates:
@@ -144,6 +152,27 @@ sechroom setup mcp claude-desktop # just the MCP config for one client
144
152
  sechroom setup agent-files codex # just the AGENTS.md instruction file
145
153
  ```
146
154
 
155
+ ### `sechroom onboard` — guided first run
156
+
157
+ `onboard` orchestrates the whole zero-to-wired path interactively: configure base
158
+ URL + tenant, sign in, set the profile timezone, then wire your AI client(s). Two
159
+ prompts make it fit how you actually work:
160
+
161
+ - **Where to save config** — globally (`~/.config/sechroom`, all projects) or a
162
+ directory-local `.sechroom.json` (this project + subdirs). Defaults to local
163
+ when a `.sechroom.json` already governs the dir.
164
+ - **How far to wire** — full (MCP server + agent instructions), agent
165
+ instructions only (skip `.mcp.json`), or **CLI only** (write nothing for AI
166
+ clients — for when you just want the `sechroom` command).
167
+
168
+ ```bash
169
+ sechroom onboard # interactive: asks where to save + how to wire
170
+ sechroom onboard --cli-only # just the CLI — no .mcp.json, no agent files
171
+ sechroom onboard --no-mcp # agent instructions only, skip MCP config
172
+ sechroom onboard --local # save tenant + base URL to ./.sechroom.json
173
+ sechroom onboard --yes # non-interactive: defaults + global config + full wire
174
+ ```
175
+
147
176
  Per client → where it writes:
148
177
 
149
178
  | client | MCP config | instruction file |
package/dist/index.js CHANGED
@@ -173,13 +173,13 @@ function startLoopback(state) {
173
173
  }
174
174
  const got = url.searchParams.get("code");
175
175
  const gotState = url.searchParams.get("state");
176
- const err = url.searchParams.get("error");
177
- res.writeHead(err ? 400 : 200, { "content-type": "text/html" });
176
+ const err2 = url.searchParams.get("error");
177
+ res.writeHead(err2 ? 400 : 200, { "content-type": "text/html" });
178
178
  res.end(
179
- err ? `<h3>Authorization failed: ${err}</h3>` : "<h3>Signed in to Sechroom.</h3><p>Return to the terminal.</p>"
179
+ err2 ? `<h3>Authorization failed: ${err2}</h3>` : "<h3>Signed in to Sechroom.</h3><p>Return to the terminal.</p>"
180
180
  );
181
181
  server.close();
182
- if (err) return rejectCode(new Error(`Authorization error: ${err}`));
182
+ if (err2) return rejectCode(new Error(`Authorization error: ${err2}`));
183
183
  if (!got) return rejectCode(new Error("No code in callback."));
184
184
  if (gotState !== state) return rejectCode(new Error("State mismatch \u2014 possible CSRF."));
185
185
  resolveCode(got);
@@ -277,6 +277,22 @@ var quiet = false;
277
277
  function setQuiet(q) {
278
278
  quiet = q;
279
279
  }
280
+ function colorOn() {
281
+ return !quiet && !process.env.NO_COLOR && process.env.FORCE_COLOR !== "0" && Boolean(process.stdout.isTTY);
282
+ }
283
+ function wrap(open2, close) {
284
+ return (s) => colorOn() ? `\x1B[${open2}m${s}\x1B[${close}m` : String(s);
285
+ }
286
+ var style = {
287
+ bold: wrap(1, 22),
288
+ dim: wrap(2, 22),
289
+ red: wrap(31, 39),
290
+ green: wrap(32, 39),
291
+ yellow: wrap(33, 39),
292
+ cyan: wrap(36, 39)
293
+ };
294
+ var ok = (s) => style.green(s);
295
+ var err = (s) => style.red(s);
280
296
  function active() {
281
297
  return !quiet && Boolean(process.stderr.isTTY);
282
298
  }
@@ -302,12 +318,12 @@ function spinner(text) {
302
318
  return {
303
319
  succeed(t) {
304
320
  clear();
305
- process.stderr.write(`\u2713 ${t ?? text}
321
+ process.stderr.write(`${ok("\u2713")} ${t ?? text}
306
322
  `);
307
323
  },
308
324
  fail(t) {
309
325
  clear();
310
- process.stderr.write(`\u2717 ${t ?? text}
326
+ process.stderr.write(`${err("\u2717")} ${t ?? text}
311
327
  `);
312
328
  },
313
329
  stop: clear
@@ -344,15 +360,48 @@ async function promptText(question, def) {
344
360
  rl.close();
345
361
  }
346
362
  }
363
+ async function promptSelect(question, choices, def) {
364
+ if (choices.length === 0) throw new Error("promptSelect: no choices");
365
+ const defIdx = Math.max(
366
+ 0,
367
+ def !== void 0 ? choices.findIndex((c) => c.value === def) : 0
368
+ );
369
+ if (!canPrompt()) return choices[defIdx].value;
370
+ const { createInterface } = await import("readline");
371
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
372
+ try {
373
+ process.stderr.write(`${style.bold(question)}
374
+ `);
375
+ choices.forEach((c, i) => {
376
+ const marker = i === defIdx ? style.cyan("\u203A") : " ";
377
+ const hint = c.hint ? ` ${style.dim(`\u2014 ${c.hint}`)}` : "";
378
+ process.stderr.write(` ${marker} ${style.bold(String(i + 1))}. ${c.label}${hint}
379
+ `);
380
+ });
381
+ const answer = await new Promise((resolve) => {
382
+ rl.question(`Choose ${style.dim(`[${defIdx + 1}]`)} `, resolve);
383
+ });
384
+ const trimmed = answer.trim();
385
+ if (!trimmed) return choices[defIdx].value;
386
+ const n = Number(trimmed);
387
+ if (Number.isInteger(n) && n >= 1 && n <= choices.length) return choices[n - 1].value;
388
+ const byLabel = choices.find(
389
+ (c) => c.label.toLowerCase().startsWith(trimmed.toLowerCase())
390
+ );
391
+ return byLabel ? byLabel.value : choices[defIdx].value;
392
+ } finally {
393
+ rl.close();
394
+ }
395
+ }
347
396
  async function withSpinner(text, fn) {
348
397
  const s = spinner(text);
349
398
  try {
350
399
  const result = await fn();
351
400
  s.succeed();
352
401
  return result;
353
- } catch (err) {
402
+ } catch (err2) {
354
403
  s.fail();
355
- throw err;
404
+ throw err2;
356
405
  }
357
406
  }
358
407
 
@@ -376,14 +425,25 @@ function emit(data, json) {
376
425
  process.stdout.write(JSON.stringify(data, null, 2) + "\n");
377
426
  }
378
427
  }
428
+ function publicUrl(url) {
429
+ return url.replace(/^https?:\/\/localhost:5012/, "https://sechroom.yi.ocd.codes");
430
+ }
431
+ function emitAction(summary, data, json) {
432
+ if (json) {
433
+ process.stdout.write(JSON.stringify(data) + "\n");
434
+ return;
435
+ }
436
+ process.stdout.write(`${ok("\u2713")} ${summary}
437
+ `);
438
+ }
379
439
  async function runApi(label, fn) {
380
440
  const s = spinner(label);
381
441
  let res;
382
442
  try {
383
443
  res = await fn();
384
- } catch (err) {
444
+ } catch (err2) {
385
445
  s.fail();
386
- fail(err);
446
+ fail(err2);
387
447
  }
388
448
  if (res.error !== void 0 && res.error !== null) {
389
449
  s.fail();
@@ -402,6 +462,16 @@ function fail(error) {
402
462
  // src/commands/memory.ts
403
463
  function registerMemory(program2) {
404
464
  const memory = program2.command("memory").description("Create, read, and search memories");
465
+ memory.addHelpText(
466
+ "after",
467
+ `
468
+ Examples:
469
+ $ sechroom memory create --text "first note" --type reference --tag idea --tag cli
470
+ $ sechroom memory create --text "filed note" --owner-type Workspace --owner-id wsp_XXXX
471
+ $ sechroom memory search "rate limiting" --limit 5 --tag kind:decision
472
+ $ sechroom memory search "auth flow" --workspace wsp_XXXX --json
473
+ $ sechroom memory get mem_XXXX --json`
474
+ );
405
475
  memory.command("create").description("Create a memory (POST /memories)").requiredOption("--text <text>", "Memory body text").option("--type <type>", "Memory type", "reference").option("--title <title>", "Optional title").option("--tag <tag...>", "Tags (repeatable)").option("--owner-type <ownerType>", "Workspace | Project | Unfiled", "Unfiled").option("--owner-id <ownerId>", "Owner id (required for Workspace/Project)").option("--source <source>", "Source / lane stamp", "cli").option("--confidence <n>", "Confidence 0..1", "1.0").action(async (opts, cmd) => {
406
476
  const cfg = resolveConfig(cmd.optsWithGlobals());
407
477
  const unfiled = String(opts.ownerType).toLowerCase() === "unfiled";
@@ -421,7 +491,9 @@ function registerMemory(program2) {
421
491
  }
422
492
  });
423
493
  });
424
- emit(data, cmd.optsWithGlobals().json);
494
+ const titlePart = opts.title ? ` ${style.dim(`"${opts.title}"`)}` : "";
495
+ const urlPart = data.url ? ` ${style.dim("\u2192")} ${publicUrl(data.url)}` : "";
496
+ emitAction(`created memory ${style.bold(data.id)}${titlePart}${urlPart}`, data, cmd.optsWithGlobals().json);
425
497
  });
426
498
  memory.command("get <memoryId>").description("Fetch a memory by id (GET /memories/{memoryId})").action(async (memoryId, _opts, cmd) => {
427
499
  const cfg = resolveConfig(cmd.optsWithGlobals());
@@ -456,6 +528,13 @@ function registerMemory(program2) {
456
528
  // src/commands/worklog.ts
457
529
  function registerWorklog(program2) {
458
530
  const worklog = program2.command("worklog").description("Append to the daily work log");
531
+ worklog.addHelpText(
532
+ "after",
533
+ `
534
+ Examples:
535
+ $ sechroom worklog append --text "shipped CLI help + onboarding scope; PR #1430"
536
+ $ sechroom worklog append --text "smoke passed" --source claude-code-chris --title "CLI smoke"`
537
+ );
459
538
  worklog.command("append").description("Append a work-log entry (POST /operator-surface/work-log/append)").requiredOption("--text <text>", "Entry body (short bullets / pointers) \u2014 the bullet").option("--source <source>", "Lane stamp (e.g. claude-code-chris) \u2014 laneId", "cli").option("--workspace <workspaceId>", "Target work-log workspace (default: caller's daily log)").option("--title <title>", "Optional entry title").action(async (opts, cmd) => {
460
539
  const cfg = resolveConfig(cmd.optsWithGlobals());
461
540
  const data = await runApi("Appending work-log entry", async () => {
@@ -469,7 +548,7 @@ function registerWorklog(program2) {
469
548
  }
470
549
  });
471
550
  });
472
- emit(data, cmd.optsWithGlobals().json);
551
+ emitAction(`appended work-log entry ${style.bold(data.memoryId)}`, data, cmd.optsWithGlobals().json);
473
552
  });
474
553
  }
475
554
 
@@ -477,6 +556,14 @@ function registerWorklog(program2) {
477
556
  function registerLookup(program2) {
478
557
  program2.command("lookup <id>").description(
479
558
  "Resolve a sechroom id to its kind, title, and view URL (mem_\u2026/wsp_\u2026/prj_\u2026, unprefixed, or sechroom:<id>)"
559
+ ).addHelpText(
560
+ "after",
561
+ `
562
+ Examples:
563
+ $ sechroom lookup mem_XXXX a memory -> kind / title / view URL
564
+ $ sechroom lookup wsp_XXXX a workspace
565
+ $ sechroom lookup sechroom:mem_XXXX namespaced / portable form also resolves
566
+ $ sechroom lookup mem_XXXX --json`
480
567
  ).action(async (id, _opts, cmd) => {
481
568
  const cfg = resolveConfig(cmd.optsWithGlobals());
482
569
  const data = await runApi(`Resolving ${id}`, async () => {
@@ -799,7 +886,16 @@ ${client.label} (${client.key}):
799
886
  }
800
887
  }
801
888
  function registerInit(program2) {
802
- program2.command("init").description("Wire this project for sechroom: write MCP config + agent instruction files from the server's setup descriptors").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all'`, DEFAULT_CLIENT_KEY).option("--dry-run", "print what would be written without writing", false).option("--mcp-only", "only write MCP config (skip agent files)", false).option("--agent-files-only", "only write agent instruction files (skip MCP config)", false).option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").action(async (opts, cmd) => {
889
+ program2.command("init").description("Wire this project for sechroom: write MCP config + agent instruction files from the server's setup descriptors").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all'`, DEFAULT_CLIENT_KEY).option("--dry-run", "print what would be written without writing", false).option("--mcp-only", "only write MCP config (skip agent files)", false).option("--agent-files-only", "only write agent instruction files (skip MCP config)", false).option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").addHelpText(
890
+ "after",
891
+ `
892
+ Examples:
893
+ $ sechroom init Claude Code (default): ./.mcp.json + ./CLAUDE.md
894
+ $ sechroom init --client all claude-code, claude-desktop, codex, cursor
895
+ $ sechroom init --client codex,cursor
896
+ $ sechroom init --mcp-only just the MCP config (skip agent files)
897
+ $ sechroom init --dry-run --json preview the writes, change nothing`
898
+ ).action(async (opts, cmd) => {
803
899
  const cfg = resolveConfig(cmd.optsWithGlobals());
804
900
  const setup = await withSpinner("Fetching setup descriptors", () => fetchSetup(cfg));
805
901
  const targets = clientTargets(process.cwd());
@@ -881,11 +977,12 @@ function systemTimezone() {
881
977
  return "UTC";
882
978
  }
883
979
  }
884
- async function ensureConfig(g, yes) {
980
+ async function ensureConfig(g, opts) {
885
981
  const persisted = readPersisted();
886
- let baseUrl = g.baseUrl ?? process.env.SECHROOM_BASE_URL ?? persisted.baseUrl ?? DEFAULT_BASE_URL2;
887
- let tenant = g.tenant ?? process.env.SECHROOM_TENANT ?? persisted.tenant ?? "";
888
- if (canPrompt() && !yes) {
982
+ const local = readLocalConfig();
983
+ let baseUrl = g.baseUrl ?? process.env.SECHROOM_BASE_URL ?? local.baseUrl ?? persisted.baseUrl ?? DEFAULT_BASE_URL2;
984
+ let tenant = g.tenant ?? process.env.SECHROOM_TENANT ?? local.tenant ?? persisted.tenant ?? "";
985
+ if (canPrompt() && !opts.yes) {
889
986
  baseUrl = await promptText("Sechroom API base URL?", baseUrl);
890
987
  tenant = await promptText("Tenant id?", tenant || void 0);
891
988
  }
@@ -895,7 +992,26 @@ async function ensureConfig(g, yes) {
895
992
  "No tenant set. Pass --tenant <id>, set SECHROOM_TENANT, or run `sechroom config set tenant <id>` \u2014 the API rejects untenanted requests (HTTP 400)."
896
993
  );
897
994
  }
898
- writePersisted({ baseUrl, tenant });
995
+ let storeLocal = Boolean(opts.local);
996
+ if (!opts.local && canPrompt() && !opts.yes) {
997
+ storeLocal = await promptSelect(
998
+ "Where should this tenant + base URL be saved?",
999
+ [
1000
+ { label: "Globally", value: "global", hint: "all projects on this machine" },
1001
+ { label: "This directory", value: "local", hint: ".sechroom.json \u2014 project + subdirs" }
1002
+ ],
1003
+ local.path ? "local" : "global"
1004
+ ) === "local";
1005
+ }
1006
+ if (storeLocal) {
1007
+ const path = writeLocalConfig({ baseUrl, tenant });
1008
+ if (!opts.json) process.stderr.write(`${ok("\u2713")} config saved to ${path} (directory-local)
1009
+ `);
1010
+ } else {
1011
+ writePersisted({ baseUrl, tenant });
1012
+ if (!opts.json) process.stderr.write(`${ok("\u2713")} config saved globally (~/.config/sechroom/config.json)
1013
+ `);
1014
+ }
899
1015
  return { baseUrl, tenant, clientId: persisted.clientId };
900
1016
  }
901
1017
  async function ensureAuth(cfg, yes) {
@@ -949,21 +1065,45 @@ Available clients: ${ALL_CLIENT_KEYS.join(", ")}
949
1065
  return resolveClientKeys(answer);
950
1066
  }
951
1067
  function registerOnboard(program2) {
952
- program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).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("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients)", false).action(async (opts, cmd) => {
1068
+ program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).option("--local", "save tenant + base URL to a directory-local .sechroom.json instead of the global config", false).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("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients, global config, full wire)", false).addHelpText(
1069
+ "after",
1070
+ `
1071
+ Examples:
1072
+ $ sechroom onboard guided, interactive (asks where to save config + how to wire)
1073
+ $ sechroom onboard --cli-only just the CLI \u2014 no .mcp.json, no agent files
1074
+ $ sechroom onboard --no-mcp agent instructions only, skip MCP config
1075
+ $ sechroom onboard --local save tenant + base URL to ./.sechroom.json
1076
+ $ sechroom onboard --yes non-interactive: defaults + global config + full wire
1077
+ $ sechroom onboard --client all --dry-run preview wiring every client, write nothing`
1078
+ ).action(async (opts, cmd) => {
953
1079
  const g = cmd.optsWithGlobals();
954
1080
  const json = Boolean(g.json);
955
1081
  const yes = Boolean(opts.yes);
956
1082
  const dryRun = Boolean(opts.dryRun);
957
- const cfg = await ensureConfig(g, yes);
1083
+ const cfg = await ensureConfig(g, { yes, json, local: Boolean(opts.local) });
958
1084
  await ensureAuth(cfg, yes);
959
1085
  const tz = await ensureTimezone(cfg, { yes, dryRun });
960
1086
  if (!json && tz.action !== "already-set") {
961
- const line = tz.action === "set" ? `\u2713 timezone set to ${tz.timezone}
1087
+ const line = tz.action === "set" ? `${ok("\u2713")} timezone set to ${tz.timezone}
962
1088
  ` : tz.action === "dry-run" ? `(dry run \u2014 would set timezone to ${tz.timezone})
963
1089
  ` : `timezone not set \u2014 ${tz.note}
964
1090
  `;
965
1091
  process.stderr.write(line);
966
1092
  }
1093
+ const wire = await chooseWire(opts, yes);
1094
+ if (wire === "cli-only") {
1095
+ if (json) {
1096
+ emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, timezone: tz, wire, clients: [] }, true);
1097
+ return;
1098
+ }
1099
+ process.stdout.write(
1100
+ `
1101
+ ${style.bold("Done.")} The CLI is configured for ${style.cyan(cfg.tenant)} \u2014 no AI-client files written.
1102
+ Try: ${style.cyan('sechroom memory search "..."')} or ${style.cyan("sechroom --help")}
1103
+ `
1104
+ );
1105
+ return;
1106
+ }
967
1107
  const keys = await chooseClients(opts.client, yes, process.cwd());
968
1108
  const setup = await withSpinner("Fetching setup descriptors", () => fetchSetup(cfg));
969
1109
  const targets = clientTargets(process.cwd());
@@ -971,12 +1111,13 @@ function registerOnboard(program2) {
971
1111
  if (!dryRun) {
972
1112
  await maybeOfferCopies(cfg, setup, targets, keys, personalWorkspaceId, copyChoice(opts));
973
1113
  }
1114
+ const writeMcp = wire === "full";
974
1115
  const result = [];
975
1116
  for (const key of keys) {
976
1117
  const target = targets[key];
977
1118
  const actions = await applyClient(cfg, setup, target, {
978
1119
  dryRun,
979
- mcp: true,
1120
+ mcp: writeMcp,
980
1121
  agentFiles: true,
981
1122
  personalWorkspaceId
982
1123
  });
@@ -984,14 +1125,33 @@ function registerOnboard(program2) {
984
1125
  if (!json) printActions(target, actions);
985
1126
  }
986
1127
  if (json) {
987
- emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, timezone: tz, clients: result }, true);
1128
+ emit({ dryRun, baseUrl: cfg.baseUrl, tenant: cfg.tenant, timezone: tz, wire, clients: result }, true);
988
1129
  return;
989
1130
  }
990
1131
  process.stdout.write(
991
- dryRun ? "\n(dry run \u2014 nothing written)\n" : "\nDone. Restart your AI client (or reload MCP) to pick up the new config.\n"
1132
+ dryRun ? "\n(dry run \u2014 nothing written)\n" : writeMcp ? `
1133
+ ${style.bold("Done.")} Restart your AI client (or reload MCP) to pick up the new config.
1134
+ ` : `
1135
+ ${style.bold("Done.")} Agent instructions written (no MCP config).
1136
+ `
992
1137
  );
993
1138
  });
994
1139
  }
1140
+ async function chooseWire(opts, yes) {
1141
+ if (opts.cliOnly) return "cli-only";
1142
+ if (canPrompt() && !yes) {
1143
+ return promptSelect(
1144
+ "How should I set up Sechroom in this project?",
1145
+ [
1146
+ { label: "Wire my AI client", value: "full", hint: "MCP server (.mcp.json) + agent instructions" },
1147
+ { label: "Agent instructions only", value: "agent-only", hint: "skip MCP config" },
1148
+ { label: "CLI only", value: "cli-only", hint: "don't write any AI-client files" }
1149
+ ],
1150
+ "full"
1151
+ );
1152
+ }
1153
+ return opts.mcp === false ? "agent-only" : "full";
1154
+ }
995
1155
 
996
1156
  // src/index.ts
997
1157
  function resolveVersion() {
@@ -1006,16 +1166,55 @@ function resolveVersion() {
1006
1166
  }
1007
1167
  var program = new Command();
1008
1168
  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);
1169
+ program.addHelpText(
1170
+ "after",
1171
+ `
1172
+ Examples:
1173
+ $ sechroom onboard guided first-run: configure, sign in, wire this project
1174
+ $ sechroom login sign in via browser (OAuth + PKCE)
1175
+ $ sechroom config set tenant ocd set your tenant (global)
1176
+ $ sechroom config set --local tenant cli-smoke pin tenant for this directory (.sechroom.json)
1177
+ $ sechroom config show resolved config + which source won
1178
+
1179
+ $ sechroom memory create --text "a note" --title "Note" --tag idea
1180
+ $ sechroom memory search "convention drift" --limit 5
1181
+ $ sechroom memory get mem_XXXX
1182
+ $ sechroom worklog append --text "shipped X; PR #123" --source claude-code-chris
1183
+ $ sechroom lookup mem_XXXX resolve any id -> kind / title / view URL
1184
+
1185
+ $ sechroom --json memory search "auth" compact JSON for scripts and agents
1186
+ $ SECHROOM_TOKEN=<bearer> sechroom --json memory get mem_XXXX headless
1187
+
1188
+ Config precedence (high -> low): --flag > env (SECHROOM_*) > ./.sechroom.json > global > default.
1189
+ Run 'sechroom <command> --help' for command-specific examples.`
1190
+ );
1009
1191
  program.hook("preAction", (_thisCmd, actionCmd) => {
1010
1192
  setQuiet(Boolean(actionCmd.optsWithGlobals().json));
1011
1193
  });
1012
- program.command("login").description("Sign in via browser (OAuth auth-code + PKCE, dynamic client registration)").action(async (_opts, cmd) => {
1194
+ program.command("login").description("Sign in via browser (OAuth auth-code + PKCE, dynamic client registration)").addHelpText(
1195
+ "after",
1196
+ `
1197
+ Examples:
1198
+ $ sechroom login sign in to the configured base URL + tenant
1199
+ $ sechroom login --base-url https://staging.app.sechroom.ai/api
1200
+ $ export SECHROOM_TOKEN=<bearer> headless: skip login entirely (CI / agents)`
1201
+ ).action(async (_opts, cmd) => {
1013
1202
  const g = cmd.optsWithGlobals();
1014
1203
  const persisted = readPersisted();
1015
1204
  const baseUrl = g.baseUrl ?? process.env.SECHROOM_BASE_URL ?? persisted.baseUrl ?? "https://app.sechroom.ai/api";
1016
1205
  await login({ baseUrl: baseUrl.replace(/\/$/, ""), tenant: g.tenant ?? "" });
1017
1206
  });
1018
1207
  var config = program.command("config").description("Manage persisted CLI config");
1208
+ config.addHelpText(
1209
+ "after",
1210
+ `
1211
+ Examples:
1212
+ $ sechroom config set baseUrl https://app.sechroom.ai/api prod (staging: https://staging.app.sechroom.ai/api)
1213
+ $ sechroom config set tenant ocd
1214
+ $ sechroom config set --local tenant cli-smoke this dir + subdirs (.sechroom.json)
1215
+ $ sechroom config set clientId dyn-XXXX global-only escape hatch (no DCR endpoint)
1216
+ $ sechroom config show --json`
1217
+ );
1019
1218
  config.command("set <key> <value>").description("Set baseUrl | tenant | clientId (clientId is global-only)").option("--local", "Write to the directory-local .sechroom.json (nearest up the tree, else cwd) instead of the global config").action((key, value, opts) => {
1020
1219
  if (opts.local) {
1021
1220
  if (!["baseUrl", "tenant"].includes(key)) {
@@ -1065,8 +1264,8 @@ registerLookup(program);
1065
1264
  registerInit(program);
1066
1265
  registerSetup(program);
1067
1266
  registerOnboard(program);
1068
- program.parseAsync().catch((err) => {
1069
- process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
1267
+ program.parseAsync().catch((err2) => {
1268
+ process.stderr.write(`error: ${err2 instanceof Error ? err2.message : String(err2)}
1070
1269
  `);
1071
1270
  process.exit(1);
1072
1271
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.6",
3
+ "version": "2026.6.7",
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",