@sechroom/cli 2026.6.36 → 2026.6.37
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 +97 -43
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1275,7 +1275,8 @@ Examples:
|
|
|
1275
1275
|
$ sechroom workspace get wsp_XXXX --json
|
|
1276
1276
|
$ sechroom workspace rename wsp_XXXX --name "Renamed"
|
|
1277
1277
|
$ sechroom workspace move wsp_XXXX --parent wsp_YYYY
|
|
1278
|
-
$ sechroom workspace feed wsp_XXXX --limit 20 --cascade
|
|
1278
|
+
$ sechroom workspace feed wsp_XXXX --limit 20 --cascade
|
|
1279
|
+
$ sechroom workspace feed wsp_XXXX --tag kind:plan --since 2026-06-01T00:00:00Z --order UpdatedDesc`
|
|
1279
1280
|
);
|
|
1280
1281
|
workspace.command("create").description("Create a workspace (POST /workspaces)").requiredOption("--name <name>", "Workspace name").option("--description <description>", "Optional description").option("--parent <parentId>", "Parent workspace id (omit for a top-level workspace)").action(async (opts, cmd) => {
|
|
1281
1282
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
@@ -1383,7 +1384,7 @@ Examples:
|
|
|
1383
1384
|
});
|
|
1384
1385
|
emitAction(`restored workspace ${style.bold(workspaceId)}`, data, cmd.optsWithGlobals().json);
|
|
1385
1386
|
});
|
|
1386
|
-
workspace.command("feed <workspaceId>").description("List a workspace's memory feed (GET /workspaces/{workspaceId}/memories/feed)").option("--limit <n>", "Max results", "20").option("--cursor <cursor>", "Paging cursor from a prior page").option("--cascade", "Cascade into descendant workspaces", false).option("--include-projects", "Include the workspace's projects", false).option("--include-archived", "Include archived memories", false).option("--query <query>", "Filter the feed by text").option("--tag <tag>", "Filter tags (comma-separated)").action(async (workspaceId, opts, cmd) => {
|
|
1387
|
+
workspace.command("feed <workspaceId>").description("List a workspace's memory feed (GET /workspaces/{workspaceId}/memories/feed)").option("--limit <n>", "Max results", "20").option("--cursor <cursor>", "Paging cursor from a prior page").option("--cascade", "Cascade into descendant workspaces", false).option("--include-projects", "Include the workspace's projects", false).option("--include-archived", "Include archived memories", false).option("--query <query>", "Filter the feed by text").option("--tag <tag>", "Filter tags (comma-separated)").option("--since <iso>", "Only memories updated since this ISO-8601 timestamp (updatedSince)").option("--order <order>", "Order: UpdatedDesc | UpdatedAsc | CreatedDesc | CreatedAsc").action(async (workspaceId, opts, cmd) => {
|
|
1387
1388
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
1388
1389
|
const data = await runApi("Fetching feed", async () => {
|
|
1389
1390
|
const client = await makeClient(cfg);
|
|
@@ -1397,7 +1398,12 @@ Examples:
|
|
|
1397
1398
|
includeArchived: Boolean(opts.includeArchived),
|
|
1398
1399
|
...opts.cursor ? { cursor: opts.cursor } : {},
|
|
1399
1400
|
...opts.query ? { query: opts.query } : {},
|
|
1400
|
-
...opts.tag ? { filterTags: opts.tag } : {}
|
|
1401
|
+
...opts.tag ? { filterTags: opts.tag } : {},
|
|
1402
|
+
...opts.since ? { updatedSince: opts.since } : {},
|
|
1403
|
+
// orderBy is WorkspaceFeedOrder (Updated*/Created*) post-FR-feed-005
|
|
1404
|
+
// (the FeedOrder schema-name collision is fixed). opts.order is a raw
|
|
1405
|
+
// commander string; cast to a valid member to satisfy the union.
|
|
1406
|
+
...opts.order ? { orderBy: opts.order } : {}
|
|
1401
1407
|
}
|
|
1402
1408
|
}
|
|
1403
1409
|
});
|
|
@@ -1827,10 +1833,9 @@ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as rea
|
|
|
1827
1833
|
import { delimiter, dirname as dirname4, join as join5 } from "path";
|
|
1828
1834
|
|
|
1829
1835
|
// src/sem.ts
|
|
1830
|
-
import {
|
|
1836
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1831
1837
|
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1832
1838
|
var SEM_FILE = join2(".sechroom", "lane.json");
|
|
1833
|
-
var LEGACY_SEM_FILE = ".sem";
|
|
1834
1839
|
var STATE_DIR_NAME2 = ".sechroom";
|
|
1835
1840
|
function localSemPath(cwd = process.cwd()) {
|
|
1836
1841
|
return join2(cwd, SEM_FILE);
|
|
@@ -1840,8 +1845,6 @@ function resolveSemPathForRead(start = process.cwd()) {
|
|
|
1840
1845
|
while (true) {
|
|
1841
1846
|
const candidate = join2(dir, SEM_FILE);
|
|
1842
1847
|
if (existsSync2(candidate)) return candidate;
|
|
1843
|
-
const legacy = join2(dir, LEGACY_SEM_FILE);
|
|
1844
|
-
if (existsSync2(legacy)) return legacy;
|
|
1845
1848
|
const parent = dirname2(dir);
|
|
1846
1849
|
if (parent === dir) return void 0;
|
|
1847
1850
|
dir = parent;
|
|
@@ -1884,34 +1887,17 @@ function laneWithWorktreeSuffix(lane, gitFile, siblings) {
|
|
|
1884
1887
|
const idx = [...siblings].sort().indexOf(m[1]);
|
|
1885
1888
|
return idx < 0 ? lane : `${lane}-${idx + 2}`;
|
|
1886
1889
|
}
|
|
1887
|
-
function parseSem(text) {
|
|
1888
|
-
const out = {};
|
|
1889
|
-
for (const raw of text.split("\n")) {
|
|
1890
|
-
const line = raw.trim();
|
|
1891
|
-
if (!line || line.startsWith("#")) continue;
|
|
1892
|
-
const eq = line.indexOf("=");
|
|
1893
|
-
if (eq === -1) continue;
|
|
1894
|
-
const key = line.slice(0, eq).trim();
|
|
1895
|
-
const value = line.slice(eq + 1).trim();
|
|
1896
|
-
if (key) out[key] = value;
|
|
1897
|
-
}
|
|
1898
|
-
return out;
|
|
1899
|
-
}
|
|
1900
1890
|
function serializeSem(values) {
|
|
1901
1891
|
return JSON.stringify(values, null, 2) + "\n";
|
|
1902
1892
|
}
|
|
1903
1893
|
function readSem(path) {
|
|
1904
1894
|
const p = path ?? resolveSemPathForRead();
|
|
1905
1895
|
if (!p || !existsSync2(p)) return void 0;
|
|
1906
|
-
|
|
1907
|
-
const values = basename2(p) === LEGACY_SEM_FILE ? parseSem(text) : parseLaneJson(text);
|
|
1908
|
-
return { path: p, values };
|
|
1896
|
+
return { path: p, values: parseLaneJson(readFileSync2(p, "utf8")) };
|
|
1909
1897
|
}
|
|
1910
1898
|
function readLocalSemValues(cwd = process.cwd()) {
|
|
1911
1899
|
const next = join2(cwd, SEM_FILE);
|
|
1912
1900
|
if (existsSync2(next)) return readSem(next)?.values ?? {};
|
|
1913
|
-
const legacy = join2(cwd, LEGACY_SEM_FILE);
|
|
1914
|
-
if (existsSync2(legacy)) return readSem(legacy)?.values ?? {};
|
|
1915
1901
|
return {};
|
|
1916
1902
|
}
|
|
1917
1903
|
function parseLaneJson(text) {
|
|
@@ -1931,8 +1917,34 @@ function writeSem(values, path = localSemPath()) {
|
|
|
1931
1917
|
mkdirSync2(dirname2(path), { recursive: true });
|
|
1932
1918
|
writeFileSync2(path, serializeSem(values));
|
|
1933
1919
|
ensureSemIgnored(path);
|
|
1920
|
+
ensureContinuityScaffold(path);
|
|
1934
1921
|
return path;
|
|
1935
1922
|
}
|
|
1923
|
+
var CONTINUITY_FILE_NAME = "continuity.json";
|
|
1924
|
+
var CONTINUITY_SCAFFOLD = JSON.stringify(
|
|
1925
|
+
{
|
|
1926
|
+
_readme: "Agent-maintained continuity intent. Keep these current during the session; `sechroom checkpoint` and the PreCompact hook snapshot from here. The five required fields (objective, state, lastAction, nextAction, resumeInstruction) must all be non-empty for a snapshot to be created.",
|
|
1927
|
+
objective: "",
|
|
1928
|
+
state: "",
|
|
1929
|
+
lastAction: "",
|
|
1930
|
+
nextAction: "",
|
|
1931
|
+
resumeInstruction: "",
|
|
1932
|
+
constraints: [],
|
|
1933
|
+
questions: [],
|
|
1934
|
+
artifacts: [],
|
|
1935
|
+
confidence: null
|
|
1936
|
+
},
|
|
1937
|
+
null,
|
|
1938
|
+
2
|
|
1939
|
+
) + "\n";
|
|
1940
|
+
function ensureContinuityScaffold(semPath) {
|
|
1941
|
+
try {
|
|
1942
|
+
const target = join2(dirname2(semPath), CONTINUITY_FILE_NAME);
|
|
1943
|
+
if (existsSync2(target)) return;
|
|
1944
|
+
writeFileSync2(target, CONTINUITY_SCAFFOLD);
|
|
1945
|
+
} catch {
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1936
1948
|
function ignoresSem(content) {
|
|
1937
1949
|
return content.split("\n").some((line) => {
|
|
1938
1950
|
const t = line.trim();
|
|
@@ -2201,10 +2213,22 @@ function clientTargets(cwd, opts = {}) {
|
|
|
2201
2213
|
label: "Cursor",
|
|
2202
2214
|
mcp: { surfaceKey: "claude-code", sectionType: SectionType.McpConfig, path: join4(cwd, ".cursor", "mcp.json"), format: "json" },
|
|
2203
2215
|
instruction: { surfaceKey: "chatgpt", path: join4(cwd, "AGENTS.md") }
|
|
2216
|
+
},
|
|
2217
|
+
antigravity: {
|
|
2218
|
+
key: "antigravity",
|
|
2219
|
+
label: "Google Antigravity",
|
|
2220
|
+
// FR-sechroom-247 — Antigravity reads MCP from a GLOBAL, home-relative
|
|
2221
|
+
// `~/.gemini/config/mcp_config.json` (not cwd; not affected by
|
|
2222
|
+
// CLAUDE_CONFIG_DIR / CODEX_HOME). The snippet — `serverUrl`-shaped, no
|
|
2223
|
+
// `type` — comes from the `antigravity` server surface, so we don't
|
|
2224
|
+
// hardcode it here. Instructions go in the project `AGENTS.md`
|
|
2225
|
+
// (cross-tool, shared with Codex/Cursor).
|
|
2226
|
+
mcp: { surfaceKey: "antigravity", sectionType: SectionType.McpConfig, path: join4(home, ".gemini", "config", "mcp_config.json"), format: "json" },
|
|
2227
|
+
instruction: { surfaceKey: "antigravity", path: join4(cwd, "AGENTS.md") }
|
|
2204
2228
|
}
|
|
2205
2229
|
};
|
|
2206
2230
|
}
|
|
2207
|
-
var ALL_CLIENT_KEYS = ["claude-code", "claude-desktop", "codex", "cursor"];
|
|
2231
|
+
var ALL_CLIENT_KEYS = ["claude-code", "claude-desktop", "codex", "cursor", "antigravity"];
|
|
2208
2232
|
var DEFAULT_CLIENT_KEY = "claude-code";
|
|
2209
2233
|
function detectInstalledClients(cwd) {
|
|
2210
2234
|
const home = homedir3();
|
|
@@ -2213,6 +2237,7 @@ function detectInstalledClients(cwd) {
|
|
|
2213
2237
|
if (existsSync3(dirname3(claudeDesktopConfigPath(home)))) detected.push("claude-desktop");
|
|
2214
2238
|
if (resolveCodexHomes({}).some((d) => existsSync3(d))) detected.push("codex");
|
|
2215
2239
|
if (existsSync3(join4(home, ".cursor")) || existsSync3(join4(cwd, ".cursor"))) detected.push("cursor");
|
|
2240
|
+
if (existsSync3(join4(home, ".gemini"))) detected.push("antigravity");
|
|
2216
2241
|
return detected;
|
|
2217
2242
|
}
|
|
2218
2243
|
|
|
@@ -2541,15 +2566,17 @@ Examples:
|
|
|
2541
2566
|
$ sechroom hook install --surface codex Codex only
|
|
2542
2567
|
$ sechroom hook install --local --dry-run preview the project .claude/settings.json
|
|
2543
2568
|
|
|
2544
|
-
Lane source (high -> low): --lane > SECHROOM_LANE > ./.
|
|
2569
|
+
Lane source (high -> low): --lane > SECHROOM_LANE > ./.sechroom/lane.json code-lane (D-binding-5).
|
|
2545
2570
|
Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0, never blocks.`
|
|
2546
2571
|
);
|
|
2547
|
-
hook.command("session-start").description("Resume the checkout's lane and emit continuity context for a SessionStart hook").option("--lane <laneId>", "Override the resolved lane (else SECHROOM_LANE, else ./.
|
|
2572
|
+
hook.command("session-start").description("Resume the checkout's lane and emit continuity context for a SessionStart hook").option("--lane <laneId>", "Override the resolved lane (else SECHROOM_LANE, else ./.sechroom/lane.json code-lane)").option("--surface <surface>", "Target surface: claude | codex (output is identical for session-start)", "claude").option("--max-artifacts <n>", "Cap artifacts in the resume bundle").action(async (opts, cmd) => {
|
|
2548
2573
|
try {
|
|
2549
2574
|
const raw = await readStdin();
|
|
2550
2575
|
const input = parseHookInput(raw);
|
|
2551
2576
|
const lane = resolveLane(opts.lane, input.cwd);
|
|
2552
2577
|
if (!lane) return process.exit(0);
|
|
2578
|
+
const semPath = resolveSemPathForRead(input.cwd ?? process.cwd());
|
|
2579
|
+
if (semPath) ensureContinuityScaffold(semPath);
|
|
2553
2580
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
2554
2581
|
const client = await makeClient(cfg);
|
|
2555
2582
|
const { data } = await client.POST("/continuity/resume/lane", {
|
|
@@ -2568,7 +2595,7 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
|
|
|
2568
2595
|
return process.exit(0);
|
|
2569
2596
|
}
|
|
2570
2597
|
});
|
|
2571
|
-
hook.command("pre-compact").description("Save a continuity snapshot from the agent-maintained intent file on a PreCompact hook").option("--lane <laneId>", "Override the resolved lane (else SECHROOM_LANE, else ./.
|
|
2598
|
+
hook.command("pre-compact").description("Save a continuity snapshot from the agent-maintained intent file on a PreCompact hook").option("--lane <laneId>", "Override the resolved lane (else SECHROOM_LANE, else ./.sechroom/lane.json code-lane)").option("--scope <scope>", "Snapshot scope (else the intent file's `scope`, else 'compaction')").option("--surface <surface>", "Target surface: claude | codex (lifecycle-only on both)", "claude").action(async (opts, cmd) => {
|
|
2572
2599
|
try {
|
|
2573
2600
|
const raw = await readStdin();
|
|
2574
2601
|
const input = parseHookInput(raw);
|
|
@@ -2579,7 +2606,7 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
|
|
|
2579
2606
|
return process.exit(0);
|
|
2580
2607
|
}
|
|
2581
2608
|
});
|
|
2582
|
-
hook.command("session-end").description("Save a continuity snapshot from the intent file on a SessionEnd (Claude) / Stop (Codex) hook").option("--lane <laneId>", "Override the resolved lane (else SECHROOM_LANE, else ./.
|
|
2609
|
+
hook.command("session-end").description("Save a continuity snapshot from the intent file on a SessionEnd (Claude) / Stop (Codex) hook").option("--lane <laneId>", "Override the resolved lane (else SECHROOM_LANE, else ./.sechroom/lane.json code-lane)").option("--scope <scope>", "Snapshot scope (else the intent file's `scope`, else 'session-end')").option("--surface <surface>", "Target surface: claude | codex (lifecycle-only on both)", "claude").option(
|
|
2583
2610
|
"--debounce-minutes <n>",
|
|
2584
2611
|
"skip if a hook checkpoint ran within this many minutes \u2014 for high-frequency triggers like Codex Stop (Claude SessionEnd passes none)"
|
|
2585
2612
|
).action(async (opts, cmd) => {
|
|
@@ -2656,12 +2683,12 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
|
|
|
2656
2683
|
function registerCheckpoint(program2) {
|
|
2657
2684
|
program2.command("checkpoint").description(
|
|
2658
2685
|
"Checkpoint working state: create a continuity snapshot (server-validated) AND sync ./.sechroom/continuity.json in one step"
|
|
2659
|
-
).option("--lane <laneId>", "Lane id (else SECHROOM_LANE, else ./.
|
|
2686
|
+
).option("--lane <laneId>", "Lane id (else SECHROOM_LANE, else ./.sechroom/lane.json code-lane)").option("--scope <scope>", "Snapshot scope (else the file's scope, else 'session')").option("--objective <text>", "Current objective").option("--state <text>", "Current state").option("--last-action <text>", "Last meaningful action").option("--next-action <text>", "Next intended action").option("--resume-instruction <text>", "Resume instruction").option("--constraint <text...>", "Active constraints (repeatable)").option("--question <text...>", "Open questions (repeatable)").option("--surface-marker <text...>", "Surface markers (repeatable)").option("--artifact <id...>", "Relevant artifact ids (repeatable)").option("--confidence <n>", "Confidence 0..1").option("--dry-run", "validate + print the snapshot payload without creating it or writing the file", false).addHelpText(
|
|
2660
2687
|
"after",
|
|
2661
2688
|
`
|
|
2662
2689
|
File-first: reads ./.sechroom/continuity.json (kept current as you work) as the base; any flag
|
|
2663
2690
|
overrides that field. The snapshot is created FIRST (server-validated), then the local file is
|
|
2664
|
-
written/normalized with the returned snapshotId. Lane: --lane > SECHROOM_LANE > ./.
|
|
2691
|
+
written/normalized with the returned snapshotId. Lane: --lane > SECHROOM_LANE > ./.sechroom/lane.json code-lane.
|
|
2665
2692
|
|
|
2666
2693
|
Examples:
|
|
2667
2694
|
$ sechroom checkpoint snapshot from ./.sechroom/continuity.json, then sync it
|
|
@@ -2689,7 +2716,7 @@ Examples:
|
|
|
2689
2716
|
const lane = resolveLane(opts.lane, cwd);
|
|
2690
2717
|
if (!lane) {
|
|
2691
2718
|
fail(
|
|
2692
|
-
"no lane resolved \u2014 pass --lane, set SECHROOM_LANE, or pin one in ./.
|
|
2719
|
+
"no lane resolved \u2014 pass --lane, set SECHROOM_LANE, or pin one in ./.sechroom/lane.json (code-lane). See `sechroom lane`."
|
|
2693
2720
|
);
|
|
2694
2721
|
}
|
|
2695
2722
|
const required = [
|
|
@@ -2815,7 +2842,7 @@ Examples:
|
|
|
2815
2842
|
});
|
|
2816
2843
|
emitAction("updated profile", data, cmd.optsWithGlobals().json);
|
|
2817
2844
|
});
|
|
2818
|
-
account.command("feed").description("Your recent memory feed (GET /me/memories/feed)").option("--limit <n>", "Max results", "20").option("--cursor <cursor>", "Opaque paging cursor").option("--query <query>", "Free-text filter").option("--filter-tags <tags>", "Comma-separated tag filter").option("--include-archived", "Include archived memories", false).option("--include-text", "Include memory body text", false).action(async (opts, cmd) => {
|
|
2845
|
+
account.command("feed").description("Your recent memory feed (GET /me/memories/feed)").option("--limit <n>", "Max results", "20").option("--cursor <cursor>", "Opaque paging cursor").option("--query <query>", "Free-text filter").option("--filter-tags <tags>", "Comma-separated tag filter").option("--include-archived", "Include archived memories", false).option("--include-text", "Include memory body text", false).option("--since <iso>", "Only contributions updated since this ISO-8601 timestamp (updatedSince)").option("--order <order>", "Order: LastTouchedDesc | LastTouchedAsc | FirstTouchedDesc | FirstTouchedAsc").action(async (opts, cmd) => {
|
|
2819
2846
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
2820
2847
|
const data = await runApi("Fetching feed", async () => {
|
|
2821
2848
|
const client = await makeClient(cfg);
|
|
@@ -2827,7 +2854,9 @@ Examples:
|
|
|
2827
2854
|
includeText: Boolean(opts.includeText),
|
|
2828
2855
|
...opts.cursor ? { cursor: opts.cursor } : {},
|
|
2829
2856
|
...opts.query ? { query: opts.query } : {},
|
|
2830
|
-
...opts.filterTags ? { filterTags: opts.filterTags } : {}
|
|
2857
|
+
...opts.filterTags ? { filterTags: opts.filterTags } : {},
|
|
2858
|
+
...opts.since ? { updatedSince: opts.since } : {},
|
|
2859
|
+
...opts.order ? { orderBy: opts.order } : {}
|
|
2831
2860
|
}
|
|
2832
2861
|
}
|
|
2833
2862
|
});
|
|
@@ -3314,6 +3343,8 @@ var SKILL_ROLE_TAG = "sechroom:role:skill-template";
|
|
|
3314
3343
|
var SKILL_NAME_PREFIX = "skill:";
|
|
3315
3344
|
var AGENT_ROLE_TAG = "sechroom:role:agent-template";
|
|
3316
3345
|
var AGENT_NAME_PREFIX = "agent:";
|
|
3346
|
+
var REFERENCE_ROLE_TAG = "sechroom:role:skill-reference";
|
|
3347
|
+
var REFERENCE_NAME_PREFIX = "component:";
|
|
3317
3348
|
function tagsOf(row) {
|
|
3318
3349
|
const m = row?.item ?? row;
|
|
3319
3350
|
return m?.tags ?? m?.Tags ?? [];
|
|
@@ -3350,6 +3381,9 @@ function resolveSkills(systemRows, personalRows, surface) {
|
|
|
3350
3381
|
function resolveAgents(systemRows, personalRows, surface) {
|
|
3351
3382
|
return resolveByRole(systemRows, personalRows, surface, AGENT_ROLE_TAG, AGENT_NAME_PREFIX);
|
|
3352
3383
|
}
|
|
3384
|
+
function resolveReferences(systemRows, personalRows, surface) {
|
|
3385
|
+
return resolveByRole(systemRows, personalRows, surface, REFERENCE_ROLE_TAG, REFERENCE_NAME_PREFIX);
|
|
3386
|
+
}
|
|
3353
3387
|
|
|
3354
3388
|
// src/setup/skill-resolution-io.ts
|
|
3355
3389
|
var AGENT_TARGET = { "claude-code": "claude-agent" };
|
|
@@ -3385,6 +3419,9 @@ function resolveSkillSet(rows, surface) {
|
|
|
3385
3419
|
function resolveAgentSet(rows, surface) {
|
|
3386
3420
|
return resolveAgents(rows.systemRows, rows.personalRows, agentTargetFor(surface));
|
|
3387
3421
|
}
|
|
3422
|
+
function resolveReferenceSet(rows, surface) {
|
|
3423
|
+
return resolveReferences(rows.systemRows, rows.personalRows, surface);
|
|
3424
|
+
}
|
|
3388
3425
|
|
|
3389
3426
|
// src/setup/skills-lock.ts
|
|
3390
3427
|
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
@@ -3594,7 +3631,7 @@ function registerInit(program2) {
|
|
|
3594
3631
|
`
|
|
3595
3632
|
Examples:
|
|
3596
3633
|
$ sechroom init Claude Code (default): ./.mcp.json + ./CLAUDE.md
|
|
3597
|
-
$ sechroom init --client all claude-code, claude-desktop, codex, cursor
|
|
3634
|
+
$ sechroom init --client all claude-code, claude-desktop, codex, cursor, antigravity
|
|
3598
3635
|
$ sechroom init --client codex cursor space-separated (comma also works)
|
|
3599
3636
|
$ sechroom init --mcp-only just the MCP config (skip agent files)
|
|
3600
3637
|
$ sechroom init --dry-run --json preview the writes, change nothing`
|
|
@@ -3847,7 +3884,7 @@ Wired to namespace '${slug}'. Restart your AI client (or reload MCP) to pick it
|
|
|
3847
3884
|
|
|
3848
3885
|
// src/commands/onboard.ts
|
|
3849
3886
|
import { existsSync as existsSync8 } from "fs";
|
|
3850
|
-
import { basename as
|
|
3887
|
+
import { basename as basename2, join as join10 } from "path";
|
|
3851
3888
|
|
|
3852
3889
|
// src/commands/fanout.ts
|
|
3853
3890
|
import { spawnSync } from "child_process";
|
|
@@ -4056,7 +4093,7 @@ function personalSubtreeIds(personalId, all) {
|
|
|
4056
4093
|
}
|
|
4057
4094
|
async function pickWorkspace(client, opts = {}) {
|
|
4058
4095
|
const promptLabel = opts.promptLabel ?? "Bind this directory to a workspace:";
|
|
4059
|
-
const dirName = opts.dirName ??
|
|
4096
|
+
const dirName = opts.dirName ?? basename2(process.cwd());
|
|
4060
4097
|
const all = await withSpinner("Listing your workspaces", () => fetchWorkspaces(client));
|
|
4061
4098
|
if (all.length === 0) {
|
|
4062
4099
|
process.stderr.write(`no workspaces found \u2014 skipping workspace binding (you can set it later with \`sechroom config set --local workspaceId <id>\`)
|
|
@@ -4118,7 +4155,7 @@ async function resolveWorkspaceBinding(client, existing, opts) {
|
|
|
4118
4155
|
}
|
|
4119
4156
|
if (existing) return existing;
|
|
4120
4157
|
if (!canPrompt() || opts.yes) return void 0;
|
|
4121
|
-
return pickWorkspace(client, { dirName:
|
|
4158
|
+
return pickWorkspace(client, { dirName: basename2(process.cwd()) });
|
|
4122
4159
|
}
|
|
4123
4160
|
async function ensureTenant(baseUrl, g, opts) {
|
|
4124
4161
|
const persisted = readPersisted();
|
|
@@ -4295,7 +4332,7 @@ ${style.bold(entry.path)} ${style.dim("is not bound yet.")}
|
|
|
4295
4332
|
`);
|
|
4296
4333
|
const ws = await pickWorkspace(client, {
|
|
4297
4334
|
promptLabel: `Bind ${style.cyan(entry.path)} to a workspace:`,
|
|
4298
|
-
dirName:
|
|
4335
|
+
dirName: basename2(entry.path)
|
|
4299
4336
|
});
|
|
4300
4337
|
if (!ws) {
|
|
4301
4338
|
return { label: entry.path, dir, disposition: "skip-unbound", argv: [], reason: "unbound \u2014 no workspace chosen (skipped)" };
|
|
@@ -4740,6 +4777,17 @@ function writeAgents(dir, agents, surface) {
|
|
|
4740
4777
|
if (written.length) recordMaterialisedSkills(dir, DEFAULT_SKILLS_SLUG, written, { surface });
|
|
4741
4778
|
return written;
|
|
4742
4779
|
}
|
|
4780
|
+
function writeReferencesIntoSkillDirs(dir, skills, refs) {
|
|
4781
|
+
if (!refs.length || !skills.length) return [];
|
|
4782
|
+
for (const s of skills) {
|
|
4783
|
+
const refDir = join12(dir, s.name, "references");
|
|
4784
|
+
mkdirSync8(refDir, { recursive: true });
|
|
4785
|
+
for (const r of refs) {
|
|
4786
|
+
writeFileSync8(join12(refDir, `${r.name}.md`), r.body.endsWith("\n") ? r.body : r.body + "\n");
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
return refs.map((r) => r.name);
|
|
4790
|
+
}
|
|
4743
4791
|
var SKILL_SPEC = { kind: "skill", dir: skillsDir, resolve: resolveSkillSet, write: writeSkills };
|
|
4744
4792
|
var AGENT_SPEC = { kind: "agent", dir: agentsDir, resolve: resolveAgentSet, write: writeAgents };
|
|
4745
4793
|
function scopeOf(opts) {
|
|
@@ -4760,12 +4808,14 @@ async function runInstall(spec, cmd, opts) {
|
|
|
4760
4808
|
const personalWorkspaceId = await getPersonalWorkspaceId(cfg);
|
|
4761
4809
|
const rows = await fetchTemplateRows(cfg, personalWorkspaceId);
|
|
4762
4810
|
const items = spec.resolve(rows, CLIENT_SURFACE);
|
|
4811
|
+
const refs = spec.kind === "skill" ? resolveReferenceSet(rows, CLIENT_SURFACE) : [];
|
|
4763
4812
|
const results = targets.map((t) => {
|
|
4764
4813
|
const dir = spec.dir(t.dir);
|
|
4765
4814
|
const written = dryRun ? items.map((i) => i.name) : spec.write(dir, items, CLIENT_SURFACE);
|
|
4766
|
-
|
|
4815
|
+
const refsWritten = dryRun ? refs.map((r) => r.name) : writeReferencesIntoSkillDirs(dir, items, refs);
|
|
4816
|
+
return { dir, label: t.label, written, refsWritten };
|
|
4767
4817
|
});
|
|
4768
|
-
if (json) return emit({ kind: spec.kind, dryRun, available: items.length, targets: results }, true);
|
|
4818
|
+
if (json) return emit({ kind: spec.kind, dryRun, available: items.length, references: refs.length, targets: results }, true);
|
|
4769
4819
|
if (items.length === 0) {
|
|
4770
4820
|
console.log(style.dim(`No ${spec.kind}s available to install \u2014 is the bundle installed for your account?`));
|
|
4771
4821
|
return;
|
|
@@ -4774,6 +4824,10 @@ async function runInstall(spec, cmd, opts) {
|
|
|
4774
4824
|
console.log(
|
|
4775
4825
|
`${dryRun ? "" : style.green("\u2713 ")}${dryRun ? "would write" : "wrote"} ${r.written.length} ${spec.kind}(s) ${style.dim("\u2192")} ${r.dir}`
|
|
4776
4826
|
);
|
|
4827
|
+
if (r.refsWritten.length)
|
|
4828
|
+
console.log(
|
|
4829
|
+
`${dryRun ? "" : style.green("\u2713 ")}${dryRun ? "would write" : "wrote"} ${r.refsWritten.length} reference(s) into each skill ${style.dim("\u2192")} ${r.dir}/<skill>/references`
|
|
4830
|
+
);
|
|
4777
4831
|
if (dryRun) for (const i of items) console.log(` ${i.name} ${style.dim(`[${i.source}]`)}`);
|
|
4778
4832
|
}
|
|
4779
4833
|
}
|
|
@@ -4910,7 +4964,7 @@ Examples:
|
|
|
4910
4964
|
console.log(" " + style.bold("default-design-lane") + " = " + (data?.defaultDesignLane ?? style.dim("(unset)")));
|
|
4911
4965
|
console.log(" " + style.bold("handover-recipient") + " = " + (data?.handoverRecipient ?? style.dim("(unset)")));
|
|
4912
4966
|
});
|
|
4913
|
-
skills.command("resolve").description("Resolve the effective ${identity.*} slot values (per-location .
|
|
4967
|
+
skills.command("resolve").description("Resolve the effective ${identity.*} slot values (per-location .sechroom/lane.json + per-operator workflow prefs)").option("--json", "machine output (a flat slot->value map + per-slot source)").action(async (opts, cmd) => {
|
|
4914
4968
|
const local = readSem()?.values ?? {};
|
|
4915
4969
|
let operator = {};
|
|
4916
4970
|
try {
|
package/package.json
CHANGED