@sechroom/cli 2026.6.221-rc.1f0a622c → 2026.6.223-rc.d98a230b
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 +74 -37
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -844,6 +844,50 @@ Examples:
|
|
|
844
844
|
cmd.optsWithGlobals().json
|
|
845
845
|
);
|
|
846
846
|
});
|
|
847
|
+
memory.command("update <memoryId>").description("Update metadata only \u2014 title/tags/type/confidence (PATCH /memories/{memoryId}/metadata; omitted = unchanged)").option("--title <text>", "Set the title").option("--tag <tag...>", "Set the full tag list (replaces existing); repeatable").option("--add-tag <tag...>", "Add tag(s) to the existing set (repeatable)").option("--remove-tag <tag...>", "Remove tag(s) from the existing set (repeatable)").option("--type <type>", "Set the memory type (e.g. reference, note, document)").option("--confidence <n>", "Set confidence (0..1)", (v) => Number(v)).option("--memory-source <src>", "Set the memory's own Source field").option("--bump-version", "Bump the version chain (use for a content reinterpretation, e.g. a type promotion)", false).option("--source <source>", "Contributing lane stamp (attribution)", "cli").action(async (memoryId, opts, cmd) => {
|
|
848
|
+
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
849
|
+
const json = cmd.optsWithGlobals().json;
|
|
850
|
+
const hasTagOps = Boolean(opts.tag || opts.addTag || opts.removeTag);
|
|
851
|
+
const hasAny = opts.title !== void 0 || hasTagOps || opts.type !== void 0 || opts.confidence !== void 0 || opts.memorySource !== void 0 || Boolean(opts.bumpVersion);
|
|
852
|
+
if (!hasAny)
|
|
853
|
+
fail("nothing to update \u2014 pass at least one of --title / --tag / --add-tag / --remove-tag / --type / --confidence / --memory-source.");
|
|
854
|
+
let tags;
|
|
855
|
+
if (hasTagOps) {
|
|
856
|
+
let base;
|
|
857
|
+
if (opts.tag) base = opts.tag;
|
|
858
|
+
else {
|
|
859
|
+
const current = await runApi("Reading current tags", async () => {
|
|
860
|
+
const client = await makeClient(cfg);
|
|
861
|
+
return client.GET("/memories/{memoryId}", { params: { path: { memoryId } } });
|
|
862
|
+
});
|
|
863
|
+
base = current?.item?.tags ?? current?.tags ?? [];
|
|
864
|
+
}
|
|
865
|
+
const set = new Set(base);
|
|
866
|
+
for (const t of opts.addTag ?? []) set.add(t);
|
|
867
|
+
for (const t of opts.removeTag ?? []) set.delete(t);
|
|
868
|
+
tags = [...set];
|
|
869
|
+
}
|
|
870
|
+
const body = {
|
|
871
|
+
memoryId,
|
|
872
|
+
source: opts.source,
|
|
873
|
+
bumpVersion: Boolean(opts.bumpVersion)
|
|
874
|
+
};
|
|
875
|
+
if (opts.title !== void 0) body.title = opts.title;
|
|
876
|
+
if (tags !== void 0) body.tags = tags;
|
|
877
|
+
if (opts.type !== void 0) body.type = opts.type;
|
|
878
|
+
if (opts.confidence !== void 0) body.confidence = opts.confidence;
|
|
879
|
+
if (opts.memorySource !== void 0) body.memorySource = opts.memorySource;
|
|
880
|
+
const data = await runApi("Updating metadata", async () => {
|
|
881
|
+
const client = await makeClient(cfg);
|
|
882
|
+
return client.PATCH("/memories/{memoryId}/metadata", {
|
|
883
|
+
params: { path: { memoryId } },
|
|
884
|
+
body
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
const changed = data?.changed ?? [];
|
|
888
|
+
const summary = changed.length > 0 ? `updated ${changed.join(", ")}` : "no changes";
|
|
889
|
+
emitAction(`${summary} on ${style.bold(memoryId)} \u2192 v${style.bold(String(data?.version ?? "?"))}`, data, json);
|
|
890
|
+
});
|
|
847
891
|
memory.command("archive <memoryId>").description("Archive a memory (POST /memories/{memoryId}/archive)").option("--source <source>", "Source / lane stamp", "cli").action(async (memoryId, opts, cmd) => {
|
|
848
892
|
const cfg = resolveConfig(cmd.optsWithGlobals());
|
|
849
893
|
const data = await runApi("Archiving memory", async () => {
|
|
@@ -1783,10 +1827,9 @@ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as rea
|
|
|
1783
1827
|
import { delimiter, dirname as dirname4, join as join5 } from "path";
|
|
1784
1828
|
|
|
1785
1829
|
// src/sem.ts
|
|
1786
|
-
import {
|
|
1830
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1787
1831
|
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1788
1832
|
var SEM_FILE = join2(".sechroom", "lane.json");
|
|
1789
|
-
var LEGACY_SEM_FILE = ".sem";
|
|
1790
1833
|
var STATE_DIR_NAME2 = ".sechroom";
|
|
1791
1834
|
function localSemPath(cwd = process.cwd()) {
|
|
1792
1835
|
return join2(cwd, SEM_FILE);
|
|
@@ -1796,8 +1839,6 @@ function resolveSemPathForRead(start = process.cwd()) {
|
|
|
1796
1839
|
while (true) {
|
|
1797
1840
|
const candidate = join2(dir, SEM_FILE);
|
|
1798
1841
|
if (existsSync2(candidate)) return candidate;
|
|
1799
|
-
const legacy = join2(dir, LEGACY_SEM_FILE);
|
|
1800
|
-
if (existsSync2(legacy)) return legacy;
|
|
1801
1842
|
const parent = dirname2(dir);
|
|
1802
1843
|
if (parent === dir) return void 0;
|
|
1803
1844
|
dir = parent;
|
|
@@ -1840,34 +1881,17 @@ function laneWithWorktreeSuffix(lane, gitFile, siblings) {
|
|
|
1840
1881
|
const idx = [...siblings].sort().indexOf(m[1]);
|
|
1841
1882
|
return idx < 0 ? lane : `${lane}-${idx + 2}`;
|
|
1842
1883
|
}
|
|
1843
|
-
function parseSem(text) {
|
|
1844
|
-
const out = {};
|
|
1845
|
-
for (const raw of text.split("\n")) {
|
|
1846
|
-
const line = raw.trim();
|
|
1847
|
-
if (!line || line.startsWith("#")) continue;
|
|
1848
|
-
const eq = line.indexOf("=");
|
|
1849
|
-
if (eq === -1) continue;
|
|
1850
|
-
const key = line.slice(0, eq).trim();
|
|
1851
|
-
const value = line.slice(eq + 1).trim();
|
|
1852
|
-
if (key) out[key] = value;
|
|
1853
|
-
}
|
|
1854
|
-
return out;
|
|
1855
|
-
}
|
|
1856
1884
|
function serializeSem(values) {
|
|
1857
1885
|
return JSON.stringify(values, null, 2) + "\n";
|
|
1858
1886
|
}
|
|
1859
1887
|
function readSem(path) {
|
|
1860
1888
|
const p = path ?? resolveSemPathForRead();
|
|
1861
1889
|
if (!p || !existsSync2(p)) return void 0;
|
|
1862
|
-
|
|
1863
|
-
const values = basename2(p) === LEGACY_SEM_FILE ? parseSem(text) : parseLaneJson(text);
|
|
1864
|
-
return { path: p, values };
|
|
1890
|
+
return { path: p, values: parseLaneJson(readFileSync2(p, "utf8")) };
|
|
1865
1891
|
}
|
|
1866
1892
|
function readLocalSemValues(cwd = process.cwd()) {
|
|
1867
1893
|
const next = join2(cwd, SEM_FILE);
|
|
1868
1894
|
if (existsSync2(next)) return readSem(next)?.values ?? {};
|
|
1869
|
-
const legacy = join2(cwd, LEGACY_SEM_FILE);
|
|
1870
|
-
if (existsSync2(legacy)) return readSem(legacy)?.values ?? {};
|
|
1871
1895
|
return {};
|
|
1872
1896
|
}
|
|
1873
1897
|
function parseLaneJson(text) {
|
|
@@ -2157,10 +2181,22 @@ function clientTargets(cwd, opts = {}) {
|
|
|
2157
2181
|
label: "Cursor",
|
|
2158
2182
|
mcp: { surfaceKey: "claude-code", sectionType: SectionType.McpConfig, path: join4(cwd, ".cursor", "mcp.json"), format: "json" },
|
|
2159
2183
|
instruction: { surfaceKey: "chatgpt", path: join4(cwd, "AGENTS.md") }
|
|
2184
|
+
},
|
|
2185
|
+
antigravity: {
|
|
2186
|
+
key: "antigravity",
|
|
2187
|
+
label: "Google Antigravity",
|
|
2188
|
+
// FR-sechroom-247 — Antigravity reads MCP from a GLOBAL, home-relative
|
|
2189
|
+
// `~/.gemini/config/mcp_config.json` (not cwd; not affected by
|
|
2190
|
+
// CLAUDE_CONFIG_DIR / CODEX_HOME). The snippet — `serverUrl`-shaped, no
|
|
2191
|
+
// `type` — comes from the `antigravity` server surface, so we don't
|
|
2192
|
+
// hardcode it here. Instructions go in the project `AGENTS.md`
|
|
2193
|
+
// (cross-tool, shared with Codex/Cursor).
|
|
2194
|
+
mcp: { surfaceKey: "antigravity", sectionType: SectionType.McpConfig, path: join4(home, ".gemini", "config", "mcp_config.json"), format: "json" },
|
|
2195
|
+
instruction: { surfaceKey: "antigravity", path: join4(cwd, "AGENTS.md") }
|
|
2160
2196
|
}
|
|
2161
2197
|
};
|
|
2162
2198
|
}
|
|
2163
|
-
var ALL_CLIENT_KEYS = ["claude-code", "claude-desktop", "codex", "cursor"];
|
|
2199
|
+
var ALL_CLIENT_KEYS = ["claude-code", "claude-desktop", "codex", "cursor", "antigravity"];
|
|
2164
2200
|
var DEFAULT_CLIENT_KEY = "claude-code";
|
|
2165
2201
|
function detectInstalledClients(cwd) {
|
|
2166
2202
|
const home = homedir3();
|
|
@@ -2169,6 +2205,7 @@ function detectInstalledClients(cwd) {
|
|
|
2169
2205
|
if (existsSync3(dirname3(claudeDesktopConfigPath(home)))) detected.push("claude-desktop");
|
|
2170
2206
|
if (resolveCodexHomes({}).some((d) => existsSync3(d))) detected.push("codex");
|
|
2171
2207
|
if (existsSync3(join4(home, ".cursor")) || existsSync3(join4(cwd, ".cursor"))) detected.push("cursor");
|
|
2208
|
+
if (existsSync3(join4(home, ".gemini"))) detected.push("antigravity");
|
|
2172
2209
|
return detected;
|
|
2173
2210
|
}
|
|
2174
2211
|
|
|
@@ -2497,10 +2534,10 @@ Examples:
|
|
|
2497
2534
|
$ sechroom hook install --surface codex Codex only
|
|
2498
2535
|
$ sechroom hook install --local --dry-run preview the project .claude/settings.json
|
|
2499
2536
|
|
|
2500
|
-
Lane source (high -> low): --lane > SECHROOM_LANE > ./.
|
|
2537
|
+
Lane source (high -> low): --lane > SECHROOM_LANE > ./.sechroom/lane.json code-lane (D-binding-5).
|
|
2501
2538
|
Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0, never blocks.`
|
|
2502
2539
|
);
|
|
2503
|
-
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 ./.
|
|
2540
|
+
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) => {
|
|
2504
2541
|
try {
|
|
2505
2542
|
const raw = await readStdin();
|
|
2506
2543
|
const input = parseHookInput(raw);
|
|
@@ -2524,7 +2561,7 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
|
|
|
2524
2561
|
return process.exit(0);
|
|
2525
2562
|
}
|
|
2526
2563
|
});
|
|
2527
|
-
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 ./.
|
|
2564
|
+
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) => {
|
|
2528
2565
|
try {
|
|
2529
2566
|
const raw = await readStdin();
|
|
2530
2567
|
const input = parseHookInput(raw);
|
|
@@ -2535,7 +2572,7 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
|
|
|
2535
2572
|
return process.exit(0);
|
|
2536
2573
|
}
|
|
2537
2574
|
});
|
|
2538
|
-
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 ./.
|
|
2575
|
+
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(
|
|
2539
2576
|
"--debounce-minutes <n>",
|
|
2540
2577
|
"skip if a hook checkpoint ran within this many minutes \u2014 for high-frequency triggers like Codex Stop (Claude SessionEnd passes none)"
|
|
2541
2578
|
).action(async (opts, cmd) => {
|
|
@@ -2612,12 +2649,12 @@ Fail-soft: no lane / no auth / no-or-partial intent file / API error -> exit 0,
|
|
|
2612
2649
|
function registerCheckpoint(program2) {
|
|
2613
2650
|
program2.command("checkpoint").description(
|
|
2614
2651
|
"Checkpoint working state: create a continuity snapshot (server-validated) AND sync ./.sechroom/continuity.json in one step"
|
|
2615
|
-
).option("--lane <laneId>", "Lane id (else SECHROOM_LANE, else ./.
|
|
2652
|
+
).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(
|
|
2616
2653
|
"after",
|
|
2617
2654
|
`
|
|
2618
2655
|
File-first: reads ./.sechroom/continuity.json (kept current as you work) as the base; any flag
|
|
2619
2656
|
overrides that field. The snapshot is created FIRST (server-validated), then the local file is
|
|
2620
|
-
written/normalized with the returned snapshotId. Lane: --lane > SECHROOM_LANE > ./.
|
|
2657
|
+
written/normalized with the returned snapshotId. Lane: --lane > SECHROOM_LANE > ./.sechroom/lane.json code-lane.
|
|
2621
2658
|
|
|
2622
2659
|
Examples:
|
|
2623
2660
|
$ sechroom checkpoint snapshot from ./.sechroom/continuity.json, then sync it
|
|
@@ -2645,7 +2682,7 @@ Examples:
|
|
|
2645
2682
|
const lane = resolveLane(opts.lane, cwd);
|
|
2646
2683
|
if (!lane) {
|
|
2647
2684
|
fail(
|
|
2648
|
-
"no lane resolved \u2014 pass --lane, set SECHROOM_LANE, or pin one in ./.
|
|
2685
|
+
"no lane resolved \u2014 pass --lane, set SECHROOM_LANE, or pin one in ./.sechroom/lane.json (code-lane). See `sechroom lane`."
|
|
2649
2686
|
);
|
|
2650
2687
|
}
|
|
2651
2688
|
const required = [
|
|
@@ -3550,7 +3587,7 @@ function registerInit(program2) {
|
|
|
3550
3587
|
`
|
|
3551
3588
|
Examples:
|
|
3552
3589
|
$ sechroom init Claude Code (default): ./.mcp.json + ./CLAUDE.md
|
|
3553
|
-
$ sechroom init --client all claude-code, claude-desktop, codex, cursor
|
|
3590
|
+
$ sechroom init --client all claude-code, claude-desktop, codex, cursor, antigravity
|
|
3554
3591
|
$ sechroom init --client codex cursor space-separated (comma also works)
|
|
3555
3592
|
$ sechroom init --mcp-only just the MCP config (skip agent files)
|
|
3556
3593
|
$ sechroom init --dry-run --json preview the writes, change nothing`
|
|
@@ -3803,7 +3840,7 @@ Wired to namespace '${slug}'. Restart your AI client (or reload MCP) to pick it
|
|
|
3803
3840
|
|
|
3804
3841
|
// src/commands/onboard.ts
|
|
3805
3842
|
import { existsSync as existsSync8 } from "fs";
|
|
3806
|
-
import { basename as
|
|
3843
|
+
import { basename as basename2, join as join10 } from "path";
|
|
3807
3844
|
|
|
3808
3845
|
// src/commands/fanout.ts
|
|
3809
3846
|
import { spawnSync } from "child_process";
|
|
@@ -4012,7 +4049,7 @@ function personalSubtreeIds(personalId, all) {
|
|
|
4012
4049
|
}
|
|
4013
4050
|
async function pickWorkspace(client, opts = {}) {
|
|
4014
4051
|
const promptLabel = opts.promptLabel ?? "Bind this directory to a workspace:";
|
|
4015
|
-
const dirName = opts.dirName ??
|
|
4052
|
+
const dirName = opts.dirName ?? basename2(process.cwd());
|
|
4016
4053
|
const all = await withSpinner("Listing your workspaces", () => fetchWorkspaces(client));
|
|
4017
4054
|
if (all.length === 0) {
|
|
4018
4055
|
process.stderr.write(`no workspaces found \u2014 skipping workspace binding (you can set it later with \`sechroom config set --local workspaceId <id>\`)
|
|
@@ -4074,7 +4111,7 @@ async function resolveWorkspaceBinding(client, existing, opts) {
|
|
|
4074
4111
|
}
|
|
4075
4112
|
if (existing) return existing;
|
|
4076
4113
|
if (!canPrompt() || opts.yes) return void 0;
|
|
4077
|
-
return pickWorkspace(client, { dirName:
|
|
4114
|
+
return pickWorkspace(client, { dirName: basename2(process.cwd()) });
|
|
4078
4115
|
}
|
|
4079
4116
|
async function ensureTenant(baseUrl, g, opts) {
|
|
4080
4117
|
const persisted = readPersisted();
|
|
@@ -4251,7 +4288,7 @@ ${style.bold(entry.path)} ${style.dim("is not bound yet.")}
|
|
|
4251
4288
|
`);
|
|
4252
4289
|
const ws = await pickWorkspace(client, {
|
|
4253
4290
|
promptLabel: `Bind ${style.cyan(entry.path)} to a workspace:`,
|
|
4254
|
-
dirName:
|
|
4291
|
+
dirName: basename2(entry.path)
|
|
4255
4292
|
});
|
|
4256
4293
|
if (!ws) {
|
|
4257
4294
|
return { label: entry.path, dir, disposition: "skip-unbound", argv: [], reason: "unbound \u2014 no workspace chosen (skipped)" };
|
|
@@ -4866,7 +4903,7 @@ Examples:
|
|
|
4866
4903
|
console.log(" " + style.bold("default-design-lane") + " = " + (data?.defaultDesignLane ?? style.dim("(unset)")));
|
|
4867
4904
|
console.log(" " + style.bold("handover-recipient") + " = " + (data?.handoverRecipient ?? style.dim("(unset)")));
|
|
4868
4905
|
});
|
|
4869
|
-
skills.command("resolve").description("Resolve the effective ${identity.*} slot values (per-location .
|
|
4906
|
+
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) => {
|
|
4870
4907
|
const local = readSem()?.values ?? {};
|
|
4871
4908
|
let operator = {};
|
|
4872
4909
|
try {
|
|
@@ -4903,7 +4940,7 @@ function registerAgents(program2) {
|
|
|
4903
4940
|
Examples:
|
|
4904
4941
|
$ sechroom agents install materialise your installed agents to ~/.claude/agents
|
|
4905
4942
|
$ sechroom agents install --scope project write them to ./.claude/agents instead
|
|
4906
|
-
$ sechroom agents install --claude-config-dir ~/.claude-
|
|
4943
|
+
$ sechroom agents install --claude-config-dir ~/.claude-work target another instance
|
|
4907
4944
|
$ sechroom agents list what's materialised on disk
|
|
4908
4945
|
$ sechroom agents clean remove the materialised agent files
|
|
4909
4946
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sechroom/cli",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.223-rc.d98a230b",
|
|
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",
|