@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.
- package/README.md +29 -0
- package/dist/index.js +226 -27
- 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
|
|
177
|
-
res.writeHead(
|
|
176
|
+
const err2 = url.searchParams.get("error");
|
|
177
|
+
res.writeHead(err2 ? 400 : 200, { "content-type": "text/html" });
|
|
178
178
|
res.end(
|
|
179
|
-
|
|
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 (
|
|
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(
|
|
321
|
+
process.stderr.write(`${ok("\u2713")} ${t ?? text}
|
|
306
322
|
`);
|
|
307
323
|
},
|
|
308
324
|
fail(t) {
|
|
309
325
|
clear();
|
|
310
|
-
process.stderr.write(
|
|
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 (
|
|
402
|
+
} catch (err2) {
|
|
354
403
|
s.fail();
|
|
355
|
-
throw
|
|
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 (
|
|
444
|
+
} catch (err2) {
|
|
385
445
|
s.fail();
|
|
386
|
-
fail(
|
|
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
|
-
|
|
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
|
-
|
|
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)").
|
|
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,
|
|
980
|
+
async function ensureConfig(g, opts) {
|
|
885
981
|
const persisted = readPersisted();
|
|
886
|
-
|
|
887
|
-
let
|
|
888
|
-
|
|
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
|
-
|
|
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).
|
|
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" ?
|
|
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:
|
|
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" :
|
|
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)").
|
|
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((
|
|
1069
|
-
process.stderr.write(`error: ${
|
|
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