@bli-cockpit/cli 0.2.257 → 0.2.258

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.
@@ -375,6 +375,18 @@ function parseTeamDeviceArgs(rest, flags, common) {
375
375
  }
376
376
  return { kind: "team", action: "device-scope", deviceRef, deviceScope, ...common };
377
377
  }
378
+ if (deviceVerb === "mark-service" || deviceVerb === "unmark-service") {
379
+ const reasonLabel = optionalNonEmpty(flags.get("--reason"));
380
+ if (!deviceRef || extra.length > 0)
381
+ throw new Error(`team device ${deviceVerb} takes one <id|name>${deviceVerb === "mark-service" ? " and --reason <label>" : ""}.`);
382
+ if (deviceVerb === "mark-service") {
383
+ if (!reasonLabel || !/^[a-z0-9][a-z0-9_-]{1,39}$/.test(reasonLabel)) {
384
+ throw new Error("team device mark-service needs --reason <label> (2-40 chars: a-z, 0-9, _ or -), e.g. --reason boat-agent.");
385
+ }
386
+ return { kind: "team", action: "device-mark-service", deviceRef, reasonLabel, ...common };
387
+ }
388
+ return { kind: "team", action: "device-unmark-service", deviceRef, ...common };
389
+ }
378
390
  if (extra.length > 0)
379
391
  throw new Error("team device does not accept that many arguments.");
380
392
  if (!deviceVerb || deviceVerb === "list") {
@@ -406,7 +418,7 @@ function parseTeamDeviceArgs(rest, flags, common) {
406
418
  ...common,
407
419
  };
408
420
  }
409
- throw new Error("team device takes one verb: list, doctor <id|name>, scope <id|name> notetaker_bot|none, or revoke <id|name>.");
421
+ throw new Error("team device takes one verb: list, doctor <id|name>, scope <id|name> notetaker_bot|none, mark-service <id|name> --reason <label>, unmark-service <id|name>, or revoke <id|name>.");
410
422
  }
411
423
  /** `cockpit model [show|set <key>]` — the alias surface for the personal chat model. */
412
424
  export function parseModelArgs(args) {
@@ -211,6 +211,11 @@ const COMMAND_HELP = new Map([
211
211
  " cockpit team device scope <id|name> notetaker_bot|none [--json]",
212
212
  " Super admin only. Gives the meeting bot's device the scope Tower's claim door requires,",
213
213
  " or takes it away. Without it the bot is refused every meeting (bot_scope_required).",
214
+ " cockpit team device mark-service <id|name> --reason <label> [--json]",
215
+ " cockpit team device unmark-service <id|name> [--json]",
216
+ " Super admin only. Marks a device as automation (cloud agents, alert senders) so the fleet board",
217
+ " lists it under service identities, judged only on when it last called Tower, instead of a red laptop.",
218
+ " --reason is a short label such as boat-agent. Only this door can set it; a device cannot.",
214
219
  " cockpit team device revoke <id|name> --reason <label> [--note \"<text>\"] [--yes]",
215
220
  " Admin only. Names the device, asks first unless --yes, then revokes it —",
216
221
  " this cannot be undone. --reason must be one of: stale_test_pairing,",
@@ -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.257");
24
+ writeLine(io?.stdout ?? process.stdout, "0.2.258");
25
25
  return 0;
26
26
  }
27
27
 
@@ -41,6 +41,8 @@ export async function runTeam(command, io) {
41
41
  return deviceDoctor(command, tower, io);
42
42
  if (command.action === "device-scope")
43
43
  return deviceScope(command, tower, io);
44
+ if (command.action === "device-mark-service" || command.action === "device-unmark-service")
45
+ return deviceMarkService(command, tower, io);
44
46
  return deviceRevoke(command, tower, io);
45
47
  }
46
48
  async function showMembers(command, tower, io) {
@@ -246,6 +248,37 @@ async function deviceScope(command, tower, io) {
246
248
  : command.deviceScope === "none" ? `Took the service scope away from ${name}.` : `${name} now holds the notetaker_bot scope. The bot's next claim will go through.`);
247
249
  return 0;
248
250
  }
251
+ /**
252
+ * `cockpit team device mark-service <id|name> --reason <label>` and `unmark-service <id|name>` (ticket 23031): the ONE
253
+ * door that tells the fleet board a device is automation, not a laptop. Super admin on the server.
254
+ */
255
+ async function deviceMarkService(command, tower, io) {
256
+ const mark = command.action === "device-mark-service";
257
+ const listed = await callTower(tower, { path: "/api/team/devices", label: "team device mark-service lookup" });
258
+ if (!listed.ok)
259
+ return writeCommandFailure(io, command.json, listed);
260
+ const device = findDevice(devicesOf(asRecord(listed.body)), command.deviceRef ?? "");
261
+ if (!device)
262
+ return writeCommandFailure(io, command.json, { reason: "device_not_found", detail: "No unique device matches. Run cockpit team device list to see ids and names." });
263
+ const result = await callTower(tower, {
264
+ path: `/api/ambient/devices/${encodeURIComponent(device.device_id)}/service-mark`,
265
+ method: "POST", label: "team device mark-service", body: mark ? { mark: true, reason: command.reasonLabel } : { mark: false },
266
+ });
267
+ writeLine(io.stderr, `[team cli] device mark-service ${JSON.stringify({ device_id: device.device_id, mark, reason: mark ? command.reasonLabel : null, outcome: result.ok ? "answered" : result.reason })}`);
268
+ if (!result.ok)
269
+ return writeCommandFailure(io, command.json, result);
270
+ const body = asRecord(result.body);
271
+ if (command.json) {
272
+ writeLine(io.stdout, JSON.stringify({ ok: true, ...body }));
273
+ return 0;
274
+ }
275
+ const name = String(device.device_name ?? device.device_id);
276
+ writeLine(io.stdout, body.changed === false
277
+ ? `${name} was already ${mark ? "marked as automation" : "not marked"}. Nothing changed.`
278
+ : mark ? `${name} is now marked as automation (${command.reasonLabel}). The fleet board lists it under service identities.`
279
+ : `Took the automation mark off ${name}. The fleet board grades it as a laptop again.`);
280
+ return 0;
281
+ }
249
282
  /** Exact directory labels locate a roster id; attribution remains the server resolver's job. */
250
283
  async function githubLogin(command, tower, io) {
251
284
  let personId = command.personRef ?? "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/cli",
3
- "version": "0.2.257",
3
+ "version": "0.2.258",
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.76",
31
- "@bli-cockpit/mcp": "0.1.157",
31
+ "@bli-cockpit/mcp": "0.1.158",
32
32
  "@bli-cockpit/telemetry-core": "0.1.83"
33
33
  },
34
34
  "repository": {