@bli-cockpit/cli 0.2.239 → 0.2.240

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `cockpit msg` — channels and messages, typed (BLI-3706, BLI-3749).
3
3
  *
4
- * Eight verbs over `/api/msg/**` (BLI-3654 Wave 1a), which already accepts the
4
+ * Eleven verbs over `/api/msg/**` (BLI-3654 Wave 1a), which already accepts the
5
5
  * collector device token through `resolveCaller({ allowDeviceToken: true })`
6
6
  * on every route — no server-side door change was needed for this terminal.
7
7
  *
@@ -15,14 +15,15 @@
15
15
  * silently adding a stranger. `dm` does not have to name the caller — the
16
16
  * door unions them in.
17
17
  */
18
+ import { randomUUID } from "node:crypto";
18
19
  import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./agent-door.js";
19
- import { isInteractiveStdin, readPipedText, writeLine } from "./cli-io.js";
20
+ import { writeLine } from "./cli-io.js";
20
21
  import { cacheAgeLabel, cacheCoverage, readThroughCache } from "./read-cache.js";
21
22
  import { markReadThrough } from "./msg-read-line.js";
23
+ import { deleteMessage, editMessage, messageOutcome, messageText, readContent, refuseMsgWrite } from "./msg-write.js";
22
24
  const TAG = "[msg cli]";
23
25
  const READ_DEADLINE_MS = 30_000;
24
26
  const WRITE_DEADLINE_MS = 30_000;
25
- const CONTENT_MAX_CHARS = 8_000;
26
27
  export async function runMsg(command, io) {
27
28
  const door = await openAgentDoor("msg", command, io);
28
29
  switch (command.action) {
@@ -36,6 +37,12 @@ export async function runMsg(command, io) {
36
37
  return markUnread(command, door);
37
38
  case "send":
38
39
  return sendMessage(command, door);
40
+ case "edit":
41
+ return editMessage(command, door);
42
+ case "delete":
43
+ return deleteMessage(command, door);
44
+ case "outcome":
45
+ return messageOutcome(command, door);
39
46
  case "thread":
40
47
  return readThread(command, door);
41
48
  case "create":
@@ -180,7 +187,7 @@ async function readChannel(command, door) {
180
187
  for (const message of [...messages].reverse()) {
181
188
  // An imported Slack message has no Tower user; its Slack name is the label.
182
189
  const who = message.agent_label ?? message.user_id ?? message.slack_author_label ?? "someone";
183
- writeLine(door.io.stdout, `${message.id} ${message.created_at} ${who} ${message.content ?? ""}`);
190
+ writeLine(door.io.stdout, `${message.id} ${message.created_at} ${who} ${messageText(message)}`);
184
191
  }
185
192
  if (readMove) {
186
193
  writeLine(readMove.ok ? door.io.stdout : door.io.stderr, readMove.ok
@@ -243,29 +250,10 @@ async function readThread(command, door) {
243
250
  }
244
251
  for (const message of [...messages].reverse()) {
245
252
  const who = message.agent_label ?? message.user_id;
246
- writeLine(door.io.stdout, `${message.id} ${message.created_at} ${who} ${message.content ?? ""}`);
253
+ writeLine(door.io.stdout, `${message.id} ${message.created_at} ${who} ${messageText(message)}`);
247
254
  }
248
255
  return 0;
249
256
  }
250
- async function readContent(io) {
251
- if (isInteractiveStdin(io)) {
252
- return {
253
- ok: false,
254
- reason: "nothing_piped",
255
- detail: "Pipe the message in, e.g. `echo \"hello\" | cockpit msg send general`. A message never travels on the command line.",
256
- };
257
- }
258
- try {
259
- const text = await readPipedText(io.stdin, {
260
- maxChars: CONTENT_MAX_CHARS,
261
- overflowMessage: `A message is limited to ${CONTENT_MAX_CHARS} characters.`,
262
- });
263
- return { ok: true, text: text.trim() };
264
- }
265
- catch (error) {
266
- return { ok: false, reason: "content_too_long", detail: error instanceof Error ? error.message : String(error) };
267
- }
268
- }
269
257
  async function sendMessage(command, door) {
270
258
  const ref = command.channelRef ?? "";
271
259
  const resolved = await resolveChannelId(door, ref);
@@ -278,6 +266,8 @@ async function sendMessage(command, door) {
278
266
  return failAgentDoor(door, TAG, content.reason, content.detail);
279
267
  if (content.text === "")
280
268
  return failAgentDoor(door, TAG, "invalid_body", "There was nothing to send. Nothing was sent.");
269
+ // BLI-21314: every send carries a request id, so a lost response can be checked and a retry posts once.
270
+ const requestId = command.requestId ?? randomUUID();
281
271
  const answer = await askAgentDoor(door, {
282
272
  path: `/api/msg/channels/${encodeURIComponent(resolved.id)}/messages`,
283
273
  method: "POST",
@@ -285,15 +275,16 @@ async function sendMessage(command, door) {
285
275
  timeoutMs: WRITE_DEADLINE_MS,
286
276
  body: {
287
277
  content: content.text,
278
+ request_id: requestId,
288
279
  ...(command.threadId ? { thread_parent_id: command.threadId } : {}),
289
280
  },
290
281
  });
291
282
  if (!answer.ok)
292
- return failAgentDoor(door, TAG, answer.reason, answer.detail);
293
- const message = answer.body.message ?? null;
294
- writeLine(door.io.stderr, `${TAG} sent ${JSON.stringify({ channel_id: resolved.id, message_id: message?.id ?? null, chars: content.text.length })}`);
283
+ return refuseMsgWrite(door, answer, requestId, "send");
284
+ const { message = null, mutation = null } = answer.body;
285
+ writeLine(door.io.stderr, `${TAG} sent ${JSON.stringify({ channel_id: resolved.id, message_id: message?.id ?? null, request_id: requestId, chars: content.text.length })}`);
295
286
  if (door.json)
296
- return emitAgentDoor(door, { ok: true, message });
287
+ return emitAgentDoor(door, { ok: true, message, mutation });
297
288
  writeLine(door.io.stdout, `Sent to ${ref} (${message?.id ?? "?"}).`);
298
289
  return 0;
299
290
  }
@@ -386,7 +377,7 @@ async function showMessage(command, door) {
386
377
  writeLine(door.io.stdout, body.href ?? "Message context");
387
378
  for (const page of [body.channelPage, body.threadPage]) {
388
379
  for (const row of [...(page?.messages ?? [])].reverse())
389
- writeLine(door.io.stdout, `${row.id} ${row.created_at} ${row.content ?? ""}`);
380
+ writeLine(door.io.stdout, `${row.id} ${row.created_at} ${messageText(row)}`);
390
381
  if (page?.nextCursor)
391
382
  writeLine(door.io.stdout, `More history: --before ${page.nextCursor}`);
392
383
  }
@@ -10,21 +10,30 @@
10
10
  */
11
11
  import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor } from "./agent-door.js";
12
12
  import { writeLine } from "./cli-io.js";
13
+ import { pageFieldsOf, pageFooter } from "./issue-list-page.js";
13
14
  const TAG = "[project cli]";
14
15
  const READ_DEADLINE_MS = 30_000;
15
16
  export async function runProject(command, io) {
16
17
  const door = await openAgentDoor("project", command, io);
18
+ const params = new URLSearchParams();
19
+ if (command.includeArchived)
20
+ params.set("include_archived", "true");
21
+ if (command.limit !== undefined)
22
+ params.set("limit", String(command.limit));
23
+ if (command.cursor)
24
+ params.set("cursor", command.cursor);
17
25
  const answer = await askAgentDoor(door, {
18
- path: `/api/work/projects${command.includeArchived ? "?include_archived=true" : ""}`,
26
+ path: `/api/work/projects${params.toString() ? `?${params.toString()}` : ""}`,
19
27
  method: "GET",
20
28
  label: "project list",
21
29
  timeoutMs: READ_DEADLINE_MS,
22
30
  });
23
31
  if (!answer.ok)
24
32
  return failAgentDoor(door, TAG, answer.reason, answer.detail);
25
- const projects = answer.body.projects ?? [];
33
+ const body = answer.body;
34
+ const projects = body.projects ?? [];
26
35
  if (door.json)
27
- return emitAgentDoor(door, { ok: true, projects });
36
+ return emitAgentDoor(door, { ok: true, projects, ...pageFieldsOf(body) });
28
37
  if (projects.length === 0) {
29
38
  writeLine(door.io.stdout, "No projects.");
30
39
  return 0;
@@ -34,5 +43,8 @@ export async function runProject(command, io) {
34
43
  }
35
44
  writeLine(door.io.stdout, "");
36
45
  writeLine(door.io.stdout, `${projects.length} project(s).`);
46
+ const footer = pageFooter(body, "projects");
47
+ if (footer)
48
+ writeLine(door.io.stdout, footer);
37
49
  return 0;
38
50
  }
@@ -21,7 +21,7 @@ export async function runCockpitCli(argv, io) {
21
21
  }
22
22
 
23
23
  if (command === "--version" || command === "-V" || command === "version") {
24
- writeLine(io?.stdout ?? process.stdout, "0.2.239");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.240");
25
25
  return 0;
26
26
  }
27
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.239",
3
+ "version": "0.2.240",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@bli-cockpit/memory-mcp": "0.1.70",
31
- "@bli-cockpit/mcp": "0.1.144",
31
+ "@bli-cockpit/mcp": "0.1.145",
32
32
  "@bli-cockpit/telemetry-core": "0.1.77"
33
33
  },
34
34
  "repository": {