@skrr-ai/cli 0.1.22 → 0.1.23

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 (243) hide show
  1. package/README.md +83 -73
  2. package/bin/dev-fallback.js +221 -0
  3. package/bin/run.js +43 -0
  4. package/dist/base-command.d.ts +15 -0
  5. package/dist/base-command.js +71 -10
  6. package/dist/commands/agents/attach-harness.d.ts +37 -0
  7. package/dist/commands/agents/attach-harness.js +102 -3
  8. package/dist/commands/agents/chat.d.ts +69 -1
  9. package/dist/commands/agents/chat.js +202 -22
  10. package/dist/commands/agents/create.d.ts +38 -0
  11. package/dist/commands/agents/create.js +95 -2
  12. package/dist/commands/agents/schedule-trace.js +11 -0
  13. package/dist/commands/agents/show.js +29 -1
  14. package/dist/commands/api-keys/create.d.ts +24 -0
  15. package/dist/commands/api-keys/create.js +80 -0
  16. package/dist/commands/api-keys/list.d.ts +11 -0
  17. package/dist/commands/api-keys/list.js +42 -0
  18. package/dist/commands/api-keys/revoke.d.ts +14 -0
  19. package/dist/commands/api-keys/revoke.js +33 -0
  20. package/dist/commands/api-keys/rotate.d.ts +19 -0
  21. package/dist/commands/api-keys/rotate.js +42 -0
  22. package/dist/commands/balance/index.d.ts +18 -0
  23. package/dist/commands/balance/index.js +29 -0
  24. package/dist/commands/balance/show.d.ts +25 -0
  25. package/dist/commands/balance/show.js +60 -0
  26. package/dist/commands/balance/statement.d.ts +19 -0
  27. package/dist/commands/balance/statement.js +52 -0
  28. package/dist/commands/browser/doctor.d.ts +11 -0
  29. package/dist/commands/browser/doctor.js +11 -0
  30. package/dist/commands/code/jobs/approve.d.ts +25 -0
  31. package/dist/commands/code/jobs/approve.js +52 -0
  32. package/dist/commands/code/jobs/cancel.d.ts +12 -0
  33. package/dist/commands/code/jobs/cancel.js +39 -0
  34. package/dist/commands/code/jobs/index.d.ts +19 -0
  35. package/dist/commands/code/jobs/index.js +38 -0
  36. package/dist/commands/code/jobs/list.d.ts +12 -0
  37. package/dist/commands/code/jobs/list.js +87 -0
  38. package/dist/commands/code/jobs/run.d.ts +32 -0
  39. package/dist/commands/code/jobs/run.js +151 -0
  40. package/dist/commands/code/jobs/show.d.ts +12 -0
  41. package/dist/commands/code/jobs/show.js +86 -0
  42. package/dist/commands/commitments/preflight.js +18 -4
  43. package/dist/commands/commitments/receipt-metrics.d.ts +39 -0
  44. package/dist/commands/commitments/receipt-metrics.js +78 -0
  45. package/dist/commands/convos/search.js +5 -5
  46. package/dist/commands/harnesses/leases/list.d.ts +3 -0
  47. package/dist/commands/harnesses/leases/list.js +59 -12
  48. package/dist/commands/harnesses/leases/show.js +36 -0
  49. package/dist/commands/harnesses/list.d.ts +18 -0
  50. package/dist/commands/harnesses/list.js +30 -0
  51. package/dist/commands/harnesses/products.js +48 -3
  52. package/dist/commands/harnesses/quota.d.ts +16 -0
  53. package/dist/commands/harnesses/quota.js +26 -1
  54. package/dist/commands/harnesses/usage.js +17 -0
  55. package/dist/commands/inbox/index.d.ts +2 -1
  56. package/dist/commands/inbox/index.js +27 -4
  57. package/dist/commands/login.js +4 -1
  58. package/dist/commands/machines/list.d.ts +1 -0
  59. package/dist/commands/machines/list.js +33 -5
  60. package/dist/commands/memory/approvals/issue.d.ts +18 -0
  61. package/dist/commands/memory/approvals/issue.js +63 -0
  62. package/dist/commands/memory/commit.js +5 -2
  63. package/dist/commands/memory/context.d.ts +3 -2
  64. package/dist/commands/memory/context.js +9 -11
  65. package/dist/commands/memory/delete.d.ts +1 -1
  66. package/dist/commands/memory/list.d.ts +1 -1
  67. package/dist/commands/memory/list.js +1 -1
  68. package/dist/commands/memory/mounts/resolve.d.ts +10 -0
  69. package/dist/commands/memory/mounts/resolve.js +27 -0
  70. package/dist/commands/memory/proposals/commit.d.ts +18 -0
  71. package/dist/commands/memory/proposals/commit.js +32 -0
  72. package/dist/commands/memory/proposals/create.d.ts +18 -0
  73. package/dist/commands/memory/proposals/create.js +56 -0
  74. package/dist/commands/memory/proposals/list.d.ts +13 -0
  75. package/dist/commands/memory/proposals/list.js +31 -0
  76. package/dist/commands/memory/proposals/promote.d.ts +22 -0
  77. package/dist/commands/memory/proposals/promote.js +33 -0
  78. package/dist/commands/memory/proposals/reject.d.ts +16 -0
  79. package/dist/commands/memory/proposals/reject.js +28 -0
  80. package/dist/commands/memory/rebuild-index.d.ts +20 -0
  81. package/dist/commands/memory/rebuild-index.js +25 -0
  82. package/dist/commands/memory/save.d.ts +1 -1
  83. package/dist/commands/memory/save.js +1 -1
  84. package/dist/commands/memory/search.d.ts +6 -3
  85. package/dist/commands/memory/search.js +27 -3
  86. package/dist/commands/memory/show.d.ts +1 -1
  87. package/dist/commands/memory/stores/archive.d.ts +14 -0
  88. package/dist/commands/memory/stores/archive.js +31 -0
  89. package/dist/commands/memory/stores/create.d.ts +12 -0
  90. package/dist/commands/memory/stores/create.js +30 -0
  91. package/dist/commands/memory/stores/delete.d.ts +15 -0
  92. package/dist/commands/memory/stores/delete.js +38 -0
  93. package/dist/commands/memory/stores/list.d.ts +10 -0
  94. package/dist/commands/memory/stores/list.js +29 -0
  95. package/dist/commands/memory/stores/show.d.ts +12 -0
  96. package/dist/commands/memory/stores/show.js +14 -0
  97. package/dist/commands/memory/stores/update.d.ts +15 -0
  98. package/dist/commands/memory/stores/update.js +39 -0
  99. package/dist/commands/memory/sync/delete.d.ts +13 -0
  100. package/dist/commands/memory/sync/delete.js +35 -0
  101. package/dist/commands/memory/sync/pull.d.ts +19 -0
  102. package/dist/commands/memory/sync/pull.js +52 -0
  103. package/dist/commands/memory/sync/push.d.ts +18 -0
  104. package/dist/commands/memory/sync/push.js +115 -0
  105. package/dist/commands/memory/sync/resolve.d.ts +23 -0
  106. package/dist/commands/memory/sync/resolve.js +27 -0
  107. package/dist/commands/memory/sync/status.d.ts +9 -0
  108. package/dist/commands/memory/sync/status.js +22 -0
  109. package/dist/commands/memory/topics/archive.d.ts +20 -0
  110. package/dist/commands/memory/topics/archive.js +29 -0
  111. package/dist/commands/memory/topics/create.d.ts +23 -0
  112. package/dist/commands/memory/topics/create.js +46 -0
  113. package/dist/commands/memory/topics/delete.d.ts +22 -0
  114. package/dist/commands/memory/topics/delete.js +30 -0
  115. package/dist/commands/memory/topics/forget.d.ts +22 -0
  116. package/dist/commands/memory/topics/forget.js +29 -0
  117. package/dist/commands/memory/topics/list.d.ts +13 -0
  118. package/dist/commands/memory/topics/list.js +22 -0
  119. package/dist/commands/memory/topics/move.d.ts +22 -0
  120. package/dist/commands/memory/topics/move.js +26 -0
  121. package/dist/commands/memory/topics/show.d.ts +17 -0
  122. package/dist/commands/memory/topics/show.js +21 -0
  123. package/dist/commands/memory/topics/update.d.ts +23 -0
  124. package/dist/commands/memory/topics/update.js +27 -0
  125. package/dist/commands/memory/tree.d.ts +5 -2
  126. package/dist/commands/memory/tree.js +20 -2
  127. package/dist/commands/memory/update.d.ts +1 -1
  128. package/dist/commands/memory/versions/list.d.ts +16 -0
  129. package/dist/commands/memory/versions/list.js +22 -0
  130. package/dist/commands/memory/versions/redact.d.ts +21 -0
  131. package/dist/commands/memory/versions/redact.js +22 -0
  132. package/dist/commands/memory/versions/restore.d.ts +20 -0
  133. package/dist/commands/memory/versions/restore.js +16 -0
  134. package/dist/commands/memory/versions/show.d.ts +15 -0
  135. package/dist/commands/memory/versions/show.js +17 -0
  136. package/dist/commands/messages/list.js +25 -8
  137. package/dist/commands/messages/show.js +10 -0
  138. package/dist/commands/store/install.js +5 -1
  139. package/dist/commands/store/releases.js +1 -1
  140. package/dist/commands/store/update.js +5 -1
  141. package/dist/commands/subscriptions/cancel.js +5 -1
  142. package/dist/commands/subscriptions/health.js +5 -1
  143. package/dist/commands/subscriptions/status.js +5 -1
  144. package/dist/commands/subscriptions/subscribe.js +5 -1
  145. package/dist/commands/tasks/activity.d.ts +2 -3
  146. package/dist/commands/tasks/activity.js +15 -5
  147. package/dist/commands/tasks/complete.d.ts +18 -150
  148. package/dist/commands/tasks/complete.js +161 -600
  149. package/dist/commands/tasks/create.d.ts +5 -0
  150. package/dist/commands/tasks/create.js +107 -4
  151. package/dist/commands/tasks/deliverable/add.d.ts +8 -0
  152. package/dist/commands/tasks/deliverable/add.js +41 -8
  153. package/dist/commands/tasks/deliverable/confirm.d.ts +5 -5
  154. package/dist/commands/tasks/deliverable/confirm.js +9 -18
  155. package/dist/commands/tasks/deliverable/list.d.ts +12 -0
  156. package/dist/commands/tasks/deliverable/list.js +37 -0
  157. package/dist/commands/tasks/events/append.d.ts +2 -0
  158. package/dist/commands/tasks/events/append.js +11 -4
  159. package/dist/commands/tasks/events/list.d.ts +1 -0
  160. package/dist/commands/tasks/events/list.js +1 -0
  161. package/dist/commands/tasks/events/tail.d.ts +1 -0
  162. package/dist/commands/tasks/events/tail.js +1 -0
  163. package/dist/commands/tasks/expectations/assess.d.ts +21 -0
  164. package/dist/commands/tasks/expectations/assess.js +77 -0
  165. package/dist/commands/tasks/expectations.js +14 -18
  166. package/dist/commands/tasks/judge.d.ts +1 -0
  167. package/dist/commands/tasks/judge.js +2 -1
  168. package/dist/commands/tasks/output.d.ts +2 -11
  169. package/dist/commands/tasks/output.js +64 -145
  170. package/dist/commands/tasks/report.d.ts +3 -3
  171. package/dist/commands/tasks/report.js +64 -41
  172. package/dist/commands/tasks/resource/add.d.ts +23 -0
  173. package/dist/commands/tasks/resource/add.js +76 -0
  174. package/dist/commands/tasks/resource/list.d.ts +13 -0
  175. package/dist/commands/tasks/resource/list.js +41 -0
  176. package/dist/commands/tasks/result/show.d.ts +13 -0
  177. package/dist/commands/tasks/result/show.js +46 -0
  178. package/dist/commands/tasks/result/submit.d.ts +26 -0
  179. package/dist/commands/tasks/result/submit.js +134 -0
  180. package/dist/commands/tasks/resume/save.d.ts +16 -0
  181. package/dist/commands/tasks/resume/save.js +59 -0
  182. package/dist/commands/tasks/resume/show.d.ts +13 -0
  183. package/dist/commands/tasks/resume/show.js +35 -0
  184. package/dist/commands/tasks/review.d.ts +17 -0
  185. package/dist/commands/tasks/review.js +54 -0
  186. package/dist/commands/tasks/runs.js +5 -0
  187. package/dist/commands/tasks/timeline.d.ts +2 -2
  188. package/dist/commands/tasks/timeline.js +10 -23
  189. package/dist/commands/tasks/updates/add.d.ts +22 -0
  190. package/dist/commands/tasks/updates/add.js +101 -0
  191. package/dist/commands/tasks/updates/list.d.ts +15 -0
  192. package/dist/commands/tasks/updates/list.js +57 -0
  193. package/dist/commands/tasks/waive.d.ts +1 -0
  194. package/dist/commands/tasks/waive.js +2 -1
  195. package/dist/lib/agentic-stream.d.ts +257 -0
  196. package/dist/lib/agentic-stream.js +490 -33
  197. package/dist/lib/balance.d.ts +8 -0
  198. package/dist/lib/commitment-product.d.ts +10 -1
  199. package/dist/lib/commitment-product.js +66 -2
  200. package/dist/lib/commitments.d.ts +2 -1
  201. package/dist/lib/commitments.js +1 -0
  202. package/dist/lib/conversation-search.d.ts +3 -0
  203. package/dist/lib/conversation-search.js +75 -0
  204. package/dist/lib/execution-target.d.ts +21 -0
  205. package/dist/lib/execution-target.js +22 -6
  206. package/dist/lib/harnesses.d.ts +82 -6
  207. package/dist/lib/harnesses.js +30 -2
  208. package/dist/lib/hosted-machines.js +9 -0
  209. package/dist/lib/inbox.d.ts +2 -1
  210. package/dist/lib/machines.d.ts +17 -0
  211. package/dist/lib/machines.js +29 -0
  212. package/dist/lib/managed-agent-keys.d.ts +104 -0
  213. package/dist/lib/managed-agent-keys.js +50 -0
  214. package/dist/lib/memory-scope.d.ts +4 -3
  215. package/dist/lib/memory-scope.js +9 -9
  216. package/dist/lib/memory-sync.d.ts +114 -0
  217. package/dist/lib/memory-sync.js +730 -0
  218. package/dist/lib/message-provenance.d.ts +42 -0
  219. package/dist/lib/message-provenance.js +89 -0
  220. package/dist/lib/scoped-memory-command.d.ts +68 -0
  221. package/dist/lib/scoped-memory-command.js +183 -0
  222. package/dist/lib/session-task-endpoints.d.ts +1 -1
  223. package/dist/lib/session-task-endpoints.js +7 -0
  224. package/dist/lib/sky-code-broker.d.ts +47 -0
  225. package/dist/lib/sky-code-broker.js +50 -0
  226. package/dist/lib/sky-code-doctor.js +10 -1
  227. package/dist/lib/task-artifact-flags.d.ts +2 -0
  228. package/dist/lib/task-artifact-flags.js +7 -0
  229. package/dist/lib/task-execution.d.ts +4 -5
  230. package/dist/lib/task-execution.js +4 -5
  231. package/dist/lib/task-extras.d.ts +1 -5
  232. package/dist/lib/task-extras.js +1 -5
  233. package/dist/lib/task-transcript.d.ts +5 -7
  234. package/dist/lib/task-transcript.js +8 -16
  235. package/dist/lib/tasks.d.ts +7 -21
  236. package/dist/lib/tasks.js +17 -20
  237. package/dist/lib/triggers.d.ts +39 -1
  238. package/dist/lib/triggers.js +70 -2
  239. package/dist/lib/usage-discovery.d.ts +17 -1
  240. package/dist/lib/usage-discovery.js +17 -1
  241. package/dist/node_modules/@skrr-ai/data-provider/index.js +3733 -3618
  242. package/oclif.manifest.json +16201 -10377
  243. package/package.json +26 -5
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
+ const conversation_search_1 = require("../../lib/conversation-search");
6
7
  const format_1 = require("../../lib/format");
7
8
  /**
8
9
  * Message-level search across the caller's own conversations.
@@ -51,13 +52,14 @@ class ConvosSearch extends base_command_1.BaseCommand {
51
52
  }
52
53
  const hits = Array.isArray(response?.messages) ? response.messages : [];
53
54
  const shown = hits.slice(0, flags.limit);
55
+ const compactHits = shown.map((message) => (0, conversation_search_1.compactMessageSearchHit)(message, flags.snippet, query));
54
56
  if (flags.json) {
55
57
  this.log(JSON.stringify({
56
58
  query,
57
59
  total: hits.length,
58
60
  shown: shown.length,
59
61
  truncated: hits.length > shown.length,
60
- hits: shown,
62
+ hits: compactHits,
61
63
  }, null, 2));
62
64
  return;
63
65
  }
@@ -65,13 +67,11 @@ class ConvosSearch extends base_command_1.BaseCommand {
65
67
  this.log(`No messages matched "${query}".`);
66
68
  return;
67
69
  }
68
- (0, format_1.renderTable)(shown.map((m) => ({
70
+ (0, format_1.renderTable)(compactHits.map((m) => ({
69
71
  when: m.createdAt ? String(m.createdAt).slice(0, 10) : '-',
70
72
  who: m.isCreatedByUser === true ? 'you' : (0, format_1.formatCell)(m.sender, 'agent'),
71
73
  thread: (0, format_1.formatCell)(m.title, 'Untitled'),
72
- snippet: String(m.text ?? '')
73
- .replace(/\s+/g, ' ')
74
- .slice(0, flags.snippet),
74
+ snippet: String(m.text ?? ''),
75
75
  })), [
76
76
  { key: 'when', header: 'WHEN' },
77
77
  { key: 'who', header: 'WHO', maxWidth: 14 },
@@ -4,6 +4,9 @@ export default class HarnessesLeasesList extends BaseCommand {
4
4
  static examples: string[];
5
5
  static flags: {
6
6
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ state: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ product: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
7
10
  };
8
11
  run(): Promise<void>;
9
12
  }
@@ -14,18 +14,38 @@ const base_command_1 = require("../../../base-command");
14
14
  const format_1 = require("../../../lib/format");
15
15
  const harnesses_1 = require("../../../lib/harnesses");
16
16
  class HarnessesLeasesList extends base_command_1.BaseCommand {
17
- static description = 'List your open harness leases';
17
+ static description = 'List your harness leases, newest first';
18
18
  static examples = [
19
19
  '<%= config.bin %> harnesses leases list',
20
+ '<%= config.bin %> harnesses leases list --state lost',
21
+ '<%= config.bin %> harnesses leases list --limit 100 --product hosted-machine',
20
22
  '<%= config.bin %> harnesses leases list --json',
21
23
  ];
22
- static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
24
+ static flags = {
25
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
26
+ limit: core_1.Flags.integer({
27
+ description: 'How many to return, newest first (1-100; the server defaults to 50)',
28
+ min: 1,
29
+ max: 100,
30
+ }),
31
+ state: core_1.Flags.string({
32
+ description: 'Only this lease state — e.g. active, paused, terminated, failed, lost. `lost` is the one worth asking for: it means the platform stopped tracking the machine, and it is billed for the whole gap.',
33
+ }),
34
+ product: core_1.Flags.string({
35
+ description: 'Only this machine product',
36
+ options: ['hosted-machine', 'hosted-job-machine'],
37
+ }),
38
+ };
23
39
  async run() {
24
40
  this.requireAuth();
25
41
  const { flags } = await this.parse(HarnessesLeasesList);
26
42
  let response;
27
43
  try {
28
- response = await (0, harnesses_1.listHarnessLeases)();
44
+ response = await (0, harnesses_1.listHarnessLeases)({
45
+ limit: flags.limit,
46
+ state: flags.state,
47
+ machineProduct: flags.product,
48
+ });
29
49
  }
30
50
  catch (err) {
31
51
  this.handleApiError(err);
@@ -39,21 +59,48 @@ class HarnessesLeasesList extends base_command_1.BaseCommand {
39
59
  if (leases.length === 0) {
40
60
  // Say what empty MEANS. "No leases" reads as a failed query; the honest
41
61
  // reading is that nothing hosted is running, and nothing is being billed.
42
- this.log('No open leases nothing hosted is running, and nothing is accruing.');
62
+ // With a filter on, that reading would be a lie — the absence is about
63
+ // the filter, not the account.
64
+ const filters = [
65
+ flags.state ? `state ${flags.state}` : '',
66
+ flags.product ? `product ${flags.product}` : '',
67
+ ].filter(Boolean);
68
+ this.log(filters.length
69
+ ? `No leases matching ${filters.join(' and ')}. Without a filter this lists every lease, newest first.`
70
+ : 'No leases — nothing hosted is running, and nothing is accruing.');
43
71
  return;
44
72
  }
73
+ // The server caps at 100 and defaults to 50, so a full page is ambiguous:
74
+ // it may be all there is, or the newest of more. Say which rather than
75
+ // letting the reader assume.
76
+ if (!flags.json && leases.length === (flags.limit ?? 50)) {
77
+ this.log(`Showing the newest ${leases.length}. There may be more — raise \`--limit\` (max 100) or narrow with \`--state\`.`);
78
+ }
79
+ // Columns follow the payload, not a guess at it. PRODUCT is the one that
80
+ // tells a Hosted Machine lease from a Hosted Job Machine lease, and BILLING
81
+ // answers the question a lease list exists to answer: is this still
82
+ // charging me. There is no ENGINE or EXPIRES on a lease -- it carries no
83
+ // top-level runtime id and does not expire -- so those columns are gone
84
+ // rather than printed empty.
45
85
  (0, format_1.renderTable)(leases.map((lease) => ({
46
- id: String(lease.leaseId ?? lease.id ?? ''),
47
- product: String(lease.productId ?? ''),
48
- state: String(lease.state ?? ''),
49
- harness: String(lease.runtimeId ?? ''),
50
- expires: String(lease.expiresAt ?? ''),
86
+ id: (0, format_1.formatCell)(lease.id),
87
+ product: (0, format_1.formatCell)(lease.machineProduct),
88
+ name: (0, format_1.formatCell)(lease.displayName),
89
+ state: (0, format_1.formatCell)(lease.stateReason && lease.stateReason !== lease.state
90
+ ? `${lease.state} (${lease.stateReason})`
91
+ : lease.state),
92
+ billing: (0, format_1.formatCell)(lease.billing?.billableState),
93
+ updated: (0, format_1.localStamp)(lease.updatedAt ?? lease.leaseTimestamps?.lastStateChangeAt),
51
94
  })), [
52
95
  { key: 'id', header: 'LEASE' },
53
96
  { key: 'product', header: 'PRODUCT' },
54
- { key: 'state', header: 'STATE' },
55
- { key: 'harness', header: 'ENGINE' },
56
- { key: 'expires', header: 'EXPIRES' },
97
+ { key: 'name', header: 'NAME', maxWidth: 24 },
98
+ // The reason is the actionable half of a state, so it stays in the
99
+ // column rather than moving to `show` -- capped only so a long one
100
+ // cannot wrap the whole table on an 80-column terminal.
101
+ { key: 'state', header: 'STATE', maxWidth: 30 },
102
+ { key: 'billing', header: 'BILLING' },
103
+ { key: 'updated', header: 'UPDATED' },
57
104
  ], (line) => this.log(line));
58
105
  }
59
106
  }
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const core_1 = require("@oclif/core");
13
13
  const base_command_1 = require("../../../base-command");
14
14
  const harnesses_1 = require("../../../lib/harnesses");
15
+ const balance_1 = require("../../../lib/balance");
15
16
  class HarnessesLeasesShow extends base_command_1.BaseCommand {
16
17
  static description = 'Show one harness lease';
17
18
  static examples = ['<%= config.bin %> harnesses leases show <lease-id>'];
@@ -46,6 +47,8 @@ class HarnessesLeasesShow extends base_command_1.BaseCommand {
46
47
  const line = (label, value) => {
47
48
  if (value === undefined || value === null || value === '')
48
49
  return;
50
+ // 14 columns. A longer label does not wrap, it collides with its own
51
+ // value — keep new labels inside it.
49
52
  this.log(`${label.padEnd(14)}${String(value)}`);
50
53
  };
51
54
  // Only ever a PRIMITIVE reaches `line`. Falling back to the parent object
@@ -77,6 +80,39 @@ class HarnessesLeasesShow extends base_command_1.BaseCommand {
77
80
  // Money last, and only when the server sent it — an absent billing block
78
81
  // and a zero are different states.
79
82
  line('Billing:', nested(lease.billing, 'billableState'));
83
+ // The monthly cap, which is what actually refuses a Hosted Machine.
84
+ //
85
+ // The server has computed and sent these on every lease read all along and
86
+ // the web renders them; the CLI printed only `billableState`. So a refusal
87
+ // said "spending limit exceeded" and the only number a CLI user could find
88
+ // was `skrr balance show`'s DAILY cap, sitting at full headroom — which
89
+ // made the refusal look like it was about something else (OSK-7920).
90
+ //
91
+ // Read through `nested` like every other sub-object here: these arrive on a
92
+ // permissive payload type, and the helper is what keeps a non-primitive
93
+ // from rendering as `[object Object]`.
94
+ const cents = (key) => {
95
+ const value = nested(lease.billing, key);
96
+ // `formatMicros` takes micro-dollars; these are cents.
97
+ return typeof value === 'number' ? (0, balance_1.formatMicros)(value * 10_000) : undefined;
98
+ };
99
+ if (nested(lease.billing, 'spendStatusUnavailable') === true) {
100
+ // Distinguish "we could not read it" from "you have headroom". Admission
101
+ // fails CLOSED on an unreadable status, so silence would be the most
102
+ // misleading thing to print.
103
+ line('Monthly cap:', 'unavailable — the spend status could not be read');
104
+ }
105
+ else if (cents('monthlySpendLimitCents')) {
106
+ line('Monthly cap:', cents('monthlySpendLimitCents'));
107
+ line('Spent:', cents('currentMonthSpendCents'));
108
+ line('Open leases:', cents('openUsageEstimateCents'));
109
+ // Projected is what admission compares against the cap, so it is the
110
+ // number that decides whether the next turn is refused.
111
+ const over = nested(lease.billing, 'spendingLimitExceeded') === true;
112
+ const projected = cents('projectedCurrentMonthSpendCents');
113
+ line('Projected:', projected ? `${projected}${over ? ' — over the cap' : ''}` : undefined);
114
+ line('Month began:', nested(lease.billing, 'monthStartedAt'));
115
+ }
80
116
  this.log('');
81
117
  this.log('Full record: --json');
82
118
  }
@@ -14,6 +14,10 @@ export type HarnessRow = {
14
14
  status?: string;
15
15
  harnessLocation?: string;
16
16
  lastSeenAt?: string;
17
+ metadata?: {
18
+ machineProduct?: string;
19
+ source?: string;
20
+ };
17
21
  };
18
22
  export type HarnessSelection = {
19
23
  /** Rows to print, after every filter and the `--limit` slice. */
@@ -33,6 +37,20 @@ export type HarnessSelection = {
33
37
  * cloud products never register through a daemon. Treating the missing field
34
38
  * as cloud made a healthy, connected local harness disappear from the one CLI
35
39
  * command intended to discover it.
40
+ *
41
+ * "Cloud products never register through a daemon" is no longer true, and that
42
+ * is why the product identity is checked FIRST. The Hosted Machine boots a
43
+ * daemon inside itself and registers through the ordinary local path, so its
44
+ * row carries `harnessLocation: 'local'` on a machine whose `compute.ownership`
45
+ * is `oversky-managed` — and it therefore appeared in the DEFAULT, local-only
46
+ * listing. `--local`'s documented meaning ("Only show local (daemon-backed)
47
+ * harnesses … pass --no-local to include cloud/hosted rows") did not hold: a
48
+ * user filtering to their own machines was silently shown a cloud one, and the
49
+ * flag that separates them separated nothing (OSK-7636).
50
+ *
51
+ * Read-side rather than a stored-value migration on purpose: existing rows
52
+ * already carry the wrong `harnessLocation`, and rewriting it would have to
53
+ * move the registration identity filter with it.
36
54
  */
37
55
  export declare function isLocalHarnessRow(row: HarnessRow): boolean;
38
56
  /**
@@ -7,14 +7,44 @@ const core_1 = require("@oclif/core");
7
7
  const base_command_1 = require("../../base-command");
8
8
  const api_fetch_1 = require("../../lib/api-fetch");
9
9
  const format_1 = require("../../lib/format");
10
+ /**
11
+ * Products skrr provisions and owns. A row that says it is one of these is
12
+ * cloud, whatever `harnessLocation` reads.
13
+ */
14
+ const MANAGED_MACHINE_PRODUCTS = new Set(['hosted-machine', 'hosted-job-machine']);
15
+ const MANAGED_REGISTRATION_SOURCES = new Set([
16
+ 'hosted-machine',
17
+ 'hosted-job-machine',
18
+ 'cloud-coding',
19
+ ]);
10
20
  /**
11
21
  * Harness rows created before `harnessLocation` was introduced do not carry that
12
22
  * field. A daemon id is the durable discriminator for those legacy rows:
13
23
  * cloud products never register through a daemon. Treating the missing field
14
24
  * as cloud made a healthy, connected local harness disappear from the one CLI
15
25
  * command intended to discover it.
26
+ *
27
+ * "Cloud products never register through a daemon" is no longer true, and that
28
+ * is why the product identity is checked FIRST. The Hosted Machine boots a
29
+ * daemon inside itself and registers through the ordinary local path, so its
30
+ * row carries `harnessLocation: 'local'` on a machine whose `compute.ownership`
31
+ * is `oversky-managed` — and it therefore appeared in the DEFAULT, local-only
32
+ * listing. `--local`'s documented meaning ("Only show local (daemon-backed)
33
+ * harnesses … pass --no-local to include cloud/hosted rows") did not hold: a
34
+ * user filtering to their own machines was silently shown a cloud one, and the
35
+ * flag that separates them separated nothing (OSK-7636).
36
+ *
37
+ * Read-side rather than a stored-value migration on purpose: existing rows
38
+ * already carry the wrong `harnessLocation`, and rewriting it would have to
39
+ * move the registration identity filter with it.
16
40
  */
17
41
  function isLocalHarnessRow(row) {
42
+ const product = row.metadata?.machineProduct?.trim() ?? '';
43
+ if (MANAGED_MACHINE_PRODUCTS.has(product))
44
+ return false;
45
+ const source = row.metadata?.source?.trim() ?? '';
46
+ if (MANAGED_REGISTRATION_SOURCES.has(source))
47
+ return false;
18
48
  return row.harnessLocation === 'local' || (row.harnessLocation == null && Boolean(row.daemonId));
19
49
  }
20
50
  /**
@@ -4,6 +4,34 @@ const core_1 = require("@oclif/core");
4
4
  const base_command_1 = require("../../base-command");
5
5
  const format_1 = require("../../lib/format");
6
6
  const harnesses_1 = require("../../lib/harnesses");
7
+ /**
8
+ * How you actually start each product FROM HERE.
9
+ *
10
+ * The server sends a `userAction` (`start-hosted-machine`, …) and the table
11
+ * used to print it. That token is a launch-context assertion the API validates
12
+ * against `launchSurfaces` — it is not a command, and printing it in a column
13
+ * headed ACTION told a reader to run something that does not exist (OSK-7779).
14
+ *
15
+ * So the column names a real command instead. The map lives here rather than on
16
+ * the server because only the CLI knows its own command names; a product with
17
+ * no entry prints `-` and is called out below the table, which is the honest
18
+ * rendering of "you cannot start this from a terminal".
19
+ */
20
+ const START_COMMAND = {
21
+ 'byo-machine': 'harnesses install-token',
22
+ 'hosted-machine': 'machines hosted start',
23
+ // Not `machines …`: a Hosted Job Machine is one-shot compute that exists for
24
+ // the duration of one job, so the job is what you create.
25
+ //
26
+ // `code jobs run`, not `code handover`. Both start this product, and the
27
+ // difference is the one the finding was actually about: `handover` clones a
28
+ // repository, works it and opens a pull request, so it cannot start the bare
29
+ // one-shot the product describes — `"continuity": {"semantics": "one-shot"}`
30
+ // with `stateRetention: false` on filesystem, memory and processes. Pointing
31
+ // a reader who wants compute at the repository flow is a smaller version of
32
+ // pointing them at a verb nobody implements.
33
+ 'hosted-job-machine': 'code jobs run',
34
+ };
7
35
  /**
8
36
  * `availability` is a string on some rows and an object on others. The table
9
37
  * has to render both, and a `[object Object]` cell in a catalogue listing is
@@ -43,17 +71,34 @@ class HarnessesProducts extends base_command_1.BaseCommand {
43
71
  this.log('No harness products available.');
44
72
  return;
45
73
  }
74
+ const bin = this.config.bin;
46
75
  (0, format_1.renderTable)(products.map((p) => ({
47
76
  id: (0, format_1.formatCell)(p.id),
48
77
  label: (0, format_1.formatCell)(p.label),
49
- action: (0, format_1.formatCell)(p.userAction),
78
+ // What you are buying: `one-shot` and `true-resume` are the difference
79
+ // between compute that forgets everything and a workspace that resumes.
80
+ lifecycle: (0, format_1.formatCell)(p.lifecycleSemantics),
81
+ start: p.id && START_COMMAND[p.id] ? `${bin} ${START_COMMAND[p.id]}` : '-',
50
82
  availability: availabilityLabel(p),
51
83
  })), [
52
84
  { key: 'id', header: 'ID' },
53
- { key: 'label', header: 'LABEL', maxWidth: 30 },
54
- { key: 'action', header: 'ACTION' },
85
+ { key: 'label', header: 'LABEL', maxWidth: 26 },
86
+ { key: 'lifecycle', header: 'LIFECYCLE' },
87
+ { key: 'start', header: 'START' },
55
88
  { key: 'availability', header: 'AVAILABILITY' },
56
89
  ], (line) => this.log(line));
90
+ // A product the CLI cannot start is worth one line. Silence here is what
91
+ // made the old ACTION column misleading: `available` beside a verb nobody
92
+ // implements reads as "run this", and the reader learns otherwise by
93
+ // failing.
94
+ const unreachable = products.filter((p) => p.id && !START_COMMAND[p.id]);
95
+ if (unreachable.length > 0) {
96
+ this.log('');
97
+ for (const p of unreachable) {
98
+ const where = p.catalog?.launchSurfaces?.join(', ') || 'unknown';
99
+ this.log(`${p.id}: not startable from the CLI — launch surfaces: ${where}`);
100
+ }
101
+ }
57
102
  // The full record carries contract, billing, compute and lifecycle blocks.
58
103
  // Pointing at --json is cheaper than a second table nobody asked for.
59
104
  this.log('');
@@ -21,6 +21,22 @@ import { BaseCommand } from '../../base-command';
21
21
  * cannot: is this window going to run out before it resets, which days were
22
22
  * expensive, was last week's cap actually hit.
23
23
  */
24
+ /**
25
+ * WHY this window is empty — without asserting a cause we cannot know.
26
+ *
27
+ * OSK-7616 — this used to read "Runs started outside skrr still move the
28
+ * provider meter above, but cannot be priced here", which names one
29
+ * explanation as though it were the only one. In the reported case it was the
30
+ * wrong one: three Codex turns were started THROUGH skrr, completed, and were
31
+ * recorded at zero tokens because the harness reported an empty usage envelope.
32
+ * The reader was told, by the one screen whose job is answering "what is my
33
+ * agent costing me", that the cause was something they had not done.
34
+ *
35
+ * The second sentence is the one that was missing, and it is the actionable
36
+ * half: a run skrr saw and could not price is a reportable defect, not a
37
+ * property of the user's habits.
38
+ */
39
+ export declare function unpricedWindowLines(): string[];
24
40
  export default class HarnessesQuota extends BaseCommand {
25
41
  static description: string;
26
42
  static examples: string[];
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unpricedWindowLines = unpricedWindowLines;
3
4
  const core_1 = require("@oclif/core");
4
5
  const base_command_1 = require("../../base-command");
5
6
  const data_provider_1 = require("@skrr-ai/data-provider");
@@ -28,6 +29,28 @@ const quota_render_1 = require("../../lib/quota-render");
28
29
  * cannot: is this window going to run out before it resets, which days were
29
30
  * expensive, was last week's cap actually hit.
30
31
  */
32
+ /**
33
+ * WHY this window is empty — without asserting a cause we cannot know.
34
+ *
35
+ * OSK-7616 — this used to read "Runs started outside skrr still move the
36
+ * provider meter above, but cannot be priced here", which names one
37
+ * explanation as though it were the only one. In the reported case it was the
38
+ * wrong one: three Codex turns were started THROUGH skrr, completed, and were
39
+ * recorded at zero tokens because the harness reported an empty usage envelope.
40
+ * The reader was told, by the one screen whose job is answering "what is my
41
+ * agent costing me", that the cause was something they had not done.
42
+ *
43
+ * The second sentence is the one that was missing, and it is the actionable
44
+ * half: a run skrr saw and could not price is a reportable defect, not a
45
+ * property of the user's habits.
46
+ */
47
+ function unpricedWindowLines() {
48
+ return [
49
+ 'The provider meter above counts every run on this harness; this table counts only the runs skrr priced.',
50
+ 'A run started outside skrr is never priced here. A run started THROUGH skrr should be — if one is missing, its harness reported no token usage, which is a bug worth reporting.',
51
+ ' skrr harnesses usage # the runs skrr did see',
52
+ ];
53
+ }
31
54
  class HarnessesQuota extends base_command_1.BaseCommand {
32
55
  static description = 'Claude Code / Codex subscription quota over time — windows, pace, and daily spend';
33
56
  static examples = [
@@ -180,7 +203,9 @@ class HarnessesQuota extends base_command_1.BaseCommand {
180
203
  // is a real and common state that must not read as "you spent nothing".
181
204
  this.log('');
182
205
  this.log('No skrr-recorded token spend in this window.');
183
- this.log('Runs started outside skrr still move the provider meter above, but cannot be priced here.');
206
+ for (const line of unpricedWindowLines()) {
207
+ this.log(line);
208
+ }
184
209
  return;
185
210
  }
186
211
  this.log('');
@@ -122,6 +122,23 @@ class HarnessesUsage extends base_command_1.BaseCommand {
122
122
  this.log(`Total: ${count(totals.sessions)} sessions · ${usd(totals.totalCostUsd)} · ` +
123
123
  `${count(totals.inputTokens)} in / ${count(totals.outputTokens)} out / ` +
124
124
  `${count(totals.cachedInputTokens)} cached`);
125
+ // SAY WHICH PROVIDERS THAT TOTAL COVERS.
126
+ //
127
+ // It was presented as the account total and is not: the ingestion path is
128
+ // fed by the Claude account-telemetry reader, so five completed Codex
129
+ // runs on the SAME daemon as a reported Claude harness produced no row at
130
+ // all, and `grandTotals.sessions: 150` was the Claude-only figure with no
131
+ // qualifier. A user reading it to answer "what has my automation cost me"
132
+ // gets a confidently wrong answer, and nothing hints that entire
133
+ // providers are missing (OSK-7672). Naming the providers present is not
134
+ // the whole fix — the missing producers are — but it is the difference
135
+ // between an incomplete number and a wrong one.
136
+ const providers = [...new Set(rows.map((r) => r.provider).filter(Boolean))].sort();
137
+ if (!args.harness && providers.length > 0) {
138
+ this.log(`Covers ${providers.join(', ')} only — usage is recorded per provider, and a harness ` +
139
+ 'whose provider has no usage producer contributes nothing to this total. ' +
140
+ 'This is NOT necessarily your whole account.');
141
+ }
125
142
  }
126
143
  }
127
144
  }
@@ -4,7 +4,8 @@ import { BaseCommand } from '../../base-command';
4
4
  *
5
5
  * Gap analysis §3.3 (b). The CLI's 50 command groups were entirely pull-shaped,
6
6
  * with nothing that surfaced an agent-initiated message. The reply direction
7
- * already closed the loop (`skrr agents chat --conversation <id>` stamps
7
+ * already closed the loop (`skrr agents chat <agent-id> --conversation <id>
8
+ * --prompt "…"` stamps
8
9
  * `Message.intentId` and wakes the originating run) — so the missing half was
9
10
  * literally "tell me it happened", and its absence is what made use case 3's
10
11
  * "the agent messages me a couple of times like a person" a terminal dead end.
@@ -6,12 +6,14 @@ const data_provider_1 = require("@skrr-ai/data-provider");
6
6
  const base_command_1 = require("../../base-command");
7
7
  const inbox_1 = require("../../lib/inbox");
8
8
  const format_1 = require("../../lib/format");
9
+ const agentic_stream_1 = require("../../lib/agentic-stream");
9
10
  /**
10
11
  * `skrr inbox` / `skrr inbox --follow` — what have my agents said to me?
11
12
  *
12
13
  * Gap analysis §3.3 (b). The CLI's 50 command groups were entirely pull-shaped,
13
14
  * with nothing that surfaced an agent-initiated message. The reply direction
14
- * already closed the loop (`skrr agents chat --conversation <id>` stamps
15
+ * already closed the loop (`skrr agents chat <agent-id> --conversation <id>
16
+ * --prompt "…"` stamps
15
17
  * `Message.intentId` and wakes the originating run) — so the missing half was
16
18
  * literally "tell me it happened", and its absence is what made use case 3's
17
19
  * "the agent messages me a couple of times like a person" a terminal dead end.
@@ -77,19 +79,33 @@ class Inbox extends base_command_1.BaseCommand {
77
79
  }
78
80
  (0, format_1.renderTable)(selected.map((dm) => ({
79
81
  agent: dm.agentId,
82
+ // The reply command below needs this, and the row has carried it all
83
+ // along — it was simply never rendered, so the command printed under
84
+ // the table could not be completed from the table. `verbatim` because a
85
+ // clipped id is not an id (OSK-7621).
86
+ conversation: dm.conversationId,
80
87
  unread: String(dm.unreadCount),
81
88
  when: dm.lastMessageAt ? dm.lastMessageAt.slice(0, 19) : '-',
82
89
  preview: (dm.lastMessagePreview || '').replace(/\s+/g, ' ').trim(),
83
90
  })), [
84
91
  { key: 'agent', header: 'AGENT', verbatim: true },
92
+ { key: 'conversation', header: 'CONVERSATION', verbatim: true },
85
93
  { key: 'unread', header: 'UNREAD', maxWidth: 6 },
86
94
  { key: 'when', header: 'LAST', maxWidth: 20 },
87
- { key: 'preview', header: 'PREVIEW', maxWidth: 80 },
95
+ { key: 'preview', header: 'PREVIEW', maxWidth: 48 },
88
96
  ], (line) => this.log(line));
89
97
  const totalUnread = selected.reduce((sum, dm) => sum + dm.unreadCount, 0);
90
98
  if (totalUnread > 0) {
99
+ // The reply command must be RUNNABLE (OSK-7585/7594/7597). `agents chat`
100
+ // takes the agent as a required positional and `--prompt` as a required
101
+ // flag, so the argument-less form this used to print here (and, at the
102
+ // time those findings were filed, on every detach) exits with
103
+ // "Missing 1 required arg". A command a reader copies and cannot run is
104
+ // worse than no command: it costs them a round trip to learn the surface
105
+ // does not work the way it was just told them it does.
91
106
  this.log(`\n${totalUnread} unread. \`skrr inbox --follow\` to watch, ` +
92
- '`skrr agents chat --conversation <id>` to reply.');
107
+ '`skrr agents chat <agent-id> --conversation <id> --prompt "<your reply>"` to reply ' +
108
+ '(the AGENT and CONVERSATION columns above carry both ids).');
93
109
  }
94
110
  }
95
111
  /** The terminal projection reads the same Notification rows as the web task inbox. */
@@ -192,7 +208,14 @@ class Inbox extends base_command_1.BaseCommand {
192
208
  cursors.set(dm.conversationId, newest);
193
209
  }
194
210
  };
195
- const wsURL = this.cliConfig.baseURL.replace(/\/+$/, '');
211
+ // Hoist a web apex to its `api.` realtime host, exactly as the run-stream
212
+ // sibling does. Using `baseURL` raw sends the upgrade to the apex, which is
213
+ // CloudFront-fronted: it cannot carry a WebSocket, and its WAF refuses a
214
+ // handshake that sends no `User-Agent` — which `ws` never does. Measured:
215
+ // an upgrade to `skrr.ai` answers `403 Request blocked`, the same request
216
+ // to `api.skrr.ai` answers `101`. Nothing errors at the call site; the
217
+ // socket simply never connects, which reads as a network problem.
218
+ const wsURL = (0, agentic_stream_1.toWebSocketOrigin)(this.cliConfig.baseURL);
196
219
  const socket = (0, socket_io_client_1.io)(wsURL, {
197
220
  path: '/ws/agentic',
198
221
  auth: {
@@ -134,9 +134,12 @@ class Login extends base_command_1.BaseCommand {
134
134
  // terminal through the daemon broker, which is the case that matters
135
135
  // most on a headless box, and a confirmation would break it.
136
136
  const profile = (0, keychain_1.getActiveProfile)();
137
+ const isolationGuidance = profile === 'default'
138
+ ? 'Sign in with `--profile <name>` instead to keep both.'
139
+ : `Only the named "${profile}" profile changes; the default and all other profiles remain untouched.`;
137
140
  this.warn(`Pointing profile "${profile}" at ${(0, config_1.describeDeployment)(nextBaseURL)}. ` +
138
141
  `Its ${(0, config_1.describeDeployment)(previousBaseURL)} session will be replaced — ` +
139
- `sign in with \`--profile <name>\` instead to keep both.`);
142
+ isolationGuidance);
140
143
  undoBaseURL = () => {
141
144
  // Re-read rather than reusing `existing`: the flow may have persisted
142
145
  // other fields (cliId, migration markers) before failing, and this
@@ -8,6 +8,7 @@ export default class MachinesList extends BaseCommand {
8
8
  static examples: string[];
9
9
  static flags: {
10
10
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ all: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
12
  };
12
13
  run(): Promise<void>;
13
14
  }
@@ -14,7 +14,19 @@ class MachinesList extends base_command_1.BaseCommand {
14
14
  '<%= config.bin %> machines list',
15
15
  '<%= config.bin %> machines list --json',
16
16
  ];
17
- static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
17
+ static flags = {
18
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
19
+ // `harnesses list` already learned this: "Offline rows accumulate forever,
20
+ // so they are hidden by default." The same reasoning was never applied
21
+ // here, and this listing returned 252 rows for one account — every
22
+ // ephemeral sandbox ever provisioned, months back, all rendered `offline`,
23
+ // with the one live machine at the top and a wall of dead ones after it
24
+ // (OSK-7639). A list exists so someone can pick a row.
25
+ all: core_1.Flags.boolean({
26
+ description: 'Include machines that are offline or terminated. Off by default — dead sandboxes ' +
27
+ 'accumulate forever and bury the ones you can use.',
28
+ }),
29
+ };
18
30
  async run() {
19
31
  this.requireAuth();
20
32
  const { flags } = await this.parse(MachinesList);
@@ -33,10 +45,21 @@ class MachinesList extends base_command_1.BaseCommand {
33
45
  this.log('No machines. Install a daemon with `skrr harnesses install-token`.');
34
46
  return;
35
47
  }
36
- (0, format_1.renderTable)(machines.map((m) => ({
37
- id: (0, format_1.formatCell)(m.id).slice(0, 20),
48
+ const all = machines;
49
+ const shown = flags.all ? all : all.filter((m) => (0, machines_1.isReachableMachine)(m));
50
+ if (shown.length === 0) {
51
+ this.log(`No machine is reachable right now. ${all.length} offline/terminated — see them with --all.`);
52
+ return;
53
+ }
54
+ (0, format_1.renderTable)(shown.map((m) => ({
55
+ // Printed in full. A hard 20-character slice with no ellipsis made
56
+ // `machine:03548116-d36` read like a complete id and it is not — and
57
+ // two different hosted machines rendered as byte-identical rows,
58
+ // so the one column a list exists to be picked from could not
59
+ // distinguish them (OSK-7621).
60
+ id: (0, format_1.formatCell)(m.id),
38
61
  name: (0, format_1.formatCell)(m.preferences?.name || m.name),
39
- status: (0, format_1.formatCell)(m.status),
62
+ status: (0, format_1.formatCell)((0, machines_1.machineStateLabel)(m)),
40
63
  // Both numbers matter: "2/7" is a machine that is up with most of its
41
64
  // harnesses signed out, which reads nothing like "7/7". The
42
65
  // denominator counts LIVE rows, matching the population
@@ -48,13 +71,18 @@ class MachinesList extends base_command_1.BaseCommand {
48
71
  ? new Date(m.lastSeenAt).toISOString().slice(0, 16).replace('T', ' ')
49
72
  : '-',
50
73
  })), [
51
- { key: 'id', header: 'ID' },
74
+ { key: 'id', header: 'ID', verbatim: true },
52
75
  { key: 'name', header: 'NAME', maxWidth: 28 },
53
76
  { key: 'status', header: 'STATUS' },
54
77
  { key: 'harnesses', header: 'ONLINE' },
55
78
  { key: 'platform', header: 'PLATFORM' },
56
79
  { key: 'seen', header: 'LAST SEEN' },
57
80
  ], (line) => this.log(line));
81
+ const hidden = all.length - shown.length;
82
+ if (hidden > 0) {
83
+ this.log('');
84
+ this.log(`${hidden} offline or terminated machine(s) hidden — pass --all to include them.`);
85
+ }
58
86
  }
59
87
  }
60
88
  exports.default = MachinesList;