@sechroom/cli 2026.6.9 → 2026.6.10

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/README.md CHANGED
@@ -146,12 +146,11 @@ The CLI mirrors the sechroom MCP tool surface — every command is a thin wrappe
146
146
  | `continuity` | snapshot-create / -get · snapshots · resume-me / resume-lane · changed-since · load-set · grant / revoke-grant |
147
147
  | `id` | next / peek (FR-_/D-_ sequence allocation) |
148
148
  | `account` | profile / set-profile · feed · reviews / review-get / review-accept · lookup-batch |
149
- | `chat` | messages · replies · stop-tracking (Slack / Discord, via `--surface`) |
149
+ | `chat` | send · messages · replies · stop-tracking (Slack / Discord, via `--surface`) |
150
150
  | `worklog` · `lookup` | append · resolve any id |
151
151
 
152
152
  Notes on deliberate gaps (API-rooted, not CLI):
153
153
  - **No `memory delete`** — the API exposes no hard DELETE; `memory archive` is the soft-delete path.
154
- - **No `chat send`** — the unified `/chat/*` surface has no POST send endpoint yet, so the Slack/Discord *send* tools can't be wrapped. Reading works today; `send` lands once the route is added to the spec.
155
154
  - **`memory revert`** needs `--text` + `--content` — the revert endpoint doesn't reconstruct a version's body from its number; pull them from `memory versions` / `memory get` first.
156
155
 
157
156
  ## Onboarding (`init` / `setup`)
package/dist/index.js CHANGED
@@ -468,6 +468,12 @@ function emit(data, json) {
468
468
  function publicUrl(url) {
469
469
  return url.replace(/^https?:\/\/localhost:5012/, "https://sechroom.yi.ocd.codes");
470
470
  }
471
+ function resolveViewUrl(baseUrl, url) {
472
+ if (!url) return void 0;
473
+ if (/^https?:\/\//i.test(url)) return publicUrl(url);
474
+ const origin = baseUrl.replace(/\/api\/?$/i, "").replace(/\/+$/, "");
475
+ return publicUrl(`${origin}${url.startsWith("/") ? url : `/${url}`}`);
476
+ }
471
477
  function emitAction(summary, data, json) {
472
478
  if (json) {
473
479
  process.stdout.write(JSON.stringify(data) + "\n");
@@ -538,7 +544,8 @@ Examples:
538
544
  });
539
545
  });
540
546
  const titlePart = opts.title ? ` ${style.dim(`"${opts.title}"`)}` : "";
541
- const urlPart = data.url ? ` ${style.dim("\u2192")} ${publicUrl(data.url)}` : "";
547
+ const view = resolveViewUrl(cfg.baseUrl, data.url);
548
+ const urlPart = view ? ` ${style.dim("\u2192")} ${view}` : "";
542
549
  emitAction(`created memory ${style.bold(data.id)}${titlePart}${urlPart}`, data, cmd.optsWithGlobals().json);
543
550
  });
544
551
  memory.command("get <memoryId>").description("Fetch a memory by id (GET /memories/{memoryId})").action(async (memoryId, _opts, cmd) => {
@@ -847,7 +854,8 @@ Examples:
847
854
  });
848
855
  });
849
856
  const inversePart = data.inverseId ? ` ${style.dim(`(inverse ${data.inverseId})`)}` : "";
850
- const urlPart = data.url ? ` ${style.dim("\u2192")} ${publicUrl(data.url)}` : "";
857
+ const view = resolveViewUrl(cfg.baseUrl, data.url);
858
+ const urlPart = view ? ` ${style.dim("\u2192")} ${view}` : "";
851
859
  emitAction(
852
860
  `created relationship ${style.bold(data.id)} ${style.dim(`${fromMemoryId} \u2192 ${toMemoryId}`)}${inversePart}${urlPart}`,
853
861
  data,
@@ -997,7 +1005,8 @@ Examples:
997
1005
  }
998
1006
  });
999
1007
  });
1000
- const urlPart = data.url ? ` ${style.dim("\u2192")} ${publicUrl(data.url)}` : "";
1008
+ const view = resolveViewUrl(cfg.baseUrl, data.url);
1009
+ const urlPart = view ? ` ${style.dim("\u2192")} ${view}` : "";
1001
1010
  emitAction(
1002
1011
  `created workspace ${style.bold(data.id)} ${style.dim(`"${opts.name}"`)}${urlPart}`,
1003
1012
  data,
@@ -1140,7 +1149,8 @@ Examples:
1140
1149
  }
1141
1150
  });
1142
1151
  });
1143
- const urlPart = data.url ? ` ${style.dim("\u2192")} ${publicUrl(data.url)}` : "";
1152
+ const view = resolveViewUrl(cfg.baseUrl, data.url);
1153
+ const urlPart = view ? ` ${style.dim("\u2192")} ${view}` : "";
1144
1154
  emitAction(`created project ${style.bold(data.id)}${urlPart}`, data, cmd.optsWithGlobals().json);
1145
1155
  });
1146
1156
  project.command("list").description("List projects (GET /projects)").option("--workspace <workspaceId>", "Scope to a workspace").option("--status <status>", "Draft | Active | OnHold | Completed | Cancelled").option("--include-archived", "Include archived projects", false).action(async (opts, cmd) => {
@@ -1658,17 +1668,52 @@ Examples:
1658
1668
 
1659
1669
  // src/commands/chat.ts
1660
1670
  function registerChat(program2) {
1661
- const chat = program2.command("chat").description("Read Slack / Discord channel messages + thread replies").option("--surface <surface>", "slack | discord", "slack");
1671
+ const chat = program2.command("chat").description("Send and read Slack / Discord channel messages").option("--surface <surface>", "slack | discord", "slack");
1662
1672
  chat.addHelpText(
1663
1673
  "after",
1664
1674
  `
1665
1675
  Examples:
1676
+ $ sechroom chat send C0123456789 "deploy is green" --surface slack
1677
+ $ sechroom chat send 987654321098765432 "deploy is green" --surface discord --guild 123456789012345678
1678
+ $ sechroom chat send C0123456789 "lgtm" --surface slack --as user --parent 1718049600.123456
1666
1679
  $ sechroom chat messages --surface slack
1667
- $ sechroom chat messages --surface discord --json
1668
1680
  $ sechroom chat replies 1718049600.123456 --surface slack
1669
- $ sechroom chat replies 1234567890123456789 --surface discord --json
1670
1681
  $ sechroom chat stop-tracking 1718049600.123456 --surface slack`
1671
1682
  );
1683
+ 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, text, opts, cmd) => {
1684
+ const { surface, ...globals } = cmd.optsWithGlobals();
1685
+ const json = Boolean(cmd.optsWithGlobals().json);
1686
+ const cfg = resolveConfig(globals);
1687
+ const data = await runApi("Sending message", async () => {
1688
+ const client = await makeClient(cfg);
1689
+ return client.POST("/chat/channel-messages/{surface}", {
1690
+ params: { path: { surface: String(surface) } },
1691
+ body: {
1692
+ channelId,
1693
+ text,
1694
+ guildId: opts.guild ?? null,
1695
+ attachedMemoryId: opts.memory ?? null,
1696
+ trackReplies: opts.track,
1697
+ parentMessage: opts.parent ?? null,
1698
+ source: opts.source,
1699
+ as: opts.as
1700
+ }
1701
+ });
1702
+ });
1703
+ if (!data.ok) {
1704
+ if (json) {
1705
+ emit(data, true);
1706
+ } else {
1707
+ process.stderr.write(
1708
+ `${err("\u2717")} send failed: ${data.upstreamError ?? "error"}${data.errorDescription ? ` \u2014 ${data.errorDescription}` : ""}
1709
+ `
1710
+ );
1711
+ }
1712
+ process.exit(1);
1713
+ }
1714
+ const idPart = data.persistedId ? ` ${style.dim(`(${data.persistedId})`)}` : "";
1715
+ emitAction(`sent to ${surface} ${style.bold(channelId)}${idPart}`, data, json);
1716
+ });
1672
1717
  chat.command("messages").description("List recent channel messages (GET /chat/channel-messages/{surface})").action(async (_opts, cmd) => {
1673
1718
  const { surface, ...globals } = cmd.optsWithGlobals();
1674
1719
  const cfg = resolveConfig(globals);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sechroom/cli",
3
- "version": "2026.6.9",
3
+ "version": "2026.6.10",
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",