@sechroom/cli 2026.6.30-rc.3b1b16ff → 2026.6.32-rc.925b06e7
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 +66 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21,9 +21,9 @@ var DEFAULT_ACCOUNT = "default";
|
|
|
21
21
|
var STATE_DIR_NAME = ".sechroom";
|
|
22
22
|
var BASELINE_CONFIG_NAME = ".sechroom.json";
|
|
23
23
|
var OVERRIDE_CONFIG_NAME = join(STATE_DIR_NAME, "config.json");
|
|
24
|
-
var BINDING_FIELDS = ["schemaVersion", "baseUrl", "tenant", "workspaceId", "defaultProjectId"];
|
|
24
|
+
var BINDING_FIELDS = ["schemaVersion", "baseUrl", "tenant", "workspaceId", "defaultProjectId", "workspaces"];
|
|
25
25
|
var DEFAULT_BASE_URL = "https://app.sechroom.ai/api";
|
|
26
|
-
var LOCAL_CONFIG_SCHEMA_VERSION =
|
|
26
|
+
var LOCAL_CONFIG_SCHEMA_VERSION = 3;
|
|
27
27
|
function ensureDir() {
|
|
28
28
|
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
29
29
|
}
|
|
@@ -161,12 +161,14 @@ function readLocalConfig() {
|
|
|
161
161
|
tenant: merged.tenant,
|
|
162
162
|
workspaceId: merged.workspaceId,
|
|
163
163
|
defaultProjectId: merged.defaultProjectId,
|
|
164
|
+
workspaces: merged.workspaces,
|
|
164
165
|
account: merged.account,
|
|
165
|
-
path: existsSync(baselinePath) ? baselinePath : overridePath
|
|
166
|
+
path: existsSync(baselinePath) ? baselinePath : overridePath,
|
|
167
|
+
home
|
|
166
168
|
};
|
|
167
169
|
}
|
|
168
|
-
function writeLocalConfig(patch) {
|
|
169
|
-
const home = findConfigHome() ?? process.cwd();
|
|
170
|
+
function writeLocalConfig(patch, opts) {
|
|
171
|
+
const home = (opts?.here ? process.cwd() : findConfigHome()) ?? process.cwd();
|
|
170
172
|
const baselinePath = join(home, BASELINE_CONFIG_NAME);
|
|
171
173
|
const overridePath = join(home, OVERRIDE_CONFIG_NAME);
|
|
172
174
|
const current = readJsonConfig(baselinePath) ?? {};
|
|
@@ -184,6 +186,30 @@ function committedBindingPath(dir) {
|
|
|
184
186
|
const p = join(dir, BASELINE_CONFIG_NAME);
|
|
185
187
|
return existsSync(p) ? p : void 0;
|
|
186
188
|
}
|
|
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
|
+
}
|
|
187
213
|
function resolveConfig(flags) {
|
|
188
214
|
const local = readLocalConfig();
|
|
189
215
|
const persisted = readPersisted();
|
|
@@ -194,8 +220,9 @@ function resolveConfig(flags) {
|
|
|
194
220
|
"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."
|
|
195
221
|
);
|
|
196
222
|
}
|
|
197
|
-
const
|
|
198
|
-
const
|
|
223
|
+
const binding = selectWorkspaceBinding(local, flags.binding ?? process.env.SECHROOM_BINDING);
|
|
224
|
+
const workspaceId = process.env.SECHROOM_WORKSPACE ?? binding?.workspaceId ?? local.workspaceId ?? persisted.workspaceId ?? void 0;
|
|
225
|
+
const defaultProjectId = (binding ? binding.defaultProjectId : void 0) ?? local.defaultProjectId ?? persisted.defaultProjectId ?? void 0;
|
|
199
226
|
const account = resolveAccountAlias(flags.account);
|
|
200
227
|
return { baseUrl: baseUrl.replace(/\/$/, ""), tenant, account, workspaceId, defaultProjectId, clientId: persisted.clientId };
|
|
201
228
|
}
|
|
@@ -212,10 +239,17 @@ function describeConfig(flags) {
|
|
|
212
239
|
return { value: void 0, source: "unset" };
|
|
213
240
|
};
|
|
214
241
|
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);
|
|
215
249
|
return {
|
|
216
250
|
baseUrl: { value: baseUrl.value, source: baseUrl.source },
|
|
217
251
|
tenant: pick(flags.tenant, process.env.SECHROOM_TENANT, local.tenant, g.tenant),
|
|
218
|
-
workspaceId
|
|
252
|
+
workspaceId,
|
|
219
253
|
localPath: local.path
|
|
220
254
|
};
|
|
221
255
|
}
|
|
@@ -1168,6 +1202,26 @@ Examples:
|
|
|
1168
1202
|
// src/commands/workspace.ts
|
|
1169
1203
|
function registerWorkspace(program2) {
|
|
1170
1204
|
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
|
+
});
|
|
1171
1225
|
workspace.addHelpText(
|
|
1172
1226
|
"after",
|
|
1173
1227
|
`
|
|
@@ -3382,7 +3436,7 @@ async function ensureTenant(baseUrl, g, opts) {
|
|
|
3382
3436
|
if (opts.persist !== false) {
|
|
3383
3437
|
const patch = { baseUrl, tenant, ...workspaceId ? { workspaceId } : {} };
|
|
3384
3438
|
if (storeLocal) {
|
|
3385
|
-
const path = writeLocalConfig(patch);
|
|
3439
|
+
const path = writeLocalConfig(patch, { here: Boolean(opts.here) });
|
|
3386
3440
|
if (!opts.json) process.stderr.write(`${ok("\u2713")} config saved to ${path} (directory-local)
|
|
3387
3441
|
`);
|
|
3388
3442
|
} else {
|
|
@@ -3552,7 +3606,7 @@ async function runRecurse(cfg, g, opts) {
|
|
|
3552
3606
|
summarizeFanout(results, { dryRun });
|
|
3553
3607
|
}
|
|
3554
3608
|
function registerOnboard(program2) {
|
|
3555
|
-
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(
|
|
3609
|
+
program2.command("onboard").description("Guided first-run setup: configure, sign in, set timezone, detect clients, and wire this project").option("--recurse", "orchestration-root mode: onboard every child repo under this dir (auto-discovered, or from ./.sechroom/repos.json) \u2014 refreshes bound repos, prompts a workspace per new one", false).option("--lane <id>", "set the code-lane (substrate source identity) explicitly instead of inferring it; with --recurse it's used for every child repo").option("--design-lane <id>", "set the design-lane explicitly (substrate-authoring identity); with --recurse applies to every child").option("--client <list>", `comma-separated clients (${ALL_CLIENT_KEYS.join(", ")}) or 'all' (default: auto-detected)`).option("--local", "save the binding (tenant + base URL + workspace) to a committed .sechroom.json in this repo instead of the global config", false).option("--here", "with --local: write the binding at THIS directory even when a parent already carries one \u2014 binds a subtree (e.g. a monorepo's frontend/) to its own workspace", false).option("--workspace <id>", "bind this directory to a workspace (skips the interactive workspace pick)").option("--cli-only", "configure the CLI only \u2014 don't wire any AI client (no MCP config, no agent files)", false).option("--no-mcp", "skip the MCP server config (.mcp.json etc.); still write the agent instruction files").option("--copy", "make a personal copy of the agent instructions you can edit (default: prompt on a TTY, else skip)").option("--dry-run", "walk through without writing files or changing the profile", false).option("--refresh", "re-fetch descriptors and refresh any out-of-date managed blocks (local edits preserved to .proposed)", false).option("--force", "rewrite every managed block, overwriting local edits inside the markers (content outside untouched)", false).option("--check", "report whether anything would change and exit (0 = all current, 1 = stale/drift/absent); writes nothing", false).option("-y, --yes", "non-interactive: accept defaults (system timezone, detected clients, global config, full wire)", false).addHelpText(
|
|
3556
3610
|
"after",
|
|
3557
3611
|
`
|
|
3558
3612
|
Examples:
|
|
@@ -4159,7 +4213,7 @@ function resolveVersion() {
|
|
|
4159
4213
|
}
|
|
4160
4214
|
}
|
|
4161
4215
|
var program = new Command();
|
|
4162
|
-
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);
|
|
4216
|
+
program.name("sechroom").description("Sechroom CLI \u2014 thin generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.").version(resolveVersion()).option("--base-url <url>", "API base URL (overrides config / SECHROOM_BASE_URL)").option("--tenant <tenant>", "Tenant id (required by the API; overrides config / SECHROOM_TENANT)").option("--binding <name>", "Named workspace binding from .sechroom.json `workspaces` (overrides path auto-selection / SECHROOM_BINDING)").option("--json", "Emit compact JSON (for scripts and agents)", false);
|
|
4163
4217
|
program.addHelpText(
|
|
4164
4218
|
"after",
|
|
4165
4219
|
`
|
|
@@ -4233,7 +4287,7 @@ config.command("set <key> <value>").description("Set baseUrl | tenant | workspac
|
|
|
4233
4287
|
});
|
|
4234
4288
|
config.command("show").description("Print resolved config + sources (flag > env > local > global > default)").action((_opts, cmd) => {
|
|
4235
4289
|
const g = cmd.optsWithGlobals();
|
|
4236
|
-
const d = describeConfig({ baseUrl: g.baseUrl, tenant: g.tenant });
|
|
4290
|
+
const d = describeConfig({ baseUrl: g.baseUrl, tenant: g.tenant, binding: g.binding });
|
|
4237
4291
|
if (g.json) {
|
|
4238
4292
|
process.stdout.write(
|
|
4239
4293
|
JSON.stringify({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sechroom/cli",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.32-rc.925b06e7",
|
|
4
4
|
"description": "Sechroom CLI — a thin, generated client over the Sechroom HTTP API. An agent/human surface alongside MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|