@sechroom/cli 2026.6.5 → 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 +46 -0
- package/dist/index.js +345 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,6 +100,30 @@ 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
|
+
|
|
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.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
sechroom config set --local tenant cli-smoke # this dir + subdirs
|
|
114
|
+
sechroom config set --local baseUrl https://staging.app.sechroom.ai/api
|
|
115
|
+
sechroom config show # resolved values + which source won
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Smoke testing.** There is a dedicated **`cli-smoke`** tenant on **both staging and prod** for exercising the CLI without touching real tenants — point at staging and use it:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
sechroom config set --local baseUrl https://staging.app.sechroom.ai/api
|
|
122
|
+
sechroom config set --local tenant cli-smoke
|
|
123
|
+
sechroom login
|
|
124
|
+
sechroom worklog append --text "cli smoke" --source claude-code-chris
|
|
125
|
+
```
|
|
126
|
+
|
|
103
127
|
Headless:
|
|
104
128
|
|
|
105
129
|
```bash
|
|
@@ -120,6 +144,7 @@ Codex TOML table replace; instruction files use a managed marker block).
|
|
|
120
144
|
sechroom init # Claude Code (default): .mcp.json + CLAUDE.md
|
|
121
145
|
sechroom init --client all # claude-code, claude-desktop, codex, cursor
|
|
122
146
|
sechroom init --client codex,cursor # a subset
|
|
147
|
+
sechroom init --mcp-only # just the MCP config (skip agent files)
|
|
123
148
|
sechroom init --dry-run --json # preview the writes, no changes
|
|
124
149
|
|
|
125
150
|
# granular pieces init orchestrates:
|
|
@@ -127,6 +152,27 @@ sechroom setup mcp claude-desktop # just the MCP config for one client
|
|
|
127
152
|
sechroom setup agent-files codex # just the AGENTS.md instruction file
|
|
128
153
|
```
|
|
129
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
|
+
|
|
130
176
|
Per client → where it writes:
|
|
131
177
|
|
|
132
178
|
| client | MCP config | instruction file |
|
package/dist/index.js
CHANGED
|
@@ -11,11 +11,12 @@ import open from "open";
|
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { homedir } from "os";
|
|
14
|
-
import { join } from "path";
|
|
14
|
+
import { join, dirname } from "path";
|
|
15
15
|
import { mkdirSync, readFileSync, writeFileSync, existsSync } 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 LOCAL_CONFIG_NAME = ".sechroom.json";
|
|
19
20
|
var DEFAULT_BASE_URL = "https://app.sechroom.ai/api";
|
|
20
21
|
function ensureDir() {
|
|
21
22
|
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
@@ -32,6 +33,13 @@ function writePersisted(patch) {
|
|
|
32
33
|
const next = { ...readPersisted(), ...patch };
|
|
33
34
|
writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), { mode: 384 });
|
|
34
35
|
}
|
|
36
|
+
function readDcrClientId(baseUrl) {
|
|
37
|
+
return readPersisted().clientIds?.[baseUrl];
|
|
38
|
+
}
|
|
39
|
+
function writeDcrClientId(baseUrl, clientId) {
|
|
40
|
+
const p = readPersisted();
|
|
41
|
+
writePersisted({ clientIds: { ...p.clientIds ?? {}, [baseUrl]: clientId } });
|
|
42
|
+
}
|
|
35
43
|
function readToken() {
|
|
36
44
|
const envTok = process.env.SECHROOM_TOKEN;
|
|
37
45
|
if (envTok) return { accessToken: envTok };
|
|
@@ -45,17 +53,67 @@ function writeToken(tok) {
|
|
|
45
53
|
ensureDir();
|
|
46
54
|
writeFileSync(TOKEN_FILE, JSON.stringify(tok, null, 2), { mode: 384 });
|
|
47
55
|
}
|
|
56
|
+
function findLocalConfigPath(start = process.cwd()) {
|
|
57
|
+
let dir = start;
|
|
58
|
+
for (; ; ) {
|
|
59
|
+
const candidate = join(dir, LOCAL_CONFIG_NAME);
|
|
60
|
+
if (existsSync(candidate)) return candidate;
|
|
61
|
+
const parent = dirname(dir);
|
|
62
|
+
if (parent === dir) return void 0;
|
|
63
|
+
dir = parent;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function readLocalConfig() {
|
|
67
|
+
const path = findLocalConfigPath();
|
|
68
|
+
if (!path) return {};
|
|
69
|
+
try {
|
|
70
|
+
const c = JSON.parse(readFileSync(path, "utf8"));
|
|
71
|
+
return { baseUrl: c.baseUrl, tenant: c.tenant, path };
|
|
72
|
+
} catch {
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function writeLocalConfig(patch) {
|
|
77
|
+
const path = findLocalConfigPath() ?? join(process.cwd(), LOCAL_CONFIG_NAME);
|
|
78
|
+
let current = {};
|
|
79
|
+
try {
|
|
80
|
+
current = JSON.parse(readFileSync(path, "utf8"));
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
writeFileSync(path, JSON.stringify({ ...current, ...patch }, null, 2), { mode: 384 });
|
|
84
|
+
return path;
|
|
85
|
+
}
|
|
48
86
|
function resolveConfig(flags) {
|
|
87
|
+
const local = readLocalConfig();
|
|
49
88
|
const persisted = readPersisted();
|
|
50
|
-
const baseUrl = flags.baseUrl ?? process.env.SECHROOM_BASE_URL ?? persisted.baseUrl ?? DEFAULT_BASE_URL;
|
|
51
|
-
const tenant = flags.tenant ?? process.env.SECHROOM_TENANT ?? persisted.tenant ?? "";
|
|
89
|
+
const baseUrl = flags.baseUrl ?? process.env.SECHROOM_BASE_URL ?? local.baseUrl ?? persisted.baseUrl ?? DEFAULT_BASE_URL;
|
|
90
|
+
const tenant = flags.tenant ?? process.env.SECHROOM_TENANT ?? local.tenant ?? persisted.tenant ?? "";
|
|
52
91
|
if (!tenant) {
|
|
53
92
|
throw new Error(
|
|
54
|
-
"No tenant set. The Sechroom API rejects untenanted requests (HTTP 400). Pass --tenant <id>, set SECHROOM_TENANT,
|
|
93
|
+
"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."
|
|
55
94
|
);
|
|
56
95
|
}
|
|
57
96
|
return { baseUrl: baseUrl.replace(/\/$/, ""), tenant, clientId: persisted.clientId };
|
|
58
97
|
}
|
|
98
|
+
function describeConfig(flags) {
|
|
99
|
+
const local = readLocalConfig();
|
|
100
|
+
const g = readPersisted();
|
|
101
|
+
const localTag = local.path ? `local (${local.path})` : "local";
|
|
102
|
+
const pick = (flag, env, l, gl, def) => {
|
|
103
|
+
if (flag) return { value: flag, source: "flag" };
|
|
104
|
+
if (env) return { value: env, source: "env" };
|
|
105
|
+
if (l) return { value: l, source: localTag };
|
|
106
|
+
if (gl) return { value: gl, source: "global" };
|
|
107
|
+
if (def) return { value: def, source: "default" };
|
|
108
|
+
return { value: void 0, source: "unset" };
|
|
109
|
+
};
|
|
110
|
+
const baseUrl = pick(flags.baseUrl, process.env.SECHROOM_BASE_URL, local.baseUrl, g.baseUrl, DEFAULT_BASE_URL);
|
|
111
|
+
return {
|
|
112
|
+
baseUrl: { value: baseUrl.value, source: baseUrl.source },
|
|
113
|
+
tenant: pick(flags.tenant, process.env.SECHROOM_TENANT, local.tenant, g.tenant),
|
|
114
|
+
localPath: local.path
|
|
115
|
+
};
|
|
116
|
+
}
|
|
59
117
|
|
|
60
118
|
// src/auth.ts
|
|
61
119
|
var SCOPES = "openid email profile";
|
|
@@ -72,26 +130,26 @@ async function discover(baseUrl) {
|
|
|
72
130
|
if (!res.ok) throw new Error(`AS discovery failed (${res.status}) at ${baseUrl}`);
|
|
73
131
|
return await res.json();
|
|
74
132
|
}
|
|
75
|
-
async function ensureClientId(meta) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
133
|
+
async function ensureClientId(meta, baseUrl) {
|
|
134
|
+
if (meta.registration_endpoint) {
|
|
135
|
+
const res = await fetch(meta.registration_endpoint, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "content-type": "application/json" },
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
client_name: "sechroom-cli",
|
|
140
|
+
redirect_uris: CANDIDATE_PORTS.map(redirectUriFor)
|
|
141
|
+
})
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok) throw new Error(`Dynamic client registration failed (${res.status}): ${await res.text()}`);
|
|
144
|
+
const reg = await res.json();
|
|
145
|
+
writeDcrClientId(baseUrl, reg.client_id);
|
|
146
|
+
return reg.client_id;
|
|
82
147
|
}
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
redirect_uris: CANDIDATE_PORTS.map(redirectUriFor)
|
|
89
|
-
})
|
|
90
|
-
});
|
|
91
|
-
if (!res.ok) throw new Error(`Dynamic client registration failed (${res.status}): ${await res.text()}`);
|
|
92
|
-
const reg = await res.json();
|
|
93
|
-
writePersisted({ clientId: reg.client_id });
|
|
94
|
-
return reg.client_id;
|
|
148
|
+
const fallback = readPersisted().clientId ?? readDcrClientId(baseUrl);
|
|
149
|
+
if (fallback) return fallback;
|
|
150
|
+
throw new Error(
|
|
151
|
+
"Server advertises no registration_endpoint and no client_id is configured. Pre-register a client and run `sechroom config set clientId <id>`."
|
|
152
|
+
);
|
|
95
153
|
}
|
|
96
154
|
function startLoopback(state) {
|
|
97
155
|
return new Promise((resolveOuter, rejectOuter) => {
|
|
@@ -115,13 +173,13 @@ function startLoopback(state) {
|
|
|
115
173
|
}
|
|
116
174
|
const got = url.searchParams.get("code");
|
|
117
175
|
const gotState = url.searchParams.get("state");
|
|
118
|
-
const
|
|
119
|
-
res.writeHead(
|
|
176
|
+
const err2 = url.searchParams.get("error");
|
|
177
|
+
res.writeHead(err2 ? 400 : 200, { "content-type": "text/html" });
|
|
120
178
|
res.end(
|
|
121
|
-
|
|
179
|
+
err2 ? `<h3>Authorization failed: ${err2}</h3>` : "<h3>Signed in to Sechroom.</h3><p>Return to the terminal.</p>"
|
|
122
180
|
);
|
|
123
181
|
server.close();
|
|
124
|
-
if (
|
|
182
|
+
if (err2) return rejectCode(new Error(`Authorization error: ${err2}`));
|
|
125
183
|
if (!got) return rejectCode(new Error("No code in callback."));
|
|
126
184
|
if (gotState !== state) return rejectCode(new Error("State mismatch \u2014 possible CSRF."));
|
|
127
185
|
resolveCode(got);
|
|
@@ -162,7 +220,7 @@ function persistTokenResponse(json) {
|
|
|
162
220
|
}
|
|
163
221
|
async function login(cfg) {
|
|
164
222
|
const meta = await discover(cfg.baseUrl);
|
|
165
|
-
const clientId = await ensureClientId(meta);
|
|
223
|
+
const clientId = await ensureClientId(meta, cfg.baseUrl);
|
|
166
224
|
const state = b64url(randomBytes(16));
|
|
167
225
|
const verifier = b64url(randomBytes(32));
|
|
168
226
|
const challenge = b64url(createHash("sha256").update(verifier).digest());
|
|
@@ -219,6 +277,22 @@ var quiet = false;
|
|
|
219
277
|
function setQuiet(q) {
|
|
220
278
|
quiet = q;
|
|
221
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);
|
|
222
296
|
function active() {
|
|
223
297
|
return !quiet && Boolean(process.stderr.isTTY);
|
|
224
298
|
}
|
|
@@ -244,12 +318,12 @@ function spinner(text) {
|
|
|
244
318
|
return {
|
|
245
319
|
succeed(t) {
|
|
246
320
|
clear();
|
|
247
|
-
process.stderr.write(
|
|
321
|
+
process.stderr.write(`${ok("\u2713")} ${t ?? text}
|
|
248
322
|
`);
|
|
249
323
|
},
|
|
250
324
|
fail(t) {
|
|
251
325
|
clear();
|
|
252
|
-
process.stderr.write(
|
|
326
|
+
process.stderr.write(`${err("\u2717")} ${t ?? text}
|
|
253
327
|
`);
|
|
254
328
|
},
|
|
255
329
|
stop: clear
|
|
@@ -286,15 +360,48 @@ async function promptText(question, def) {
|
|
|
286
360
|
rl.close();
|
|
287
361
|
}
|
|
288
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
|
+
}
|
|
289
396
|
async function withSpinner(text, fn) {
|
|
290
397
|
const s = spinner(text);
|
|
291
398
|
try {
|
|
292
399
|
const result = await fn();
|
|
293
400
|
s.succeed();
|
|
294
401
|
return result;
|
|
295
|
-
} catch (
|
|
402
|
+
} catch (err2) {
|
|
296
403
|
s.fail();
|
|
297
|
-
throw
|
|
404
|
+
throw err2;
|
|
298
405
|
}
|
|
299
406
|
}
|
|
300
407
|
|
|
@@ -318,14 +425,25 @@ function emit(data, json) {
|
|
|
318
425
|
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
319
426
|
}
|
|
320
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
|
+
}
|
|
321
439
|
async function runApi(label, fn) {
|
|
322
440
|
const s = spinner(label);
|
|
323
441
|
let res;
|
|
324
442
|
try {
|
|
325
443
|
res = await fn();
|
|
326
|
-
} catch (
|
|
444
|
+
} catch (err2) {
|
|
327
445
|
s.fail();
|
|
328
|
-
fail(
|
|
446
|
+
fail(err2);
|
|
329
447
|
}
|
|
330
448
|
if (res.error !== void 0 && res.error !== null) {
|
|
331
449
|
s.fail();
|
|
@@ -344,6 +462,16 @@ function fail(error) {
|
|
|
344
462
|
// src/commands/memory.ts
|
|
345
463
|
function registerMemory(program2) {
|
|
346
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
|
+
);
|
|
347
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) => {
|
|
348
476
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
349
477
|
const unfiled = String(opts.ownerType).toLowerCase() === "unfiled";
|
|
@@ -363,7 +491,9 @@ function registerMemory(program2) {
|
|
|
363
491
|
}
|
|
364
492
|
});
|
|
365
493
|
});
|
|
366
|
-
|
|
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);
|
|
367
497
|
});
|
|
368
498
|
memory.command("get <memoryId>").description("Fetch a memory by id (GET /memories/{memoryId})").action(async (memoryId, _opts, cmd) => {
|
|
369
499
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
@@ -398,6 +528,13 @@ function registerMemory(program2) {
|
|
|
398
528
|
// src/commands/worklog.ts
|
|
399
529
|
function registerWorklog(program2) {
|
|
400
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
|
+
);
|
|
401
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) => {
|
|
402
539
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
403
540
|
const data = await runApi("Appending work-log entry", async () => {
|
|
@@ -411,7 +548,7 @@ function registerWorklog(program2) {
|
|
|
411
548
|
}
|
|
412
549
|
});
|
|
413
550
|
});
|
|
414
|
-
|
|
551
|
+
emitAction(`appended work-log entry ${style.bold(data.memoryId)}`, data, cmd.optsWithGlobals().json);
|
|
415
552
|
});
|
|
416
553
|
}
|
|
417
554
|
|
|
@@ -419,6 +556,14 @@ function registerWorklog(program2) {
|
|
|
419
556
|
function registerLookup(program2) {
|
|
420
557
|
program2.command("lookup <id>").description(
|
|
421
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`
|
|
422
567
|
).action(async (id, _opts, cmd) => {
|
|
423
568
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
424
569
|
const data = await runApi(`Resolving ${id}`, async () => {
|
|
@@ -436,7 +581,7 @@ function registerLookup(program2) {
|
|
|
436
581
|
|
|
437
582
|
// src/setup/apply.ts
|
|
438
583
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
439
|
-
import { dirname } from "path";
|
|
584
|
+
import { dirname as dirname2 } from "path";
|
|
440
585
|
|
|
441
586
|
// src/setup/operator-surface.ts
|
|
442
587
|
var SectionType = {
|
|
@@ -538,7 +683,7 @@ async function createOverride(cfg, template, personalWorkspaceId) {
|
|
|
538
683
|
var BLOCK_BEGIN = "<!-- @sechroom/cli:begin (managed \u2014 re-run `sechroom setup agent-files` to refresh) -->";
|
|
539
684
|
var BLOCK_END = "<!-- @sechroom/cli:end -->";
|
|
540
685
|
function ensureDir2(path) {
|
|
541
|
-
mkdirSync2(
|
|
686
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
542
687
|
}
|
|
543
688
|
function readOr(path, fallback) {
|
|
544
689
|
try {
|
|
@@ -634,7 +779,7 @@ async function applyClient(cfg, setup, target, opts) {
|
|
|
634
779
|
// src/setup/clients.ts
|
|
635
780
|
import { existsSync as existsSync3 } from "fs";
|
|
636
781
|
import { homedir as homedir2 } from "os";
|
|
637
|
-
import { dirname as
|
|
782
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
638
783
|
function claudeDesktopConfigPath(home) {
|
|
639
784
|
switch (process.platform) {
|
|
640
785
|
case "darwin":
|
|
@@ -680,7 +825,7 @@ function detectInstalledClients(cwd) {
|
|
|
680
825
|
const home = homedir2();
|
|
681
826
|
const detected = [];
|
|
682
827
|
if (existsSync3(join2(home, ".claude"))) detected.push("claude-code");
|
|
683
|
-
if (existsSync3(
|
|
828
|
+
if (existsSync3(dirname3(claudeDesktopConfigPath(home)))) detected.push("claude-desktop");
|
|
684
829
|
if (existsSync3(join2(home, ".codex"))) detected.push("codex");
|
|
685
830
|
if (existsSync3(join2(home, ".cursor")) || existsSync3(join2(cwd, ".cursor"))) detected.push("cursor");
|
|
686
831
|
return detected;
|
|
@@ -741,7 +886,16 @@ ${client.label} (${client.key}):
|
|
|
741
886
|
}
|
|
742
887
|
}
|
|
743
888
|
function registerInit(program2) {
|
|
744
|
-
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) => {
|
|
745
899
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
746
900
|
const setup = await withSpinner("Fetching setup descriptors", () => fetchSetup(cfg));
|
|
747
901
|
const targets = clientTargets(process.cwd());
|
|
@@ -823,11 +977,12 @@ function systemTimezone() {
|
|
|
823
977
|
return "UTC";
|
|
824
978
|
}
|
|
825
979
|
}
|
|
826
|
-
async function ensureConfig(g,
|
|
980
|
+
async function ensureConfig(g, opts) {
|
|
827
981
|
const persisted = readPersisted();
|
|
828
|
-
|
|
829
|
-
let
|
|
830
|
-
|
|
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) {
|
|
831
986
|
baseUrl = await promptText("Sechroom API base URL?", baseUrl);
|
|
832
987
|
tenant = await promptText("Tenant id?", tenant || void 0);
|
|
833
988
|
}
|
|
@@ -837,7 +992,26 @@ async function ensureConfig(g, yes) {
|
|
|
837
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)."
|
|
838
993
|
);
|
|
839
994
|
}
|
|
840
|
-
|
|
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
|
+
}
|
|
841
1015
|
return { baseUrl, tenant, clientId: persisted.clientId };
|
|
842
1016
|
}
|
|
843
1017
|
async function ensureAuth(cfg, yes) {
|
|
@@ -891,21 +1065,45 @@ Available clients: ${ALL_CLIENT_KEYS.join(", ")}
|
|
|
891
1065
|
return resolveClientKeys(answer);
|
|
892
1066
|
}
|
|
893
1067
|
function registerOnboard(program2) {
|
|
894
|
-
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) => {
|
|
895
1079
|
const g = cmd.optsWithGlobals();
|
|
896
1080
|
const json = Boolean(g.json);
|
|
897
1081
|
const yes = Boolean(opts.yes);
|
|
898
1082
|
const dryRun = Boolean(opts.dryRun);
|
|
899
|
-
const cfg = await ensureConfig(g, yes);
|
|
1083
|
+
const cfg = await ensureConfig(g, { yes, json, local: Boolean(opts.local) });
|
|
900
1084
|
await ensureAuth(cfg, yes);
|
|
901
1085
|
const tz = await ensureTimezone(cfg, { yes, dryRun });
|
|
902
1086
|
if (!json && tz.action !== "already-set") {
|
|
903
|
-
const line = tz.action === "set" ?
|
|
1087
|
+
const line = tz.action === "set" ? `${ok("\u2713")} timezone set to ${tz.timezone}
|
|
904
1088
|
` : tz.action === "dry-run" ? `(dry run \u2014 would set timezone to ${tz.timezone})
|
|
905
1089
|
` : `timezone not set \u2014 ${tz.note}
|
|
906
1090
|
`;
|
|
907
1091
|
process.stderr.write(line);
|
|
908
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
|
+
}
|
|
909
1107
|
const keys = await chooseClients(opts.client, yes, process.cwd());
|
|
910
1108
|
const setup = await withSpinner("Fetching setup descriptors", () => fetchSetup(cfg));
|
|
911
1109
|
const targets = clientTargets(process.cwd());
|
|
@@ -913,12 +1111,13 @@ function registerOnboard(program2) {
|
|
|
913
1111
|
if (!dryRun) {
|
|
914
1112
|
await maybeOfferCopies(cfg, setup, targets, keys, personalWorkspaceId, copyChoice(opts));
|
|
915
1113
|
}
|
|
1114
|
+
const writeMcp = wire === "full";
|
|
916
1115
|
const result = [];
|
|
917
1116
|
for (const key of keys) {
|
|
918
1117
|
const target = targets[key];
|
|
919
1118
|
const actions = await applyClient(cfg, setup, target, {
|
|
920
1119
|
dryRun,
|
|
921
|
-
mcp:
|
|
1120
|
+
mcp: writeMcp,
|
|
922
1121
|
agentFiles: true,
|
|
923
1122
|
personalWorkspaceId
|
|
924
1123
|
});
|
|
@@ -926,14 +1125,33 @@ function registerOnboard(program2) {
|
|
|
926
1125
|
if (!json) printActions(target, actions);
|
|
927
1126
|
}
|
|
928
1127
|
if (json) {
|
|
929
|
-
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);
|
|
930
1129
|
return;
|
|
931
1130
|
}
|
|
932
1131
|
process.stdout.write(
|
|
933
|
-
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
|
+
`
|
|
934
1137
|
);
|
|
935
1138
|
});
|
|
936
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
|
+
}
|
|
937
1155
|
|
|
938
1156
|
// src/index.ts
|
|
939
1157
|
function resolveVersion() {
|
|
@@ -948,17 +1166,67 @@ function resolveVersion() {
|
|
|
948
1166
|
}
|
|
949
1167
|
var program = new Command();
|
|
950
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
|
+
);
|
|
951
1191
|
program.hook("preAction", (_thisCmd, actionCmd) => {
|
|
952
1192
|
setQuiet(Boolean(actionCmd.optsWithGlobals().json));
|
|
953
1193
|
});
|
|
954
|
-
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) => {
|
|
955
1202
|
const g = cmd.optsWithGlobals();
|
|
956
1203
|
const persisted = readPersisted();
|
|
957
1204
|
const baseUrl = g.baseUrl ?? process.env.SECHROOM_BASE_URL ?? persisted.baseUrl ?? "https://app.sechroom.ai/api";
|
|
958
1205
|
await login({ baseUrl: baseUrl.replace(/\/$/, ""), tenant: g.tenant ?? "" });
|
|
959
1206
|
});
|
|
960
1207
|
var config = program.command("config").description("Manage persisted CLI config");
|
|
961
|
-
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
|
+
);
|
|
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) => {
|
|
1219
|
+
if (opts.local) {
|
|
1220
|
+
if (!["baseUrl", "tenant"].includes(key)) {
|
|
1221
|
+
process.stderr.write(`--local supports only: baseUrl | tenant (clientId is global)
|
|
1222
|
+
`);
|
|
1223
|
+
process.exit(1);
|
|
1224
|
+
}
|
|
1225
|
+
const path = writeLocalConfig({ [key]: value });
|
|
1226
|
+
process.stdout.write(`set ${key} (local: ${path})
|
|
1227
|
+
`);
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
962
1230
|
if (!["baseUrl", "tenant", "clientId"].includes(key)) {
|
|
963
1231
|
process.stderr.write(`unknown key: ${key} (expected baseUrl | tenant | clientId)
|
|
964
1232
|
`);
|
|
@@ -968,8 +1236,27 @@ config.command("set <key> <value>").description("Set baseUrl | tenant | clientId
|
|
|
968
1236
|
process.stdout.write(`set ${key}
|
|
969
1237
|
`);
|
|
970
1238
|
});
|
|
971
|
-
config.command("show").description("Print
|
|
972
|
-
|
|
1239
|
+
config.command("show").description("Print resolved config + sources (flag > env > local > global > default)").action((_opts, cmd) => {
|
|
1240
|
+
const g = cmd.optsWithGlobals();
|
|
1241
|
+
const d = describeConfig({ baseUrl: g.baseUrl, tenant: g.tenant });
|
|
1242
|
+
if (g.json) {
|
|
1243
|
+
process.stdout.write(
|
|
1244
|
+
JSON.stringify({
|
|
1245
|
+
resolved: { baseUrl: d.baseUrl, tenant: d.tenant },
|
|
1246
|
+
global: readPersisted(),
|
|
1247
|
+
local: readLocalConfig()
|
|
1248
|
+
}) + "\n"
|
|
1249
|
+
);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
process.stdout.write(
|
|
1253
|
+
`baseUrl: ${d.baseUrl.value} [${d.baseUrl.source}]
|
|
1254
|
+
tenant: ${d.tenant.value ?? "(unset)"} [${d.tenant.source}]
|
|
1255
|
+
|
|
1256
|
+
global: ${JSON.stringify(readPersisted())}
|
|
1257
|
+
local: ${d.localPath ?? "(none)"} ${JSON.stringify(readLocalConfig())}
|
|
1258
|
+
`
|
|
1259
|
+
);
|
|
973
1260
|
});
|
|
974
1261
|
registerMemory(program);
|
|
975
1262
|
registerWorklog(program);
|
|
@@ -977,8 +1264,8 @@ registerLookup(program);
|
|
|
977
1264
|
registerInit(program);
|
|
978
1265
|
registerSetup(program);
|
|
979
1266
|
registerOnboard(program);
|
|
980
|
-
program.parseAsync().catch((
|
|
981
|
-
process.stderr.write(`error: ${
|
|
1267
|
+
program.parseAsync().catch((err2) => {
|
|
1268
|
+
process.stderr.write(`error: ${err2 instanceof Error ? err2.message : String(err2)}
|
|
982
1269
|
`);
|
|
983
1270
|
process.exit(1);
|
|
984
1271
|
});
|
package/package.json
CHANGED