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