@sechroom/cli 2026.6.239-rc.6418da97 → 2026.7.1
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 +72 -113
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -433,6 +433,7 @@ async function requireToken(cfg) {
|
|
|
433
433
|
import createClient from "openapi-fetch";
|
|
434
434
|
|
|
435
435
|
// src/ui.ts
|
|
436
|
+
import * as clack from "@clack/prompts";
|
|
436
437
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
437
438
|
var quiet = false;
|
|
438
439
|
function setQuiet(q) {
|
|
@@ -458,7 +459,7 @@ var err = (s) => style.red(s);
|
|
|
458
459
|
function active() {
|
|
459
460
|
return !quiet && Boolean(process.stderr.isTTY);
|
|
460
461
|
}
|
|
461
|
-
function spinner(
|
|
462
|
+
function spinner(text2) {
|
|
462
463
|
if (!active()) {
|
|
463
464
|
return { succeed() {
|
|
464
465
|
}, fail() {
|
|
@@ -468,9 +469,9 @@ function spinner(text) {
|
|
|
468
469
|
let i = 0;
|
|
469
470
|
const render = () => {
|
|
470
471
|
i = (i + 1) % FRAMES.length;
|
|
471
|
-
process.stderr.write(`\r${FRAMES[i]} ${
|
|
472
|
+
process.stderr.write(`\r${FRAMES[i]} ${text2}`);
|
|
472
473
|
};
|
|
473
|
-
process.stderr.write(`\r${FRAMES[0]} ${
|
|
474
|
+
process.stderr.write(`\r${FRAMES[0]} ${text2}`);
|
|
474
475
|
const timer = setInterval(render, 80);
|
|
475
476
|
timer.unref?.();
|
|
476
477
|
const clear = () => {
|
|
@@ -480,12 +481,12 @@ function spinner(text) {
|
|
|
480
481
|
return {
|
|
481
482
|
succeed(t) {
|
|
482
483
|
clear();
|
|
483
|
-
process.stderr.write(`${ok("\u2713")} ${t ??
|
|
484
|
+
process.stderr.write(`${ok("\u2713")} ${t ?? text2}
|
|
484
485
|
`);
|
|
485
486
|
},
|
|
486
487
|
fail(t) {
|
|
487
488
|
clear();
|
|
488
|
-
process.stderr.write(`${err("\u2717")} ${t ??
|
|
489
|
+
process.stderr.write(`${err("\u2717")} ${t ?? text2}
|
|
489
490
|
`);
|
|
490
491
|
},
|
|
491
492
|
stop: clear
|
|
@@ -496,31 +497,24 @@ function canPrompt() {
|
|
|
496
497
|
}
|
|
497
498
|
async function promptYesNo(question) {
|
|
498
499
|
if (!canPrompt()) return false;
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return /^y(es)?$/i.test(answer.trim());
|
|
506
|
-
} finally {
|
|
507
|
-
rl.close();
|
|
508
|
-
}
|
|
500
|
+
const r = await clack.confirm({
|
|
501
|
+
message: question,
|
|
502
|
+
initialValue: false,
|
|
503
|
+
output: process.stderr
|
|
504
|
+
});
|
|
505
|
+
return clack.isCancel(r) ? false : r;
|
|
509
506
|
}
|
|
510
507
|
async function promptText(question, def) {
|
|
511
508
|
if (!canPrompt()) return def ?? "";
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
} finally {
|
|
522
|
-
rl.close();
|
|
523
|
-
}
|
|
509
|
+
const r = await clack.text({
|
|
510
|
+
message: question,
|
|
511
|
+
defaultValue: def,
|
|
512
|
+
placeholder: def,
|
|
513
|
+
output: process.stderr
|
|
514
|
+
});
|
|
515
|
+
if (clack.isCancel(r)) return def ?? "";
|
|
516
|
+
const trimmed = (r ?? "").trim();
|
|
517
|
+
return trimmed.length > 0 ? trimmed : def ?? "";
|
|
524
518
|
}
|
|
525
519
|
async function promptSelect(question, choices, def) {
|
|
526
520
|
if (choices.length === 0) throw new Error("promptSelect: no choices");
|
|
@@ -529,74 +523,50 @@ async function promptSelect(question, choices, def) {
|
|
|
529
523
|
def !== void 0 ? choices.findIndex((c) => c.value === def) : 0
|
|
530
524
|
);
|
|
531
525
|
if (!canPrompt()) return choices[defIdx].value;
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
choices.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
`);
|
|
542
|
-
});
|
|
543
|
-
const answer = await new Promise((resolve3) => {
|
|
544
|
-
rl.question(`Choose ${style.dim(`[${defIdx + 1}]`)} `, resolve3);
|
|
545
|
-
});
|
|
546
|
-
const trimmed = answer.trim();
|
|
547
|
-
if (!trimmed) return choices[defIdx].value;
|
|
548
|
-
const n = Number(trimmed);
|
|
549
|
-
if (Number.isInteger(n) && n >= 1 && n <= choices.length) return choices[n - 1].value;
|
|
550
|
-
const byLabel = choices.find(
|
|
551
|
-
(c) => c.label.toLowerCase().startsWith(trimmed.toLowerCase())
|
|
552
|
-
);
|
|
553
|
-
return byLabel ? byLabel.value : choices[defIdx].value;
|
|
554
|
-
} finally {
|
|
555
|
-
rl.close();
|
|
556
|
-
}
|
|
526
|
+
const r = await clack.select({
|
|
527
|
+
message: question,
|
|
528
|
+
// Cast: clack's Option<T> is a conditional type (primitive vs not) that TS
|
|
529
|
+
// can't verify against a generic T; the runtime shape is correct.
|
|
530
|
+
options: choices.map((c) => ({ value: c.value, label: c.label, hint: c.hint })),
|
|
531
|
+
initialValue: choices[defIdx].value,
|
|
532
|
+
output: process.stderr
|
|
533
|
+
});
|
|
534
|
+
return clack.isCancel(r) ? choices[defIdx].value : r;
|
|
557
535
|
}
|
|
558
536
|
async function promptMultiSelect(question, choices, preselected = []) {
|
|
559
537
|
if (choices.length === 0) return [];
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const uniq = [...new Set(picks)];
|
|
593
|
-
return uniq.length > 0 ? uniq : preValues();
|
|
594
|
-
} finally {
|
|
595
|
-
rl.close();
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
async function withSpinner(text, fn) {
|
|
599
|
-
const s = spinner(text);
|
|
538
|
+
const preValues = choices.filter((c) => preselected.includes(c.value)).map((c) => c.value);
|
|
539
|
+
if (!canPrompt()) return preValues;
|
|
540
|
+
const r = await clack.multiselect({
|
|
541
|
+
message: question,
|
|
542
|
+
// Cast: clack's Option<T> is a conditional type (primitive vs not) that TS
|
|
543
|
+
// can't verify against a generic T; the runtime shape is correct.
|
|
544
|
+
options: choices.map((c) => ({ value: c.value, label: c.label, hint: c.hint })),
|
|
545
|
+
initialValues: preValues,
|
|
546
|
+
required: false,
|
|
547
|
+
output: process.stderr
|
|
548
|
+
});
|
|
549
|
+
return clack.isCancel(r) ? preValues : r;
|
|
550
|
+
}
|
|
551
|
+
async function promptAutocomplete(question, choices, def) {
|
|
552
|
+
if (choices.length === 0) throw new Error("promptAutocomplete: no choices");
|
|
553
|
+
const defIdx = Math.max(
|
|
554
|
+
0,
|
|
555
|
+
def !== void 0 ? choices.findIndex((c) => c.value === def) : 0
|
|
556
|
+
);
|
|
557
|
+
if (!canPrompt()) return choices[defIdx].value;
|
|
558
|
+
const r = await clack.autocomplete({
|
|
559
|
+
message: question,
|
|
560
|
+
// Cast: clack's Option<T> is a conditional type (primitive vs not) that TS
|
|
561
|
+
// can't verify against a generic T; the runtime shape is correct.
|
|
562
|
+
options: choices.map((c) => ({ value: c.value, label: c.label, hint: c.hint })),
|
|
563
|
+
placeholder: "Type to filter\u2026",
|
|
564
|
+
output: process.stderr
|
|
565
|
+
});
|
|
566
|
+
return clack.isCancel(r) ? choices[defIdx].value : r;
|
|
567
|
+
}
|
|
568
|
+
async function withSpinner(text2, fn) {
|
|
569
|
+
const s = spinner(text2);
|
|
600
570
|
try {
|
|
601
571
|
const result = await fn();
|
|
602
572
|
s.succeed();
|
|
@@ -1521,7 +1491,7 @@ Examples:
|
|
|
1521
1491
|
$ sechroom chat replies 1718049600.123456 --surface slack
|
|
1522
1492
|
$ sechroom chat stop-tracking 1718049600.123456 --surface slack`
|
|
1523
1493
|
);
|
|
1524
|
-
chat.command("send <channelId> <text>").description("Send a message to a channel (POST /chat/channel-messages/{surface})").option("--guild <guildId>", "Discord guild snowflake \u2014 required for --surface discord").option("--memory <memoryId>", "Attach a sechroom memory id").option("--no-track", "Don't capture replies to this message").option("--parent <parentMessage>", "Thread under a parent (Slack thread_ts / Discord message id)").option("--source <source>", "Source / lane stamp (renders an attribution footer)", "cli").option("--as <as>", "Slack only: 'bot' (default) or 'user' (your linked Slack identity)", "bot").action(async (channelId,
|
|
1494
|
+
chat.command("send <channelId> <text>").description("Send a message to a channel (POST /chat/channel-messages/{surface})").option("--guild <guildId>", "Discord guild snowflake \u2014 required for --surface discord").option("--memory <memoryId>", "Attach a sechroom memory id").option("--no-track", "Don't capture replies to this message").option("--parent <parentMessage>", "Thread under a parent (Slack thread_ts / Discord message id)").option("--source <source>", "Source / lane stamp (renders an attribution footer)", "cli").option("--as <as>", "Slack only: 'bot' (default) or 'user' (your linked Slack identity)", "bot").action(async (channelId, text2, opts, cmd) => {
|
|
1525
1495
|
const { surface, ...globals } = cmd.optsWithGlobals();
|
|
1526
1496
|
const json = Boolean(cmd.optsWithGlobals().json);
|
|
1527
1497
|
const cfg = resolveConfig(globals);
|
|
@@ -1531,7 +1501,7 @@ Examples:
|
|
|
1531
1501
|
params: { path: { surface: String(surface) } },
|
|
1532
1502
|
body: {
|
|
1533
1503
|
channelId,
|
|
1534
|
-
text,
|
|
1504
|
+
text: text2,
|
|
1535
1505
|
guildId: opts.guild ?? null,
|
|
1536
1506
|
attachedMemoryId: opts.memory ?? null,
|
|
1537
1507
|
trackReplies: opts.track,
|
|
@@ -1666,9 +1636,9 @@ function readLocalSemValues(cwd = process.cwd()) {
|
|
|
1666
1636
|
if (existsSync4(next)) return readSem(next)?.values ?? {};
|
|
1667
1637
|
return {};
|
|
1668
1638
|
}
|
|
1669
|
-
function parseLaneJson(
|
|
1639
|
+
function parseLaneJson(text2) {
|
|
1670
1640
|
try {
|
|
1671
|
-
const parsed = JSON.parse(
|
|
1641
|
+
const parsed = JSON.parse(text2);
|
|
1672
1642
|
const out = {};
|
|
1673
1643
|
for (const [k, v] of Object.entries(parsed)) {
|
|
1674
1644
|
if (typeof v === "string") out[k] = v;
|
|
@@ -3667,20 +3637,20 @@ Examples:
|
|
|
3667
3637
|
fail("no workspace \u2014 pass --workspace <id> or bind one (`sechroom config set --local workspaceId <id>`).");
|
|
3668
3638
|
const kind = String(opts.kind).toLowerCase() === "standard" ? "standard" : "reference";
|
|
3669
3639
|
const body = typeof opts.body === "string" && opts.body.trim().length > 0 ? opts.body.trim() : "_TODO: write this section, then edit the memo and re-run the regen._";
|
|
3670
|
-
const
|
|
3640
|
+
const text2 = `# ${title}
|
|
3671
3641
|
|
|
3672
3642
|
${body}
|
|
3673
3643
|
`;
|
|
3674
3644
|
const tags = ["agent-setup-bundle", "scope:sechroom", `kind:${kind}`, "archetype:document"];
|
|
3675
3645
|
if (opts.dryRun) {
|
|
3676
|
-
emit({ dryRun: true, workspaceId, title, kind, tags, text }, json);
|
|
3646
|
+
emit({ dryRun: true, workspaceId, title, kind, tags, text: text2 }, json);
|
|
3677
3647
|
return;
|
|
3678
3648
|
}
|
|
3679
3649
|
const data = await runApi("Authoring convention memo", async () => {
|
|
3680
3650
|
const client = await makeClient(cfg);
|
|
3681
3651
|
return client.POST("/memories", {
|
|
3682
3652
|
body: {
|
|
3683
|
-
text,
|
|
3653
|
+
text: text2,
|
|
3684
3654
|
type: kind,
|
|
3685
3655
|
content: "{}",
|
|
3686
3656
|
confidence: 1,
|
|
@@ -4045,28 +4015,17 @@ async function pickWorkspace(client, opts = {}) {
|
|
|
4045
4015
|
if (candidates.length === 0) candidates = all;
|
|
4046
4016
|
const dirToks = new Set(nameTokens(dirName));
|
|
4047
4017
|
const isMatch = (w) => nameTokens(w.name).some((t) => dirToks.has(t));
|
|
4048
|
-
const suggestions = candidates.filter(isMatch);
|
|
4049
|
-
let pool = candidates;
|
|
4050
|
-
if (candidates.length > 12 && suggestions.length === 0) {
|
|
4051
|
-
const q = (await promptText(`Filter ${candidates.length} workspaces (substring, Enter to list all)?`, "")).trim().toLowerCase();
|
|
4052
|
-
if (q) {
|
|
4053
|
-
const hits = candidates.filter((w) => `${w.name} ${workspacePath(w, byId)}`.toLowerCase().includes(q));
|
|
4054
|
-
if (hits.length > 0) pool = hits;
|
|
4055
|
-
else process.stderr.write(`no match for "${q}" \u2014 listing all
|
|
4056
|
-
`);
|
|
4057
|
-
}
|
|
4058
|
-
}
|
|
4059
4018
|
const SKIP = "__skip__";
|
|
4060
4019
|
const byPath = (a, b) => workspacePath(a, byId).localeCompare(workspacePath(b, byId));
|
|
4061
|
-
const matched =
|
|
4062
|
-
const rest =
|
|
4020
|
+
const matched = candidates.filter(isMatch).sort(byPath);
|
|
4021
|
+
const rest = candidates.filter((w) => !isMatch(w)).sort(byPath);
|
|
4063
4022
|
const choices = [
|
|
4064
4023
|
...matched.map((w) => ({ label: workspacePath(w, byId), value: w.id, hint: style.dim(`matches "${dirName}"`) })),
|
|
4065
4024
|
...rest.map((w) => ({ label: workspacePath(w, byId), value: w.id, hint: w.id })),
|
|
4066
4025
|
{ label: style.dim("skip \u2014 don't bind a workspace"), value: SKIP, hint: void 0 }
|
|
4067
4026
|
];
|
|
4068
4027
|
const defaultValue = matched.length === 1 ? matched[0].id : SKIP;
|
|
4069
|
-
const chosen = await promptSelect(promptLabel, choices, defaultValue);
|
|
4028
|
+
const chosen = candidates.length > 12 ? await promptAutocomplete(promptLabel, choices, defaultValue) : await promptSelect(promptLabel, choices, defaultValue);
|
|
4070
4029
|
if (chosen === SKIP) return void 0;
|
|
4071
4030
|
const picked = byId.get(chosen);
|
|
4072
4031
|
const collisions = all.filter((w) => w.id !== picked.id && namesCollide(w.name, picked.name));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sechroom/cli",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.7.1",
|
|
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",
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
"registry": "https://registry.npmjs.org/"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@clack/prompts": "^1.6.0",
|
|
27
28
|
"@microsoft/signalr": "^10.0.0",
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
28
30
|
"commander": "^12.1.0",
|
|
29
31
|
"open": "^10.1.0",
|
|
30
|
-
"openapi-fetch": "^0.13.0"
|
|
31
|
-
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
32
|
+
"openapi-fetch": "^0.13.0"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@types/node": "^20.14.0",
|