@bli-cockpit/cli 0.2.53 → 0.2.55

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.
Files changed (60) hide show
  1. package/dist/adapters/attribution-core-fallbacks.js +247 -0
  2. package/dist/adapters/attribution-core-paths.js +182 -0
  3. package/dist/adapters/attribution-core-score.js +159 -0
  4. package/dist/adapters/attribution-core-types.js +13 -0
  5. package/dist/adapters/attribution-core.js +13 -565
  6. package/dist/adapters/claude-attribution-discovery.js +186 -0
  7. package/dist/adapters/claude-attribution-score.js +204 -0
  8. package/dist/adapters/claude-attribution-signals.js +180 -0
  9. package/dist/adapters/claude-attribution-types.js +25 -0
  10. package/dist/adapters/claude-attribution.js +14 -569
  11. package/dist/commands/doctor-access.js +129 -0
  12. package/dist/commands/doctor-pipeline.js +326 -0
  13. package/dist/commands/doctor-registration.js +105 -0
  14. package/dist/commands/doctor-report.js +111 -0
  15. package/dist/commands/doctor-update.js +120 -0
  16. package/dist/commands/doctor.js +8 -753
  17. package/dist/commands/heartbeat.js +8 -0
  18. package/dist/commands/jarvis-contracts.js +8 -0
  19. package/dist/commands/jarvis-render.js +413 -0
  20. package/dist/commands/jarvis-turn.js +305 -0
  21. package/dist/commands/jarvis.js +23 -698
  22. package/dist/commands/local-args-collector-setup.js +250 -0
  23. package/dist/commands/local-args-collector-status.js +227 -0
  24. package/dist/commands/local-args-collector-work.js +175 -0
  25. package/dist/commands/local-args-collector.js +19 -624
  26. package/dist/commands/local-args-tower-admin.js +456 -0
  27. package/dist/commands/local-args-tower-chat.js +194 -0
  28. package/dist/commands/local-args-tower-pages.js +314 -0
  29. package/dist/commands/local-args-tower.js +13 -880
  30. package/dist/commands/local-help.js +10 -2
  31. package/dist/commands/onboard-completion.js +136 -0
  32. package/dist/commands/onboard-flows.js +165 -0
  33. package/dist/commands/onboard-setup.js +102 -0
  34. package/dist/commands/onboard.js +5 -392
  35. package/dist/commands/public-root.js +1 -1
  36. package/dist/commands/session-sync-counters.js +55 -0
  37. package/dist/commands/session-sync-health.js +8 -1
  38. package/dist/commands/session-sync-plan.js +47 -7
  39. package/dist/commands/session-sync-scan.js +4 -4
  40. package/dist/commands/session-sync.js +6 -0
  41. package/dist/commands/settings-render.js +27 -0
  42. package/dist/commands/sync-followups.js +5 -1
  43. package/dist/commands/sync.js +5 -1
  44. package/dist/commands/team-device-reasons.js +16 -0
  45. package/dist/commands/team.js +87 -7
  46. package/dist/evidence-upload-client.js +14 -763
  47. package/dist/evidence-upload-object.js +181 -0
  48. package/dist/evidence-upload-plan.js +233 -0
  49. package/dist/evidence-upload-terminal.js +309 -0
  50. package/dist/evidence-upload-transport.js +104 -0
  51. package/dist/spool/local-spool-io.js +122 -0
  52. package/dist/spool/local-spool-mutations.js +174 -0
  53. package/dist/spool/local-spool-parse.js +143 -0
  54. package/dist/spool/local-spool-types.js +22 -0
  55. package/dist/spool/local-spool.js +20 -426
  56. package/dist/upload-evidence-delivery-offer.js +144 -0
  57. package/dist/upload-evidence-delivery-reconcile.js +134 -0
  58. package/dist/upload-evidence-delivery-summary.js +205 -0
  59. package/dist/upload-evidence-delivery.js +12 -482
  60. package/package.json +3 -3
@@ -98,6 +98,33 @@ export function renderTeamMembers(body) {
98
98
  }
99
99
  return lines;
100
100
  }
101
+ /**
102
+ * BLI-3559: `cockpit team device list` — the same rows and the same summary
103
+ * sentence (`revoked_reason` / "Approved <date>" / "Waiting for approval")
104
+ * `GET /api/team/devices` computes, printed the way the web ambient page's
105
+ * device table shows them.
106
+ */
107
+ export function renderDeviceList(body) {
108
+ const devices = list(body.devices);
109
+ if (devices.length === 0)
110
+ return [`${INDENT}no collector devices`];
111
+ return devices.map((device) => {
112
+ const name = text(device.device_name, text(device.device_id));
113
+ const state = text(device.trust_state, "unknown");
114
+ const owner = text(device.owner_label, "—");
115
+ const summary = text(device.summary, "");
116
+ return `${INDENT}${padEndDisplay(name, 24)} ${padEndDisplay(state, 10)} ${padEndDisplay(owner, 26)} ${summary} [${text(device.device_id)}]`;
117
+ });
118
+ }
119
+ /** `cockpit team device revoke <id|name>` — one line naming what happened. */
120
+ export function renderDeviceRevoked(body) {
121
+ const device = record(body.device);
122
+ const name = text(device.device_name, text(device.device_id, "device"));
123
+ const reason = text(device.revoked_reason, "");
124
+ return reason
125
+ ? `${INDENT}Revoked ${name}. Reason: ${reason}`
126
+ : `${INDENT}Revoked ${name}.`;
127
+ }
101
128
  export function renderSwitches(body) {
102
129
  const switches = list(body.switches);
103
130
  if (switches.length === 0)
@@ -135,7 +135,11 @@ export async function runMemoryInstallAfterSync(command, io, dashboardUrl, optio
135
135
  dashboardUrl,
136
136
  dryRun: false,
137
137
  json: command.json,
138
- }, io);
138
+ }, io,
139
+ // Undefined on the real CLI (no io literal sets it); a test io can
140
+ // override the bin lookup so `bin_missing` is a fixture rather than a
141
+ // property of the machine the suite runs on (BLI-3630).
142
+ io.memoryInstallDeps);
139
143
  event = memoryInstallEvent(outcome);
140
144
  }
141
145
  catch (error) {
@@ -168,10 +168,12 @@ async function runSyncWithHealthReceipt(command, io) {
168
168
  };
169
169
  }
170
170
  try {
171
- const { exitCode, failureReasons, failureRecords, notice, sessionsObserved, sessionsOutsideRoot, } = await runSyncLocked(command, io);
171
+ const { exitCode, failureReasons, failureRecords, notice, sessionsObserved, sessionsOutsideRoot, sessionsNewThisTick, sessionsPendingUpload, } = await runSyncLocked(command, io);
172
172
  const counts = {
173
173
  sessionsObserved,
174
174
  sessionsOutsideRoot,
175
+ sessionsNewThisTick,
176
+ sessionsPendingUpload,
175
177
  };
176
178
  if (exitCode === 0) {
177
179
  // BLI-3551: an `ok` tick can still have something to say. `nothing_in_root`
@@ -226,6 +228,8 @@ function syncResult(run) {
226
228
  notice: run.notice,
227
229
  sessionsObserved: run.sessions_observed,
228
230
  sessionsOutsideRoot: run.sessions_outside_root,
231
+ sessionsNewThisTick: run.sessions_new_this_tick,
232
+ sessionsPendingUpload: run.sessions_pending_upload,
229
233
  };
230
234
  }
231
235
  /**
@@ -0,0 +1,16 @@
1
+ /**
2
+ * BLI-3559: the same closed reason vocabulary the dashboard's
3
+ * `lib/settings/device-revoke-reasons.ts` publishes, kept in this package
4
+ * because a CLI workspace cannot import the dashboard's app code. Five
5
+ * entries; keep the two lists in sync by eye if either changes.
6
+ */
7
+ export const TEAM_DEVICE_REVOKE_REASON_LABELS = [
8
+ "stale_test_pairing",
9
+ "machine_retired",
10
+ "person_left",
11
+ "re_pairing",
12
+ "other",
13
+ ];
14
+ export function isTeamDeviceRevokeReasonLabel(value) {
15
+ return TEAM_DEVICE_REVOKE_REASON_LABELS.includes(value);
16
+ }
@@ -1,6 +1,7 @@
1
1
  /**
2
- * `cockpit team` — who is on the team, and the two things an admin does to it
3
- * (BLI-3461, decision D3: invite and role change ship in the PUBLIC build).
2
+ * `cockpit team` — who is on the team, and the things an admin does to it
3
+ * (BLI-3461, decision D3: invite and role change ship in the PUBLIC build;
4
+ * BLI-3559 adds `device list` / `device revoke`).
4
5
  *
5
6
  * Same shape as the rest of the CLI-parity surface: this file owns terminal
6
7
  * input and output, the dashboard owns every decision. The roster union, the
@@ -8,12 +9,16 @@
8
9
  * server-side, so a stale CLI can be wrong about wording and never about
9
10
  * permission.
10
11
  *
11
- * `team invite` and `team role` change what a person can do, so both are
12
- * deliberate: the role change asks before it acts unless `--yes` is passed, and
13
- * a headless run without `--yes` is refused by name rather than assumed.
12
+ * `team invite`, `team role` and `team device revoke` change what a person
13
+ * can do (or end a machine's session outright), so all three are deliberate:
14
+ * each asks before it acts unless `--yes` is passed, and a headless run
15
+ * without `--yes` is refused by name rather than assumed. `device revoke`
16
+ * additionally names the device it is about to revoke BEFORE asking — the
17
+ * same "name it, then confirm" shape `dispatchCodingTask` uses (BLI-2981) —
18
+ * because a revoke is irreversible for that token.
14
19
  */
15
20
  import { writeLine } from "./cli-io.js";
16
- import { renderTeamMembers } from "./settings-render.js";
21
+ import { renderDeviceList, renderDeviceRevoked, renderTeamMembers } from "./settings-render.js";
17
22
  import { confirmDestructive } from "./settings.js";
18
23
  import { asRecord, callTower, openTower, writeCommandFailure, } from "./tower-command.js";
19
24
  export async function runTeam(command, io) {
@@ -22,7 +27,11 @@ export async function runTeam(command, io) {
22
27
  return showMembers(command, tower, io);
23
28
  if (command.action === "invite")
24
29
  return invite(command, tower, io);
25
- return changeRole(command, tower, io);
30
+ if (command.action === "role")
31
+ return changeRole(command, tower, io);
32
+ if (command.action === "device-list")
33
+ return deviceList(command, tower, io);
34
+ return deviceRevoke(command, tower, io);
26
35
  }
27
36
  async function showMembers(command, tower, io) {
28
37
  const result = await callTower(tower, { path: "/api/team/members", label: "team members" });
@@ -108,4 +117,75 @@ async function changeRole(command, tower, io) {
108
117
  }
109
118
  writeLine(io.stdout, `${String(body.user_id ?? command.targetUserId)} is now ${String(body.role)}.`);
110
119
  return 0;
120
+ }
121
+ function devicesOf(body) {
122
+ return Array.isArray(body.devices) ? body.devices : [];
123
+ }
124
+ /** Exact device_id, or an unambiguous case-insensitive device_name match. */
125
+ function findDevice(devices, ref) {
126
+ const needle = ref.trim();
127
+ if (!needle)
128
+ return null;
129
+ const byId = devices.find((d) => d.device_id === needle);
130
+ if (byId)
131
+ return byId;
132
+ const lowerNeedle = needle.toLowerCase();
133
+ const byName = devices.filter((d) => String(d.device_name ?? "").toLowerCase() === lowerNeedle);
134
+ return byName.length === 1 ? (byName[0] ?? null) : null;
135
+ }
136
+ async function deviceList(command, tower, io) {
137
+ const result = await callTower(tower, { path: "/api/team/devices", label: "team device list" });
138
+ if (!result.ok)
139
+ return writeCommandFailure(io, command.json, result);
140
+ const body = asRecord(result.body);
141
+ if (command.json) {
142
+ writeLine(io.stdout, JSON.stringify({ ok: true, ...body }));
143
+ return 0;
144
+ }
145
+ for (const line of renderDeviceList(body))
146
+ writeLine(io.stdout, line);
147
+ return 0;
148
+ }
149
+ /**
150
+ * `cockpit team device revoke <id|name> --reason <label> [--note "<text>"]`
151
+ *
152
+ * Names the device it is about to revoke BEFORE asking for confirmation — the
153
+ * same order `dispatchCodingTask`'s approval uses — and only then sends the
154
+ * revoke. A revoke is irreversible for that token, so the confirmation is
155
+ * real: `--yes` or a person at a keyboard, nothing else (`confirmDestructive`).
156
+ */
157
+ async function deviceRevoke(command, tower, io) {
158
+ const listed = await callTower(tower, { path: "/api/team/devices", label: "team device revoke lookup" });
159
+ if (!listed.ok)
160
+ return writeCommandFailure(io, command.json, listed);
161
+ const devices = devicesOf(asRecord(listed.body));
162
+ const device = findDevice(devices, command.deviceRef ?? "");
163
+ if (!device) {
164
+ return writeCommandFailure(io, command.json, {
165
+ reason: "device_not_found",
166
+ detail: `No device matches "${String(command.deviceRef)}". Run "cockpit team device list" to see ids and names.`,
167
+ });
168
+ }
169
+ const confirmed = await confirmDestructive(command, io, `Revoke ${String(device.device_name ?? device.device_id)} (${device.device_id})? This cannot be undone. [Y/n] `, "team device revoke");
170
+ if (!confirmed.ok)
171
+ return writeCommandFailure(io, command.json, confirmed);
172
+ const result = await callTower(tower, {
173
+ path: `/api/ambient/devices/${encodeURIComponent(device.device_id)}/revoke`,
174
+ method: "POST",
175
+ label: "team device revoke",
176
+ body: { reason_label: command.reasonLabel, reason_note: command.reasonNote },
177
+ });
178
+ writeLine(io.stderr, `[team cli] device revoke ${JSON.stringify({
179
+ reason_label: command.reasonLabel,
180
+ outcome: result.ok ? "revoked" : result.reason,
181
+ })}`);
182
+ if (!result.ok)
183
+ return writeCommandFailure(io, command.json, result);
184
+ const body = asRecord(result.body);
185
+ if (command.json) {
186
+ writeLine(io.stdout, JSON.stringify({ ok: true, ...body }));
187
+ return 0;
188
+ }
189
+ writeLine(io.stdout, renderDeviceRevoked(body));
190
+ return 0;
111
191
  }