@skrr-ai/cli 0.1.37 → 0.1.39

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 (177) hide show
  1. package/bin/dev-fallback.js +175 -4
  2. package/dist/base-command.js +46 -2
  3. package/dist/commands/agents/actions/create.js +18 -7
  4. package/dist/commands/agents/actions/delete.d.ts +1 -0
  5. package/dist/commands/agents/actions/delete.js +7 -0
  6. package/dist/commands/agents/actions/update.js +5 -3
  7. package/dist/commands/agents/api-actions/add.js +5 -2
  8. package/dist/commands/agents/api-actions/index.d.ts +11 -0
  9. package/dist/commands/agents/api-actions/index.js +27 -1
  10. package/dist/commands/agents/avatar/default/index.d.ts +11 -0
  11. package/dist/commands/agents/avatar/default/index.js +17 -0
  12. package/dist/commands/agents/avatar/default/set.d.ts +2 -1
  13. package/dist/commands/agents/avatar/default/set.js +32 -7
  14. package/dist/commands/agents/avatar/upload.js +7 -1
  15. package/dist/commands/agents/categories/delete.js +8 -0
  16. package/dist/commands/agents/categories/index.d.ts +21 -5
  17. package/dist/commands/agents/categories/index.js +68 -15
  18. package/dist/commands/agents/chat.d.ts +21 -0
  19. package/dist/commands/agents/chat.js +94 -7
  20. package/dist/commands/agents/create.js +11 -4
  21. package/dist/commands/agents/list.d.ts +15 -0
  22. package/dist/commands/agents/list.js +31 -1
  23. package/dist/commands/agents/scene-background/upload.d.ts +30 -0
  24. package/dist/commands/agents/scene-background/upload.js +97 -0
  25. package/dist/commands/agents/show.js +13 -0
  26. package/dist/commands/agents/tools/calls.d.ts +23 -5
  27. package/dist/commands/agents/tools/calls.js +81 -18
  28. package/dist/commands/agents/update.js +15 -5
  29. package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
  30. package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
  31. package/dist/commands/convos/messages.d.ts +13 -0
  32. package/dist/commands/convos/messages.js +40 -16
  33. package/dist/commands/daemon/index.js +8 -1
  34. package/dist/commands/daemon/logs.d.ts +26 -0
  35. package/dist/commands/daemon/logs.js +56 -0
  36. package/dist/commands/daemon/uninstall.js +1 -1
  37. package/dist/commands/followups/act.js +3 -2
  38. package/dist/commands/followups/cancel.d.ts +6 -3
  39. package/dist/commands/followups/cancel.js +16 -6
  40. package/dist/commands/followups/observability.d.ts +23 -0
  41. package/dist/commands/followups/observability.js +114 -0
  42. package/dist/commands/followups/reschedule.js +1 -1
  43. package/dist/commands/followups/resolve.js +3 -3
  44. package/dist/commands/followups/show.js +2 -1
  45. package/dist/commands/followups/watch.js +9 -5
  46. package/dist/commands/harnesses/forget.js +28 -0
  47. package/dist/commands/harnesses/install.d.ts +41 -0
  48. package/dist/commands/harnesses/install.js +165 -0
  49. package/dist/commands/harnesses/installers.d.ts +45 -0
  50. package/dist/commands/harnesses/installers.js +155 -0
  51. package/dist/commands/harnesses/list.d.ts +82 -1
  52. package/dist/commands/harnesses/list.js +104 -12
  53. package/dist/commands/harnesses/models.js +10 -1
  54. package/dist/commands/harnesses/show.d.ts +15 -0
  55. package/dist/commands/harnesses/show.js +44 -3
  56. package/dist/commands/harnesses/update.js +1 -1
  57. package/dist/commands/harnesses/usage.d.ts +25 -0
  58. package/dist/commands/harnesses/usage.js +24 -2
  59. package/dist/commands/instructions/status.js +13 -1
  60. package/dist/commands/machines/dedicated/catalog.js +4 -15
  61. package/dist/commands/machines/dedicated/create.d.ts +8 -0
  62. package/dist/commands/machines/dedicated/create.js +63 -2
  63. package/dist/commands/machines/dedicated/destroy.js +19 -5
  64. package/dist/commands/machines/dedicated/exec.js +3 -1
  65. package/dist/commands/machines/dedicated/list.js +9 -6
  66. package/dist/commands/machines/dedicated/price-book.d.ts +17 -0
  67. package/dist/commands/machines/dedicated/price-book.js +35 -0
  68. package/dist/commands/machines/hosted/list.js +12 -0
  69. package/dist/commands/machines/list.js +11 -0
  70. package/dist/commands/machines/show.d.ts +1 -0
  71. package/dist/commands/machines/show.js +40 -11
  72. package/dist/commands/profiles/list.d.ts +23 -0
  73. package/dist/commands/profiles/list.js +96 -0
  74. package/dist/commands/search/skills.d.ts +11 -1
  75. package/dist/commands/search/skills.js +26 -8
  76. package/dist/commands/skills/details.d.ts +2 -0
  77. package/dist/commands/skills/details.js +24 -1
  78. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  79. package/dist/commands/skills/import-as-actions.js +175 -0
  80. package/dist/commands/skills/install.d.ts +2 -0
  81. package/dist/commands/skills/install.js +24 -1
  82. package/dist/commands/skills/invocable.js +6 -2
  83. package/dist/commands/skills/local.d.ts +40 -0
  84. package/dist/commands/skills/local.js +160 -0
  85. package/dist/commands/skills/search.d.ts +11 -1
  86. package/dist/commands/skills/search.js +29 -8
  87. package/dist/commands/spaces/actions/add.js +23 -5
  88. package/dist/commands/spaces/actions/available.js +3 -0
  89. package/dist/commands/spaces/actions/list.d.ts +5 -0
  90. package/dist/commands/spaces/actions/list.js +49 -3
  91. package/dist/commands/spaces/actions/run.d.ts +28 -0
  92. package/dist/commands/spaces/actions/run.js +146 -0
  93. package/dist/commands/spaces/actions/update.js +27 -5
  94. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  95. package/dist/commands/spaces/label-summaries.js +64 -0
  96. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  97. package/dist/commands/spaces/task-rollups.js +15 -4
  98. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  99. package/dist/commands/tasks/actions/attach.js +32 -7
  100. package/dist/commands/tasks/complete.js +13 -0
  101. package/dist/commands/tasks/create.js +1 -1
  102. package/dist/commands/tasks/events/append.js +29 -6
  103. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  104. package/dist/commands/tasks/expectations/assess.js +43 -5
  105. package/dist/commands/tasks/expectations.js +77 -16
  106. package/dist/commands/tasks/follow-up.js +2 -2
  107. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  108. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  109. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  110. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  111. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  112. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  113. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  114. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  115. package/dist/commands/tasks/list.js +16 -9
  116. package/dist/commands/tasks/move.js +11 -13
  117. package/dist/commands/tasks/review-queue.d.ts +15 -0
  118. package/dist/commands/tasks/review-queue.js +180 -31
  119. package/dist/commands/tasks/show.d.ts +11 -0
  120. package/dist/commands/tasks/show.js +105 -12
  121. package/dist/commands/tasks/trust.js +7 -2
  122. package/dist/commands/tasks/update.js +46 -10
  123. package/dist/commands/tasks/updates/add.js +1 -1
  124. package/dist/commands/token/list.d.ts +22 -0
  125. package/dist/commands/token/list.js +68 -0
  126. package/dist/commands/token/revoke.d.ts +23 -0
  127. package/dist/commands/token/revoke.js +53 -0
  128. package/dist/lib/agent-actions.d.ts +57 -2
  129. package/dist/lib/agent-actions.js +113 -21
  130. package/dist/lib/agent-pin-mute.d.ts +10 -5
  131. package/dist/lib/agent-pin-mute.js +17 -4
  132. package/dist/lib/agent-visual-refs.d.ts +27 -0
  133. package/dist/lib/agent-visual-refs.js +127 -2
  134. package/dist/lib/agentic-stream.d.ts +6 -0
  135. package/dist/lib/agentic-stream.js +34 -3
  136. package/dist/lib/assignee-resolver.d.ts +1 -1
  137. package/dist/lib/assignee-resolver.js +8 -0
  138. package/dist/lib/bulk-task-targets.d.ts +12 -0
  139. package/dist/lib/bulk-task-targets.js +34 -1
  140. package/dist/lib/cli-installers.d.ts +130 -0
  141. package/dist/lib/cli-installers.js +145 -0
  142. package/dist/lib/command-miss.js +55 -7
  143. package/dist/lib/dedicated-lease-command.d.ts +16 -1
  144. package/dist/lib/dedicated-lease-command.js +26 -3
  145. package/dist/lib/dedicated-machines.d.ts +135 -1
  146. package/dist/lib/dedicated-machines.js +307 -3
  147. package/dist/lib/failed-lookup.d.ts +3 -0
  148. package/dist/lib/failed-lookup.js +40 -0
  149. package/dist/lib/first-party-harness-agent.js +9 -2
  150. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  151. package/dist/lib/first-party-harness-doctor.js +126 -27
  152. package/dist/lib/followups.d.ts +14 -6
  153. package/dist/lib/followups.js +31 -6
  154. package/dist/lib/harnesses.d.ts +8 -0
  155. package/dist/lib/instruction-input.d.ts +19 -1
  156. package/dist/lib/instruction-input.js +52 -1
  157. package/dist/lib/label-scope.d.ts +15 -0
  158. package/dist/lib/label-scope.js +25 -5
  159. package/dist/lib/local-skills.d.ts +98 -0
  160. package/dist/lib/local-skills.js +195 -0
  161. package/dist/lib/machines.d.ts +10 -10
  162. package/dist/lib/machines.js +23 -1
  163. package/dist/lib/search-query.d.ts +19 -0
  164. package/dist/lib/search-query.js +35 -0
  165. package/dist/lib/space-custom-actions.d.ts +72 -0
  166. package/dist/lib/space-custom-actions.js +117 -0
  167. package/dist/lib/space-resolver.d.ts +1 -21
  168. package/dist/lib/space-resolver.js +19 -10
  169. package/dist/lib/task-closure.d.ts +3 -2
  170. package/dist/lib/task-closure.js +18 -20
  171. package/dist/lib/task-lifecycle-output.js +117 -9
  172. package/dist/lib/task-ref-resolver.d.ts +1 -17
  173. package/dist/lib/task-ref-resolver.js +18 -8
  174. package/dist/lib/tasks.js +9 -3
  175. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  176. package/oclif.manifest.json +15505 -14460
  177. package/package.json +9 -3
@@ -221,6 +221,46 @@ export declare function dedicatedRuntimeCreateInput(input: {
221
221
  */
222
222
  export declare function isDedicatedRepositoryRef(value: string): boolean;
223
223
  export declare function createDedicatedRuntime(input: CreateDedicatedRuntimeInput): Promise<DedicatedRuntimeResponse>;
224
+ /** One offered tier as the operator price book checks it. */
225
+ export interface DedicatedPriceBookPresetCheck {
226
+ sizePreset: string;
227
+ customerRateCentsPerHour: number | null;
228
+ providerCostCentsPerHour: number | null;
229
+ grossMarginBps: number | null;
230
+ priced: boolean;
231
+ providerCostRecorded: boolean;
232
+ marginTargetMet: boolean;
233
+ }
234
+ /** `GET /api/machines/dedicated/price-book` — admin only. */
235
+ export interface DedicatedOperatorPriceBook {
236
+ timestamp?: string;
237
+ availability: 'available' | 'disabled';
238
+ disabledReason: string | null;
239
+ configError: string | null;
240
+ priceBook: {
241
+ required: boolean;
242
+ approved: boolean;
243
+ version: string | null;
244
+ pricingPolicyRef: string | null;
245
+ targetGrossMarginBps: number | null;
246
+ missingReason: string | null;
247
+ presetChecks: DedicatedPriceBookPresetCheck[];
248
+ };
249
+ sizeAvailability: Array<{
250
+ id: string;
251
+ offered: boolean;
252
+ withheldReason: string | null;
253
+ }>;
254
+ }
255
+ /**
256
+ * The Dedicated Runtime price book as an operator reads it: which version and
257
+ * which pricing policy, and each tier's rate, cost and margin. The customer
258
+ * catalog carries none of the policy or cost facts, and the admin plane that
259
+ * also serves this is not deployed in production (OSK-9562).
260
+ */
261
+ export declare function getDedicatedOperatorPriceBook(): Promise<DedicatedOperatorPriceBook>;
262
+ /** Human lines for `machines dedicated price-book`. Pure, so it can be pinned. */
263
+ export declare function dedicatedOperatorPriceBookLines(read: DedicatedOperatorPriceBook): string[];
224
264
  export declare function listDedicatedRuntimes(options?: {
225
265
  limit?: number;
226
266
  state?: string;
@@ -389,10 +429,57 @@ export type DedicatedCopyPlan = {
389
429
  export declare function parseDedicatedCopyEndpoints(source: string, destination: string): DedicatedCopyPlan;
390
430
  export declare function dedicatedLeaseId(lease: DedicatedLease | HarnessLease | undefined | null): string;
391
431
  export declare function dedicatedLeaseState(lease: DedicatedLease | HarnessLease | undefined | null): string;
432
+ /**
433
+ * What `destroy` should say when it keeps the workspace — or null when it does
434
+ * not.
435
+ *
436
+ * A destroy under retention class standard/extended archives the lease and keeps
437
+ * its workspace volume, which keeps billing storage until it is purged. Nothing
438
+ * said so at the moment it mattered: `destroy` printed the new state and exited,
439
+ * `list` showed `archived` in the same column as `terminated`, and the one way to
440
+ * find out was the bill (OSK-9593).
441
+ */
442
+ export declare function dedicatedDestroyRetentionNote(priorLease: DedicatedLease | undefined | null, opts: {
443
+ purge: boolean;
444
+ retain: boolean;
445
+ bin: string;
446
+ }): string | null;
447
+ /**
448
+ * Month-to-date Dedicated Runtime spend for the scope a `list` shows.
449
+ *
450
+ * `billing.currentMonthSpendCents` is the SCOPE's spend for the product — the
451
+ * server aggregates by owner scope, not by lease — so every row carried the same
452
+ * figure, and a column headed MONTH/CAP on per-machine rows read as that
453
+ * machine's cost when it was every machine's (OSK-9576). It is said once instead.
454
+ */
455
+ export declare function dedicatedScopeMonthSpendCents(leases: Array<DedicatedLease | undefined | null>): number | null;
456
+ /**
457
+ * A lease's own cap, marked when the scope's spend has reached it. The mark is
458
+ * left off a terminal lease: its cap can no longer stop anything, and
459
+ * "$11.06/$10.00 OVER" on a machine terminated days ago read as that machine
460
+ * having overrun.
461
+ */
462
+ export declare function dedicatedLeaseCapCell(lease: DedicatedLease | undefined | null): string;
463
+ /**
464
+ * A lease's STATUS cell. `archived` and `terminated` are both "gone" to read, and
465
+ * only one of them is still charging — so a lease billing retained storage says
466
+ * so (OSK-9593).
467
+ */
468
+ export declare function dedicatedLeaseStatusCell(lease: DedicatedLease | undefined | null): string;
392
469
  export declare function dedicatedLeaseDisplayName(lease: DedicatedLease | HarnessLease | undefined | null): string;
393
470
  /** `$12.34` — cents are what the API speaks; dollars are what a person reads. */
394
471
  export declare function formatUsdCents(cents: number | null | undefined): string;
395
- /** `this month $1.23 of $500.00 cap`, or why that sentence cannot be written. */
472
+ /**
473
+ * `$1.23 this month for machines of this type · $500.00 cap for this machine`,
474
+ * or why that sentence cannot be written.
475
+ *
476
+ * The two figures are different scopes and the sentence has to say so. The spend
477
+ * is a shared pot the server computes for (owner, team, machineProduct), so every
478
+ * lease of the same product reports the same number — a machine created minutes
479
+ * ago with no active seconds still reports the month's total. The cap is this
480
+ * lease's own. Printed as "$1.23 this month of $500.00 cap" the only available
481
+ * reading was that this machine had spent it.
482
+ */
396
483
  export declare function dedicatedSpendSummary(lease: DedicatedLease): string;
397
484
  /**
398
485
  * The human rendering of `machines dedicated show`, as lines.
@@ -461,12 +548,15 @@ interface CatalogSize {
461
548
  vcpus?: number;
462
549
  memoryGiB?: number;
463
550
  accelerator?: string;
551
+ rateCentsPerHour?: number | null;
464
552
  }
465
553
  interface CatalogPriceLine {
466
554
  label?: string;
467
555
  display?: string;
468
556
  amountCents?: number | null;
469
557
  cadence?: string;
558
+ /** `charge` is billed, `hold` is reserved at start, `limit` is a cap. Absent means `charge`. */
559
+ kind?: 'charge' | 'hold' | 'limit';
470
560
  }
471
561
  export interface DedicatedCatalog {
472
562
  availability: string;
@@ -480,9 +570,15 @@ export interface DedicatedCatalog {
480
570
  };
481
571
  retentionClasses: string[];
482
572
  backupProfiles: string[];
573
+ /** Server-declared: what create applies when a flag is omitted. */
574
+ defaultSizePreset?: string;
575
+ defaultRetentionClass?: string;
576
+ defaultBackupProfile?: string;
483
577
  defaultMonthlySpendingLimitCents?: number | null;
484
578
  maxUserMonthlySpendingLimitCents?: number | null;
485
579
  priceLines: CatalogPriceLine[];
580
+ /** The price book the quoted prices come from — what a later pricing question cites. */
581
+ priceBookVersion?: string;
486
582
  headline?: string;
487
583
  }
488
584
  /**
@@ -493,6 +589,44 @@ export interface DedicatedCatalog {
493
589
  * gated by server flags — so this is where a caller learns which ones exist.
494
590
  */
495
591
  export declare function dedicatedCatalogFromProducts(payload: unknown): DedicatedCatalog | null;
592
+ /**
593
+ * The catalog's size rows: spec, the size's own hourly price, and which one
594
+ * `create` uses by default. The rows used to carry spec alone, sorted by name,
595
+ * so the $1.40/hr tier came first with nothing saying what it cost or that
596
+ * $0.65/hr standard was the default; a reader had to join two blocks by name.
597
+ */
598
+ export declare function dedicatedCatalogSizeLines(catalog: DedicatedCatalog): string[];
599
+ export interface DedicatedCreateChoices {
600
+ name: string;
601
+ sizePreset: string;
602
+ storageGb?: number;
603
+ retentionClass?: string;
604
+ spendingLimitCents?: number;
605
+ }
606
+ /**
607
+ * What a create is about to buy, in the words and numbers the catalog uses.
608
+ *
609
+ * `create` provisioned a billable machine the moment it ran — no confirmation,
610
+ * no dry run, and every omitted flag silently took a paid default — while
611
+ * `destroy` asked first. A loop probing size validation bought two real
612
+ * machines, one of them $1.40/hr (OSK-9592). This is what `create` now shows
613
+ * before it asks, and what `--dry-run` prints instead of creating.
614
+ *
615
+ * Every default comes from the catalog, never from this file: a CLI that
616
+ * restates a default it does not own is how `--storage-gb` once billed twice the
617
+ * product's disk. A value the catalog does not declare is shown as "server
618
+ * default" rather than guessed.
619
+ */
620
+ export declare function dedicatedCreateSummary(catalog: DedicatedCatalog | null, choices: DedicatedCreateChoices): string[];
621
+ /**
622
+ * The catalog's money lines, grouped by what each amount IS.
623
+ *
624
+ * A start hold and a monthly cap used to print under "Prices" beside the hourly
625
+ * rates, so starting a machine read as a 33c charge and "$500 default monthly
626
+ * cap" read as a bill (OSK-9536). A line with no `kind` comes from a server that
627
+ * predates it and is shown the way every line used to be: as a charge.
628
+ */
629
+ export declare function dedicatedCatalogPriceLineOutput(priceLines: CatalogPriceLine[], priceBookVersion?: string): string[];
496
630
  /**
497
631
  * The product refusal code the server sent (`DEDICATED_RUNTIME_NOT_ASSIGNABLE`,
498
632
  * …), for `this.error(message, { code })`. Without it a `--json` caller got the
@@ -5,6 +5,8 @@ exports.describeDedicatedImageStanding = describeDedicatedImageStanding;
5
5
  exports.dedicatedRuntimeCreateInput = dedicatedRuntimeCreateInput;
6
6
  exports.isDedicatedRepositoryRef = isDedicatedRepositoryRef;
7
7
  exports.createDedicatedRuntime = createDedicatedRuntime;
8
+ exports.getDedicatedOperatorPriceBook = getDedicatedOperatorPriceBook;
9
+ exports.dedicatedOperatorPriceBookLines = dedicatedOperatorPriceBookLines;
8
10
  exports.listDedicatedRuntimes = listDedicatedRuntimes;
9
11
  exports.getDedicatedRuntime = getDedicatedRuntime;
10
12
  exports.performDedicatedLeaseAction = performDedicatedLeaseAction;
@@ -21,6 +23,10 @@ exports.writeDedicatedRuntimeFileChunk = writeDedicatedRuntimeFileChunk;
21
23
  exports.parseDedicatedCopyEndpoints = parseDedicatedCopyEndpoints;
22
24
  exports.dedicatedLeaseId = dedicatedLeaseId;
23
25
  exports.dedicatedLeaseState = dedicatedLeaseState;
26
+ exports.dedicatedDestroyRetentionNote = dedicatedDestroyRetentionNote;
27
+ exports.dedicatedScopeMonthSpendCents = dedicatedScopeMonthSpendCents;
28
+ exports.dedicatedLeaseCapCell = dedicatedLeaseCapCell;
29
+ exports.dedicatedLeaseStatusCell = dedicatedLeaseStatusCell;
24
30
  exports.dedicatedLeaseDisplayName = dedicatedLeaseDisplayName;
25
31
  exports.formatUsdCents = formatUsdCents;
26
32
  exports.dedicatedSpendSummary = dedicatedSpendSummary;
@@ -33,6 +39,9 @@ exports.dedicatedLeaseIdForDaemon = dedicatedLeaseIdForDaemon;
33
39
  exports.dedicatedTerminalHarness = dedicatedTerminalHarness;
34
40
  exports.selectDedicatedAttachHarness = selectDedicatedAttachHarness;
35
41
  exports.dedicatedCatalogFromProducts = dedicatedCatalogFromProducts;
42
+ exports.dedicatedCatalogSizeLines = dedicatedCatalogSizeLines;
43
+ exports.dedicatedCreateSummary = dedicatedCreateSummary;
44
+ exports.dedicatedCatalogPriceLineOutput = dedicatedCatalogPriceLineOutput;
36
45
  exports.dedicatedRuntimeApiErrorCode = dedicatedRuntimeApiErrorCode;
37
46
  exports.dedicatedRuntimeApiErrorDetails = dedicatedRuntimeApiErrorDetails;
38
47
  exports.formatDedicatedRuntimeApiError = formatDedicatedRuntimeApiError;
@@ -150,6 +159,59 @@ async function createDedicatedRuntime(input) {
150
159
  const { requestId, workspaceId, ...body } = input;
151
160
  return (0, api_fetch_1.apiFetch)(withDedicatedScope('/api/machines/dedicated', { workspaceId }), { method: 'POST', body, headers: { 'Idempotency-Key': requestId } });
152
161
  }
162
+ /**
163
+ * The Dedicated Runtime price book as an operator reads it: which version and
164
+ * which pricing policy, and each tier's rate, cost and margin. The customer
165
+ * catalog carries none of the policy or cost facts, and the admin plane that
166
+ * also serves this is not deployed in production (OSK-9562).
167
+ */
168
+ async function getDedicatedOperatorPriceBook() {
169
+ return (0, api_fetch_1.apiFetch)('/api/machines/dedicated/price-book');
170
+ }
171
+ function formatBps(bps) {
172
+ return bps === null ? 'unknown' : `${(bps / 100).toFixed(1)}%`;
173
+ }
174
+ function formatRateCents(cents) {
175
+ return cents === null ? 'none' : `$${(cents / 100).toFixed(2)}/hr`;
176
+ }
177
+ /** Human lines for `machines dedicated price-book`. Pure, so it can be pinned. */
178
+ function dedicatedOperatorPriceBookLines(read) {
179
+ const book = read.priceBook;
180
+ const lines = [
181
+ `Dedicated Runtime price book — ${book.approved ? 'approved' : 'NOT approved'}${book.required ? '' : ' (not required here)'}`,
182
+ ` version ${book.version ?? 'not recorded'}`,
183
+ ` policy ${book.pricingPolicyRef ?? 'not recorded'}`,
184
+ ` target margin ${formatBps(book.targetGrossMarginBps)}`,
185
+ ];
186
+ if (!book.approved && book.missingReason)
187
+ lines.push(` why ${book.missingReason}`);
188
+ if (read.availability === 'disabled' && read.disabledReason) {
189
+ lines.push(` product disabled: ${read.disabledReason}`);
190
+ }
191
+ if (read.configError)
192
+ lines.push(` config unreadable: ${read.configError}`);
193
+ lines.push('', 'Offered tiers');
194
+ if (book.presetChecks.length === 0)
195
+ lines.push(' none');
196
+ for (const check of book.presetChecks) {
197
+ const verdict = !check.priced
198
+ ? 'UNPRICED'
199
+ : !check.providerCostRecorded
200
+ ? 'UNCOSTED'
201
+ : check.marginTargetMet
202
+ ? 'meets target'
203
+ : 'BELOW TARGET';
204
+ lines.push(` ${check.sizePreset.padEnd(12)} rate ${formatRateCents(check.customerRateCentsPerHour)} · cost ${formatRateCents(check.providerCostCentsPerHour)} · margin ${formatBps(check.grossMarginBps)} · ${verdict}`);
205
+ }
206
+ const withheld = read.sizeAvailability.filter((entry) => !entry.offered);
207
+ if (withheld.length > 0) {
208
+ lines.push('', 'Withheld tiers');
209
+ for (const entry of withheld) {
210
+ lines.push(` ${entry.id.padEnd(12)} ${entry.withheldReason ?? 'no reason recorded'}`);
211
+ }
212
+ }
213
+ return lines;
214
+ }
153
215
  async function listDedicatedRuntimes(options = {}) {
154
216
  const query = new URLSearchParams();
155
217
  if (options.limit)
@@ -300,6 +362,71 @@ function dedicatedLeaseId(lease) {
300
362
  function dedicatedLeaseState(lease) {
301
363
  return String(lease?.state ?? 'unknown');
302
364
  }
365
+ /**
366
+ * What `destroy` should say when it keeps the workspace — or null when it does
367
+ * not.
368
+ *
369
+ * A destroy under retention class standard/extended archives the lease and keeps
370
+ * its workspace volume, which keeps billing storage until it is purged. Nothing
371
+ * said so at the moment it mattered: `destroy` printed the new state and exited,
372
+ * `list` showed `archived` in the same column as `terminated`, and the one way to
373
+ * find out was the bill (OSK-9593).
374
+ */
375
+ function dedicatedDestroyRetentionNote(priorLease, opts) {
376
+ if (opts.purge || !priorLease)
377
+ return null;
378
+ if (priorLease.state === 'archived')
379
+ return null; // destroying an archived lease purges it
380
+ const retentionClass = String(priorLease.storage?.retentionClass || 'none');
381
+ if (!opts.retain && retentionClass === 'none')
382
+ return null;
383
+ const id = dedicatedLeaseId(priorLease) || '<lease-id>';
384
+ const why = opts.retain ? '--retain-storage' : `retention class ${retentionClass}`;
385
+ return (`The workspace is kept (${why}), and its storage keeps billing until you delete it: ` +
386
+ `${opts.bin} machines dedicated destroy ${id} --purge-storage`);
387
+ }
388
+ const DEDICATED_TERMINAL_STATES = new Set(['terminated', 'failed', 'lost']);
389
+ /**
390
+ * Month-to-date Dedicated Runtime spend for the scope a `list` shows.
391
+ *
392
+ * `billing.currentMonthSpendCents` is the SCOPE's spend for the product — the
393
+ * server aggregates by owner scope, not by lease — so every row carried the same
394
+ * figure, and a column headed MONTH/CAP on per-machine rows read as that
395
+ * machine's cost when it was every machine's (OSK-9576). It is said once instead.
396
+ */
397
+ function dedicatedScopeMonthSpendCents(leases) {
398
+ for (const lease of leases) {
399
+ const cents = lease?.billing
400
+ ?.currentMonthSpendCents;
401
+ if (typeof cents === 'number' && Number.isFinite(cents))
402
+ return cents;
403
+ }
404
+ return null;
405
+ }
406
+ /**
407
+ * A lease's own cap, marked when the scope's spend has reached it. The mark is
408
+ * left off a terminal lease: its cap can no longer stop anything, and
409
+ * "$11.06/$10.00 OVER" on a machine terminated days ago read as that machine
410
+ * having overrun.
411
+ */
412
+ function dedicatedLeaseCapCell(lease) {
413
+ const billing = lease?.billing;
414
+ if (!billing || typeof billing.monthlySpendLimitCents !== 'number')
415
+ return '-';
416
+ const reached = billing.spendingLimitExceeded && !DEDICATED_TERMINAL_STATES.has(dedicatedLeaseState(lease));
417
+ return `${formatUsdCents(billing.monthlySpendLimitCents)}${reached ? ' · reached' : ''}`;
418
+ }
419
+ /**
420
+ * A lease's STATUS cell. `archived` and `terminated` are both "gone" to read, and
421
+ * only one of them is still charging — so a lease billing retained storage says
422
+ * so (OSK-9593).
423
+ */
424
+ function dedicatedLeaseStatusCell(lease) {
425
+ const state = dedicatedLeaseState(lease);
426
+ const billableState = lease?.billing
427
+ ?.billableState;
428
+ return billableState === 'storage' ? `${state} · billing storage` : state;
429
+ }
303
430
  function dedicatedLeaseDisplayName(lease) {
304
431
  return lease?.displayName ? String(lease.displayName) : '-';
305
432
  }
@@ -310,7 +437,17 @@ function formatUsdCents(cents) {
310
437
  const sign = cents < 0 ? '-' : '';
311
438
  return `${sign}$${(Math.abs(cents) / 100).toFixed(2)}`;
312
439
  }
313
- /** `this month $1.23 of $500.00 cap`, or why that sentence cannot be written. */
440
+ /**
441
+ * `$1.23 this month for machines of this type · $500.00 cap for this machine`,
442
+ * or why that sentence cannot be written.
443
+ *
444
+ * The two figures are different scopes and the sentence has to say so. The spend
445
+ * is a shared pot the server computes for (owner, team, machineProduct), so every
446
+ * lease of the same product reports the same number — a machine created minutes
447
+ * ago with no active seconds still reports the month's total. The cap is this
448
+ * lease's own. Printed as "$1.23 this month of $500.00 cap" the only available
449
+ * reading was that this machine had spent it.
450
+ */
314
451
  function dedicatedSpendSummary(lease) {
315
452
  const billing = lease.billing;
316
453
  if (!billing)
@@ -321,15 +458,33 @@ function dedicatedSpendSummary(lease) {
321
458
  }
322
459
  const spent = billing.currentMonthSpendCents;
323
460
  const projected = billing.projectedCurrentMonthSpendCents;
324
- const parts = [`${formatUsdCents(spent)} this month of ${formatUsdCents(cap)} cap`];
461
+ const parts = [
462
+ `${formatUsdCents(spent)} this month for machines of this type`,
463
+ `${formatUsdCents(cap)} cap for this machine`,
464
+ ];
325
465
  if (typeof projected === 'number' && projected !== spent) {
326
466
  parts.push(`projected ${formatUsdCents(projected)}`);
327
467
  }
328
468
  if (billing.spendingLimitExceeded) {
329
- parts.push('OVER CAP — compute is stopped, files are kept');
469
+ parts.push(`OVER CAP — ${overCapComputeClause(lease.state)}, files are kept`);
330
470
  }
331
471
  return parts.join(' · ');
332
472
  }
473
+ /**
474
+ * What the cap has done to compute, in the tense the lease is actually in.
475
+ *
476
+ * This always said "compute is stopped", including for a machine that was still
477
+ * `requested` — which, over its cap, never provisions and cannot have its cap
478
+ * raised until it settles (OSK-9836). The reconciler stops compute for exactly
479
+ * `stopping`, `stopped` and `archived`; anything else is only on its way there.
480
+ */
481
+ function overCapComputeClause(state) {
482
+ if (state === 'stopped' || state === 'archived')
483
+ return 'compute is stopped';
484
+ if (state === 'stopping')
485
+ return 'compute is stopping';
486
+ return 'compute will be stopped';
487
+ }
333
488
  /**
334
489
  * The human rendering of `machines dedicated show`, as lines.
335
490
  *
@@ -743,14 +898,128 @@ function dedicatedCatalogFromProducts(payload) {
743
898
  : {}),
744
899
  retentionClasses: configuration.retentionClasses || [],
745
900
  backupProfiles: configuration.backupProfiles || [],
901
+ ...(typeof configuration.defaultSizePreset === 'string'
902
+ ? { defaultSizePreset: configuration.defaultSizePreset }
903
+ : {}),
904
+ ...(typeof configuration.defaultRetentionClass === 'string'
905
+ ? { defaultRetentionClass: configuration.defaultRetentionClass }
906
+ : {}),
907
+ ...(typeof configuration.defaultBackupProfile === 'string'
908
+ ? { defaultBackupProfile: configuration.defaultBackupProfile }
909
+ : {}),
746
910
  defaultMonthlySpendingLimitCents: billing.defaultMonthlySpendingLimitCents,
747
911
  maxUserMonthlySpendingLimitCents: billing.maxUserMonthlySpendingLimitCents,
748
912
  priceLines: Array.isArray(presentation.priceLines)
749
913
  ? presentation.priceLines
750
914
  : [],
915
+ ...(typeof billing.priceBook?.version === 'string'
916
+ ? { priceBookVersion: billing.priceBook.version }
917
+ : {}),
751
918
  ...(typeof presentation.headline === 'string' ? { headline: presentation.headline } : {}),
752
919
  };
753
920
  }
921
+ /**
922
+ * The catalog's size rows: spec, the size's own hourly price, and which one
923
+ * `create` uses by default. The rows used to carry spec alone, sorted by name,
924
+ * so the $1.40/hr tier came first with nothing saying what it cost or that
925
+ * $0.65/hr standard was the default; a reader had to join two blocks by name.
926
+ */
927
+ function dedicatedCatalogSizeLines(catalog) {
928
+ return catalog.sizes.map((size) => {
929
+ const spec = [
930
+ size.vcpus ? `${size.vcpus} vCPU` : '',
931
+ size.memoryGiB ? `${size.memoryGiB} GiB RAM` : '',
932
+ size.accelerator || '',
933
+ typeof size.rateCentsPerHour === 'number' && size.rateCentsPerHour > 0
934
+ ? `${formatUsdCents(size.rateCentsPerHour)}/hr`
935
+ : '',
936
+ ]
937
+ .filter(Boolean)
938
+ .join(' · ');
939
+ const isDefault = size.id !== undefined && size.id === catalog.defaultSizePreset;
940
+ return ` ${String(size.id).padEnd(14)}${spec}${isDefault ? ' (default)' : ''}`;
941
+ });
942
+ }
943
+ /**
944
+ * What a create is about to buy, in the words and numbers the catalog uses.
945
+ *
946
+ * `create` provisioned a billable machine the moment it ran — no confirmation,
947
+ * no dry run, and every omitted flag silently took a paid default — while
948
+ * `destroy` asked first. A loop probing size validation bought two real
949
+ * machines, one of them $1.40/hr (OSK-9592). This is what `create` now shows
950
+ * before it asks, and what `--dry-run` prints instead of creating.
951
+ *
952
+ * Every default comes from the catalog, never from this file: a CLI that
953
+ * restates a default it does not own is how `--storage-gb` once billed twice the
954
+ * product's disk. A value the catalog does not declare is shown as "server
955
+ * default" rather than guessed.
956
+ */
957
+ function dedicatedCreateSummary(catalog, choices) {
958
+ const size = catalog?.sizes.find((entry) => entry.id === choices.sizePreset);
959
+ const spec = size
960
+ ? [
961
+ size.vcpus ? `${size.vcpus} vCPU` : '',
962
+ size.memoryGiB ? `${size.memoryGiB} GiB RAM` : '',
963
+ typeof size.rateCentsPerHour === 'number' && size.rateCentsPerHour > 0
964
+ ? `${formatUsdCents(size.rateCentsPerHour)} per running hour`
965
+ : '',
966
+ ]
967
+ .filter(Boolean)
968
+ .join(' · ')
969
+ : 'not in the catalog this server offers';
970
+ const storagePrice = catalog?.priceLines.find((line) => line.cadence === 'per-gb-month');
971
+ const storageGb = choices.storageGb ?? catalog?.storage?.defaultGb;
972
+ const storage = storageGb !== undefined
973
+ ? `${storageGb} GiB${choices.storageGb === undefined ? ' (catalog default)' : ''}`
974
+ : 'server default';
975
+ const retentionClass = choices.retentionClass ?? catalog?.defaultRetentionClass;
976
+ const retention = retentionClass
977
+ ? `${retentionClass}${choices.retentionClass === undefined ? ' (catalog default)' : ''}`
978
+ : 'server default';
979
+ const retains = retentionClass !== undefined && retentionClass !== 'none';
980
+ const capCents = choices.spendingLimitCents ?? catalog?.defaultMonthlySpendingLimitCents;
981
+ const cap = capCents !== undefined && capCents !== null
982
+ ? `${formatUsdCents(capCents)} per month${choices.spendingLimitCents === undefined ? ' (catalog default)' : ''}`
983
+ : 'server default';
984
+ return [
985
+ `Create Dedicated Runtime "${choices.name}" — it bills while it runs:`,
986
+ ` size ${choices.sizePreset} — ${spec}`,
987
+ ` storage ${storage}${storagePrice?.display ? ` — ${storagePrice.display}` : ''}`,
988
+ ` retention ${retention}${retains ? ' — destroy keeps the workspace, and its storage bills until you purge it' : ''}`,
989
+ ` monthly cap ${cap}`,
990
+ ];
991
+ }
992
+ /**
993
+ * The catalog's money lines, grouped by what each amount IS.
994
+ *
995
+ * A start hold and a monthly cap used to print under "Prices" beside the hourly
996
+ * rates, so starting a machine read as a 33c charge and "$500 default monthly
997
+ * cap" read as a bill (OSK-9536). A line with no `kind` comes from a server that
998
+ * predates it and is shown the way every line used to be: as a charge.
999
+ */
1000
+ function dedicatedCatalogPriceLineOutput(priceLines, priceBookVersion) {
1001
+ // The version is what a charge dispute later cites; it was in the payload and
1002
+ // the command dropped it (OSK-9562).
1003
+ const pricesHeading = priceBookVersion ? `Prices (price book ${priceBookVersion})` : 'Prices';
1004
+ const groups = [
1005
+ { kind: 'charge', heading: pricesHeading },
1006
+ { kind: 'hold', heading: 'Held against your cap at start (not billed)' },
1007
+ { kind: 'limit', heading: 'Monthly caps (--spending-limit-cents)' },
1008
+ ];
1009
+ if (priceLines.length === 0)
1010
+ return ['', pricesHeading, ' not published by this server'];
1011
+ const output = [];
1012
+ for (const group of groups) {
1013
+ const lines = priceLines.filter((line) => (line.kind || 'charge') === group.kind);
1014
+ if (lines.length === 0)
1015
+ continue;
1016
+ output.push('', group.heading);
1017
+ for (const line of lines) {
1018
+ output.push(` ${line.display || `${formatUsdCents(line.amountCents)} ${line.label || ''}`}`);
1019
+ }
1020
+ }
1021
+ return output;
1022
+ }
754
1023
  /**
755
1024
  * Dedicated Runtime commands are unusually likely to meet a server that has
756
1025
  * never heard of them.
@@ -867,9 +1136,44 @@ function dedicatedRuntimeApiErrorDetails(err) {
867
1136
  */
868
1137
  function describeRefusalReasons(err) {
869
1138
  const details = dedicatedRuntimeApiErrorDetails(err);
1139
+ const spend = describeSpendingCapRefusal(err.body, details);
1140
+ if (spend)
1141
+ return spend;
870
1142
  const sentences = (0, harnesses_1.describeHealthReasonSentences)(details?.reasons, details?.state);
871
1143
  return sentences ? ` Reasons: ${sentences}` : '';
872
1144
  }
1145
+ /**
1146
+ * The numbers behind a cap refusal: the cap, this month's spend so far, and
1147
+ * where the refused action would have taken it — plus the command that raises
1148
+ * the cap. Without them "would exceed the monthly cap" left a user guessing by
1149
+ * how much, and which cap (OSK-9552).
1150
+ */
1151
+ function describeSpendingCapRefusal(body, details) {
1152
+ let code;
1153
+ try {
1154
+ code = JSON.parse(body || '{}').code;
1155
+ }
1156
+ catch {
1157
+ return '';
1158
+ }
1159
+ if (code !== 'RUNTIME_SPENDING_LIMIT_EXCEEDED' || !details)
1160
+ return '';
1161
+ const cents = (key) => {
1162
+ const value = details[key];
1163
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
1164
+ };
1165
+ const limit = cents('limitCents');
1166
+ const spent = cents('currentSpendCents');
1167
+ const projected = cents('projectedCents');
1168
+ const parts = [
1169
+ limit !== null ? `Cap ${formatUsdCents(limit)}` : '',
1170
+ spent !== null ? `spent this month ${formatUsdCents(spent)}` : '',
1171
+ projected !== null ? `this would bring it to ${formatUsdCents(projected)}` : '',
1172
+ ].filter(Boolean);
1173
+ if (parts.length === 0)
1174
+ return '';
1175
+ return ` ${parts.join('; ')}. Raise the cap with \`machines dedicated spending-limit <lease-id> --cents <n>\`.`;
1176
+ }
873
1177
  function formatDedicatedRuntimeApiError(err) {
874
1178
  if (!(err instanceof api_fetch_1.ApiFetchError) || !err.body) {
875
1179
  return null;
@@ -0,0 +1,3 @@
1
+ export declare function isFailedLookupValue(value: unknown): boolean;
2
+ /** The shared explanation, so every resolver refuses in the same words. */
3
+ export declare function describeFailedLookupValue(value: string, noun: string): string;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isFailedLookupValue = isFailedLookupValue;
4
+ exports.describeFailedLookupValue = describeFailedLookupValue;
5
+ /**
6
+ * Values a script produces when a lookup upstream has ALREADY failed.
7
+ *
8
+ * `jq -r .id` on a missing field prints `null`. A JavaScript value that was
9
+ * never set stringifies to `undefined`; an object to `[object Object]`. Python
10
+ * prints `None`, Ruby `nil`, and a boolean field read as an id gives `true` or
11
+ * `false`. No person types any of these as a reference to a Task, a Space or a
12
+ * member — and a script passes one exactly when its previous step went wrong.
13
+ *
14
+ * The reference resolvers are generous by design: a person can type part of a
15
+ * title, and when exactly one title contains it, that is the one they meant.
16
+ * The same generosity turned `tasks complete "$ID"` with `ID=null` into
17
+ * "complete whichever single task mentions null". So these are refused before
18
+ * any search runs. Matching is on the WHOLE trimmed value, case-insensitively:
19
+ * a real title that merely contains one of these words still resolves.
20
+ */
21
+ const FAILED_LOOKUP_VALUES = new Set([
22
+ 'null',
23
+ 'undefined',
24
+ 'none',
25
+ 'nil',
26
+ 'nan',
27
+ 'true',
28
+ 'false',
29
+ '[object object]',
30
+ ]);
31
+ function isFailedLookupValue(value) {
32
+ return typeof value === 'string' && FAILED_LOOKUP_VALUES.has(value.trim().toLowerCase());
33
+ }
34
+ /** The shared explanation, so every resolver refuses in the same words. */
35
+ function describeFailedLookupValue(value, noun) {
36
+ return (`"${value.trim()}" is not a ${noun} reference — it is what a failed lookup prints ` +
37
+ '(for example `jq -r` on a missing field, or an unset variable). Refusing to search ' +
38
+ 'names for it, because a single accidental match would be acted on.\n' +
39
+ ' → check the step that produced this value; it most likely failed.');
40
+ }
@@ -242,10 +242,17 @@ async function resolveFirstPartyHarnessAgent(explicit, deps = {}, options = {})
242
242
  // Asked, not told: the caller opted out of creation, so "no default" is a fact
243
243
  // about this machine rather than a failure, and the remedy is a real command.
244
244
  if (options.createDefault === false) {
245
- throw new FirstPartyHarnessAgentError('agent-unset', `nothing is configured and you have no ${auth_core_1.FIRST_PARTY_HARNESS.displayName} default agent yet. ` +
245
+ throw new FirstPartyHarnessAgentError('agent-unset',
246
+ // No roster here. The question was which agent a session runs as, and a
247
+ // hundred names in front of the one sentence that answers it buried the
248
+ // answer (OSK-9396). A person choosing one has `agents list` for that.
249
+ `nothing is configured and you have no ${auth_core_1.FIRST_PARTY_HARNESS.displayName} default agent yet. ` +
246
250
  'One is created automatically the first time you run a managed session — ' +
247
251
  `or make it now with \`${auth_core_1.FIRST_PARTY_HARNESS.command} skrr-agent --create\`` +
248
- (rows.length ? `. Yours: ${candidates(rows)}` : ''));
252
+ (rows.length
253
+ ? `, or pick one of your ${rows.length} with ` +
254
+ `\`${auth_core_1.FIRST_PARTY_HARNESS.command} skrr-agent --set <id-or-name>\` (see \`skrr agents list\`)`
255
+ : ''));
249
256
  }
250
257
  // The endpoint answered without an agent. Nothing here can repair that, but
251
258
  // the user still has every ordinary way to name one.
@@ -35,6 +35,21 @@ export interface DoctorCheck {
35
35
  detail: string;
36
36
  /** What to do about it. Only present when there is something to do. */
37
37
  remedy?: string;
38
+ /** The files and directories a check looked at, for `--json` consumers. */
39
+ sources?: DoctorSource[];
40
+ }
41
+ /** One place the engine reads instructions or skills from. */
42
+ export interface DoctorSource {
43
+ /** As a person would type it: relative for the project, `~/` for the user. */
44
+ path: string;
45
+ kind: 'instructions' | 'skills';
46
+ owner: 'engine' | 'compatibility';
47
+ scope: 'project' | 'user';
48
+ present: boolean;
49
+ /** Skill directories holding a SKILL.md; only for a present skill root. */
50
+ skills?: number;
51
+ /** Why the engine will not read it although it exists, when it will not. */
52
+ disabled?: string;
38
53
  }
39
54
  export interface DoctorReport {
40
55
  checks: DoctorCheck[];