@skrr-ai/cli 0.1.37 → 0.1.38

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 (174) hide show
  1. package/bin/dev-fallback.js +167 -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 +58 -1
  63. package/dist/commands/machines/dedicated/destroy.js +18 -4
  64. package/dist/commands/machines/dedicated/list.js +9 -6
  65. package/dist/commands/machines/hosted/list.js +12 -0
  66. package/dist/commands/machines/list.js +11 -0
  67. package/dist/commands/machines/show.d.ts +1 -0
  68. package/dist/commands/machines/show.js +40 -11
  69. package/dist/commands/profiles/list.d.ts +23 -0
  70. package/dist/commands/profiles/list.js +96 -0
  71. package/dist/commands/search/skills.d.ts +11 -1
  72. package/dist/commands/search/skills.js +26 -8
  73. package/dist/commands/skills/details.d.ts +2 -0
  74. package/dist/commands/skills/details.js +24 -1
  75. package/dist/commands/skills/import-as-actions.d.ts +32 -0
  76. package/dist/commands/skills/import-as-actions.js +175 -0
  77. package/dist/commands/skills/install.d.ts +2 -0
  78. package/dist/commands/skills/install.js +24 -1
  79. package/dist/commands/skills/invocable.js +6 -2
  80. package/dist/commands/skills/local.d.ts +40 -0
  81. package/dist/commands/skills/local.js +160 -0
  82. package/dist/commands/skills/search.d.ts +11 -1
  83. package/dist/commands/skills/search.js +29 -8
  84. package/dist/commands/spaces/actions/add.js +23 -5
  85. package/dist/commands/spaces/actions/available.js +3 -0
  86. package/dist/commands/spaces/actions/list.d.ts +5 -0
  87. package/dist/commands/spaces/actions/list.js +49 -3
  88. package/dist/commands/spaces/actions/run.d.ts +28 -0
  89. package/dist/commands/spaces/actions/run.js +146 -0
  90. package/dist/commands/spaces/actions/update.js +27 -5
  91. package/dist/commands/spaces/label-summaries.d.ts +27 -0
  92. package/dist/commands/spaces/label-summaries.js +64 -0
  93. package/dist/commands/spaces/task-rollups.d.ts +5 -0
  94. package/dist/commands/spaces/task-rollups.js +15 -4
  95. package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
  96. package/dist/commands/tasks/actions/attach.js +32 -7
  97. package/dist/commands/tasks/complete.js +13 -0
  98. package/dist/commands/tasks/create.js +1 -1
  99. package/dist/commands/tasks/events/append.js +29 -6
  100. package/dist/commands/tasks/expectations/assess.d.ts +1 -0
  101. package/dist/commands/tasks/expectations/assess.js +43 -5
  102. package/dist/commands/tasks/expectations.js +77 -16
  103. package/dist/commands/tasks/follow-up.js +2 -2
  104. package/dist/commands/tasks/lifecycle/definitions.js +21 -2
  105. package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
  106. package/dist/commands/tasks/lifecycle/detach.js +43 -3
  107. package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
  108. package/dist/commands/tasks/lifecycle/migrate.js +42 -3
  109. package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
  110. package/dist/commands/tasks/lifecycle/reopen.js +40 -3
  111. package/dist/commands/tasks/lifecycle/transition.js +33 -2
  112. package/dist/commands/tasks/list.js +16 -9
  113. package/dist/commands/tasks/move.js +11 -13
  114. package/dist/commands/tasks/review-queue.d.ts +15 -0
  115. package/dist/commands/tasks/review-queue.js +180 -31
  116. package/dist/commands/tasks/show.d.ts +11 -0
  117. package/dist/commands/tasks/show.js +105 -12
  118. package/dist/commands/tasks/trust.js +7 -2
  119. package/dist/commands/tasks/update.js +46 -10
  120. package/dist/commands/tasks/updates/add.js +1 -1
  121. package/dist/commands/token/list.d.ts +22 -0
  122. package/dist/commands/token/list.js +68 -0
  123. package/dist/commands/token/revoke.d.ts +23 -0
  124. package/dist/commands/token/revoke.js +53 -0
  125. package/dist/lib/agent-actions.d.ts +57 -2
  126. package/dist/lib/agent-actions.js +113 -21
  127. package/dist/lib/agent-pin-mute.d.ts +10 -5
  128. package/dist/lib/agent-pin-mute.js +17 -4
  129. package/dist/lib/agent-visual-refs.d.ts +27 -0
  130. package/dist/lib/agent-visual-refs.js +127 -2
  131. package/dist/lib/agentic-stream.d.ts +6 -0
  132. package/dist/lib/agentic-stream.js +34 -3
  133. package/dist/lib/assignee-resolver.d.ts +1 -1
  134. package/dist/lib/assignee-resolver.js +8 -0
  135. package/dist/lib/bulk-task-targets.d.ts +12 -0
  136. package/dist/lib/bulk-task-targets.js +34 -1
  137. package/dist/lib/cli-installers.d.ts +130 -0
  138. package/dist/lib/cli-installers.js +145 -0
  139. package/dist/lib/command-miss.js +55 -7
  140. package/dist/lib/dedicated-lease-command.d.ts +13 -0
  141. package/dist/lib/dedicated-lease-command.js +22 -1
  142. package/dist/lib/dedicated-machines.d.ts +95 -1
  143. package/dist/lib/dedicated-machines.js +236 -2
  144. package/dist/lib/failed-lookup.d.ts +3 -0
  145. package/dist/lib/failed-lookup.js +40 -0
  146. package/dist/lib/first-party-harness-agent.js +9 -2
  147. package/dist/lib/first-party-harness-doctor.d.ts +15 -0
  148. package/dist/lib/first-party-harness-doctor.js +126 -27
  149. package/dist/lib/followups.d.ts +14 -6
  150. package/dist/lib/followups.js +31 -6
  151. package/dist/lib/harnesses.d.ts +8 -0
  152. package/dist/lib/instruction-input.d.ts +19 -1
  153. package/dist/lib/instruction-input.js +52 -1
  154. package/dist/lib/label-scope.d.ts +15 -0
  155. package/dist/lib/label-scope.js +25 -5
  156. package/dist/lib/local-skills.d.ts +98 -0
  157. package/dist/lib/local-skills.js +195 -0
  158. package/dist/lib/machines.d.ts +10 -10
  159. package/dist/lib/machines.js +23 -1
  160. package/dist/lib/search-query.d.ts +19 -0
  161. package/dist/lib/search-query.js +35 -0
  162. package/dist/lib/space-custom-actions.d.ts +72 -0
  163. package/dist/lib/space-custom-actions.js +117 -0
  164. package/dist/lib/space-resolver.d.ts +1 -21
  165. package/dist/lib/space-resolver.js +19 -10
  166. package/dist/lib/task-closure.d.ts +3 -2
  167. package/dist/lib/task-closure.js +18 -20
  168. package/dist/lib/task-lifecycle-output.js +117 -9
  169. package/dist/lib/task-ref-resolver.d.ts +1 -17
  170. package/dist/lib/task-ref-resolver.js +18 -8
  171. package/dist/lib/tasks.js +9 -3
  172. package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
  173. package/oclif.manifest.json +26635 -25649
  174. package/package.json +9 -3
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.selectUsageRows = selectUsageRows;
4
+ exports.usageRowHarnessCell = usageRowHarnessCell;
3
5
  exports.buildUsageTable = buildUsageTable;
4
6
  const core_1 = require("@oclif/core");
5
7
  const base_command_1 = require("../../base-command");
@@ -22,6 +24,26 @@ function count(value) {
22
24
  const n = Number(value);
23
25
  return Number.isFinite(n) ? n.toLocaleString('en-US') : '-';
24
26
  }
27
+ /**
28
+ * The rows to render, whichever spelling the server used.
29
+ *
30
+ * `byHarness` is what it sends today. This reader asked only for `byRuntime` —
31
+ * the pre-rename spelling — so the fleet rollup was ALWAYS empty and the
32
+ * command always printed "No usage recorded.", while `--json` on the same call
33
+ * showed $24.93 across 46 sessions. A rollup cannot be empty while its own
34
+ * members are not, which is what made it findable.
35
+ *
36
+ * The old spellings stay as fallbacks, after the current one, so an older
37
+ * deployment still renders. They are not the primary, which is the whole bug.
38
+ */
39
+ function selectUsageRows(payload) {
40
+ return payload?.byHarness ?? payload?.byRuntime ?? payload?.usage ?? [];
41
+ }
42
+ /** The HARNESS cell, under either spelling of the id. */
43
+ function usageRowHarnessCell(row) {
44
+ const id = row.harnessId ?? row.runtimeId;
45
+ return id ? String(id).slice(0, 12) : '-';
46
+ }
25
47
  const SHARED_COLUMNS = [
26
48
  { key: 'provider', header: 'PROVIDER' },
27
49
  { key: 'sessions', header: 'SESSIONS' },
@@ -66,7 +88,7 @@ function buildUsageTable(rows, perHarness) {
66
88
  ...SHARED_COLUMNS,
67
89
  ],
68
90
  rows: rows.map((r) => ({
69
- harness: r.runtimeId ? String(r.runtimeId).slice(0, 12) : '-',
91
+ harness: usageRowHarnessCell(r),
70
92
  name: (0, format_1.formatCell)(r.name),
71
93
  ...shared(r),
72
94
  })),
@@ -105,7 +127,7 @@ class HarnessesUsage extends base_command_1.BaseCommand {
105
127
  // `{usage, totals}`. Neither is a superset, so read both rather than
106
128
  // assuming one and rendering a table of dashes for the other.
107
129
  const payload = report;
108
- const rows = payload?.byRuntime ?? payload?.usage ?? [];
130
+ const rows = selectUsageRows(payload);
109
131
  const totals = payload?.grandTotals ?? payload?.totals;
110
132
  if (rows.length === 0) {
111
133
  this.log(args.harness ? 'No usage recorded for this harness.' : 'No usage recorded.');
@@ -29,6 +29,18 @@ class InstructionsStatus extends base_command_1.BaseCommand {
29
29
  // only `bundleId`, so join to the bundle list — including archived/deleted,
30
30
  // since a target can outlive its bundle. A failed lookup is non-fatal: the
31
31
  // column falls back to the full bundle id.
32
+ // The live daemons, so a stalled row can tell "this machine now starts its
33
+ // daemon a different way" from "the daemon is gone" — and only the second
34
+ // earns a destructive remedy. Non-fatal: an empty list degrades to the old
35
+ // wording, which is a guess, not a wrong claim about a daemon we checked.
36
+ let liveDaemonIds = [];
37
+ try {
38
+ const daemons = await data_provider_1.dataService.getDaemons();
39
+ liveDaemonIds = (daemons.active || []).map((d) => d.daemonId);
40
+ }
41
+ catch {
42
+ // Leave it empty.
43
+ }
32
44
  let bundleNames = new Map();
33
45
  try {
34
46
  const bundleResponse = await data_provider_1.dataService.listManagedInstructionBundles({
@@ -62,7 +74,7 @@ class InstructionsStatus extends base_command_1.BaseCommand {
62
74
  (target.scope === 'global' ? 'global' : target.projectPath || 'project'),
63
75
  desired: target.desiredState,
64
76
  applied: target.appliedVersionNumber ? `v${target.appliedVersionNumber}` : '-',
65
- status: (0, instruction_input_1.describeTargetSync)(target),
77
+ status: (0, instruction_input_1.describeTargetSync)(target, Date.now(), liveDaemonIds),
66
78
  })), [
67
79
  { key: 'id', header: 'TARGET ID' },
68
80
  // Wide enough for a prompt name (matches `instructions list`'s NAME
@@ -41,16 +41,8 @@ class DedicatedMachinesCatalog extends dedicated_lease_command_1.DedicatedLeaseC
41
41
  this.log('\nSizes (--size-preset)');
42
42
  if (catalog.sizes.length === 0)
43
43
  this.log(' none offered right now');
44
- for (const size of catalog.sizes) {
45
- const spec = [
46
- size.vcpus ? `${size.vcpus} vCPU` : '',
47
- size.memoryGiB ? `${size.memoryGiB} GiB RAM` : '',
48
- size.accelerator || '',
49
- ]
50
- .filter(Boolean)
51
- .join(' · ');
52
- this.log(` ${String(size.id).padEnd(14)}${spec}`);
53
- }
44
+ for (const line of (0, dedicated_machines_1.dedicatedCatalogSizeLines)(catalog))
45
+ this.log(line);
54
46
  if (catalog.storage) {
55
47
  const { minGb, defaultGb, maxGb } = catalog.storage;
56
48
  this.log(`\nWorkspace (--storage-gb)\n ${minGb}–${maxGb} GiB, default ${defaultGb}; grow later, never shrink`);
@@ -64,11 +56,8 @@ class DedicatedMachinesCatalog extends dedicated_lease_command_1.DedicatedLeaseC
64
56
  if (catalog.regionClasses.length) {
65
57
  this.log(`\nRegion (--region-class)\n ${catalog.regionClasses.join(', ')}`);
66
58
  }
67
- this.log('\nPrices');
68
- if (catalog.priceLines.length === 0)
69
- this.log(' not published by this server');
70
- for (const line of catalog.priceLines) {
71
- this.log(` ${line.display || `${(0, dedicated_machines_1.formatUsdCents)(line.amountCents)} ${line.label || ''}`}`);
59
+ for (const line of (0, dedicated_machines_1.dedicatedCatalogPriceLineOutput)(catalog.priceLines, catalog.priceBookVersion)) {
60
+ this.log(line);
72
61
  }
73
62
  }
74
63
  }
@@ -17,6 +17,14 @@ export default class DedicatedMachinesCreate extends DedicatedLeaseCommand {
17
17
  workspace: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
18
18
  'request-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
19
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
20
+ yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ 'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
20
22
  };
21
23
  run(): Promise<void>;
24
+ /**
25
+ * The catalog for the summary, or null. Not through `callDedicated`, which
26
+ * exits on an API error: an unreachable catalog must cost the summary its
27
+ * detail, not stop a create the user is about to confirm anyway.
28
+ */
29
+ private readCatalogForSummary;
22
30
  }
@@ -4,12 +4,14 @@ const core_1 = require("@oclif/core");
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
6
6
  const dedicated_machines_1 = require("../../../lib/dedicated-machines");
7
+ const harnesses_1 = require("../../../lib/harnesses");
7
8
  class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCommand {
8
9
  static description = 'Request a Dedicated Runtime lease. See `machines dedicated catalog` for sizes, storage bounds and prices.';
9
10
  static examples = [
10
11
  '<%= config.bin %> machines dedicated create --name dev-box --size-preset standard',
11
12
  '<%= config.bin %> machines dedicated create --name dev-box --size-preset standard --spending-limit-cents 20000',
12
- '<%= config.bin %> machines dedicated create --name dev-box --size-preset standard --spending-limit-cents 20000 --repository acme/app --wait --json',
13
+ '<%= config.bin %> machines dedicated create --name dev-box --size-preset standard --dry-run',
14
+ '<%= config.bin %> machines dedicated create --name dev-box --size-preset standard --spending-limit-cents 20000 --repository acme/app --yes --wait --json',
13
15
  ];
14
16
  static flags = {
15
17
  name: core_1.Flags.string({ description: 'Display name for the dedicated machine', required: true }),
@@ -55,6 +57,13 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
55
57
  description: 'Idempotency key. Reuse it to safely retry the same create request.',
56
58
  }),
57
59
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
60
+ yes: core_1.Flags.boolean({
61
+ char: 'y',
62
+ description: 'Skip the confirmation. A create starts billing, so it asks first; required when not at a terminal.',
63
+ }),
64
+ 'dry-run': core_1.Flags.boolean({
65
+ description: 'Show the size, price, storage, retention and cap this create would use, and create nothing',
66
+ }),
58
67
  ...dedicated_lease_command_1.dedicatedWaitFlags,
59
68
  };
60
69
  async run() {
@@ -76,6 +85,40 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
76
85
  repositoryRef: flags.repository,
77
86
  workspaceId: flags.workspace,
78
87
  });
88
+ // Say what is about to be bought before it is, in the catalog's own numbers.
89
+ // `destroy` has always asked first; the command that starts a meter did not
90
+ // (OSK-9592). A catalog read that fails only loses the summary's detail,
91
+ // never the confirmation itself.
92
+ if (flags['dry-run'] || !flags.yes) {
93
+ const catalog = await this.readCatalogForSummary();
94
+ const summary = (0, dedicated_machines_1.dedicatedCreateSummary)(catalog, {
95
+ name: flags.name,
96
+ sizePreset: flags['size-preset'],
97
+ storageGb: flags['storage-gb'],
98
+ retentionClass: flags['retention-class'],
99
+ spendingLimitCents: flags['spending-limit-cents'],
100
+ });
101
+ if (flags['dry-run']) {
102
+ if (flags.json) {
103
+ this.log(JSON.stringify({ dryRun: true, request: body, summary }, null, 2));
104
+ }
105
+ else {
106
+ for (const line of summary)
107
+ this.log(line);
108
+ this.log('Dry run: nothing was created.');
109
+ }
110
+ return;
111
+ }
112
+ if (!process.stdin.isTTY) {
113
+ this.error('Creating a Dedicated Runtime starts billing. Pass --yes to confirm when not at a terminal, or --dry-run to see what it would create.', { exit: 2 });
114
+ }
115
+ for (const line of summary)
116
+ this.log(line);
117
+ if (!(await this.confirm('Create it? [y/N] '))) {
118
+ this.log('Aborted. Nothing was created.');
119
+ this.exit(1);
120
+ }
121
+ }
79
122
  const response = await this.callDedicated(() => (0, dedicated_machines_1.createDedicatedRuntime)(body));
80
123
  const lease = response?.lease;
81
124
  const id = (0, dedicated_machines_1.dedicatedLeaseId)(lease);
@@ -97,6 +140,20 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
97
140
  this.log(`Poll: ${this.config.bin} machines dedicated show ${id} (or pass --wait)`);
98
141
  }
99
142
  }
143
+ /**
144
+ * The catalog for the summary, or null. Not through `callDedicated`, which
145
+ * exits on an API error: an unreachable catalog must cost the summary its
146
+ * detail, not stop a create the user is about to confirm anyway.
147
+ */
148
+ async readCatalogForSummary() {
149
+ try {
150
+ return (0, dedicated_machines_1.dedicatedCatalogFromProducts)(await (0, harnesses_1.listHarnessProducts)());
151
+ }
152
+ catch (error) {
153
+ process.stderr.write(`Could not read the catalog for the summary (${error?.message || 'unknown error'}); showing what was passed.\n`);
154
+ return null;
155
+ }
156
+ }
100
157
  }
101
158
  exports.default = DedicatedMachinesCreate;
102
159
  function newRequestId() {
@@ -45,12 +45,17 @@ class DedicatedMachinesDestroy extends dedicated_lease_command_1.DedicatedLeaseC
45
45
  // so it does not ask. This mirrors the server's rule only to decide whether
46
46
  // to PROMPT; the server still decides what happens.
47
47
  let willPurge = Boolean(flags['purge-storage']);
48
+ // Read even under --yes when the workspace may be kept: whether a destroy
49
+ // keeps billing storage is worth saying afterwards, and only the lease as it
50
+ // stood before the destroy can say it.
51
+ const prior = willPurge
52
+ ? undefined
53
+ : (await this.callDedicated(() => (0, dedicated_machines_1.getDedicatedRuntime)(leaseId, this.scopeFrom(flags))))
54
+ ?.lease;
48
55
  if (!willPurge && !flags.yes) {
49
- const current = await this.callDedicated(() => (0, dedicated_machines_1.getDedicatedRuntime)(leaseId, this.scopeFrom(flags)));
50
- const lease = current?.lease;
51
56
  willPurge =
52
- lease?.state === 'archived' ||
53
- (!flags['retain-storage'] && (lease?.storage?.retentionClass || 'none') === 'none');
57
+ prior?.state === 'archived' ||
58
+ (!flags['retain-storage'] && (prior?.storage?.retentionClass || 'none') === 'none');
54
59
  }
55
60
  if (willPurge && !flags.yes) {
56
61
  if (!process.stdin.isTTY) {
@@ -69,6 +74,15 @@ class DedicatedMachinesDestroy extends dedicated_lease_command_1.DedicatedLeaseC
69
74
  retentionIntent = 'retain';
70
75
  const response = await this.callDedicated(() => (0, dedicated_machines_1.destroyDedicatedRuntime)(leaseId, { ...this.scopeFrom(flags), retentionIntent }));
71
76
  await this.reportLeaseAction(response, flags, { kind: 'destroyed' }, 'Destroying');
77
+ if (!flags.json) {
78
+ const note = (0, dedicated_machines_1.dedicatedDestroyRetentionNote)(prior, {
79
+ purge: Boolean(flags['purge-storage']),
80
+ retain: Boolean(flags['retain-storage']),
81
+ bin: this.config.bin,
82
+ });
83
+ if (note)
84
+ this.log(note);
85
+ }
72
86
  }
73
87
  }
74
88
  exports.default = DedicatedMachinesDestroy;
@@ -40,9 +40,14 @@ class DedicatedMachinesList extends dedicated_lease_command_1.DedicatedLeaseComm
40
40
  }
41
41
  return;
42
42
  }
43
+ const scopeSpendCents = (0, dedicated_machines_1.dedicatedScopeMonthSpendCents)(leases);
44
+ if (scopeSpendCents !== null) {
45
+ this.log(`Dedicated Runtime spend this month, across every machine listed: ${(0, dedicated_machines_1.formatUsdCents)(scopeSpendCents)}`);
46
+ this.log('');
47
+ }
43
48
  (0, format_1.renderTable)(leases.map((lease) => ({
44
49
  id: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseId)(lease)),
45
- status: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseState)(lease)),
50
+ status: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseStatusCell)(lease)),
46
51
  name: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseDisplayName)(lease)),
47
52
  size: (0, format_1.formatCell)([
48
53
  lease.resources?.sizePreset,
@@ -50,17 +55,15 @@ class DedicatedMachinesList extends dedicated_lease_command_1.DedicatedLeaseComm
50
55
  ]
51
56
  .filter(Boolean)
52
57
  .join(' ') || '-'),
53
- spend: (0, format_1.formatCell)(lease.billing
54
- ? `${(0, dedicated_machines_1.formatUsdCents)(lease.billing.currentMonthSpendCents)}/${(0, dedicated_machines_1.formatUsdCents)(lease.billing.monthlySpendLimitCents)}${lease.billing.spendingLimitExceeded ? ' OVER' : ''}`
55
- : '-'),
58
+ cap: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseCapCell)(lease)),
56
59
  retention: (0, format_1.formatCell)(lease.storage?.retentionClass || '-'),
57
60
  backup: (0, format_1.formatCell)(lease.storage?.lastBackupAt || '-'),
58
61
  })), [
59
62
  { key: 'id', header: 'ID' },
60
- { key: 'status', header: 'STATUS' },
63
+ { key: 'status', header: 'STATUS', maxWidth: 28 },
61
64
  { key: 'name', header: 'NAME', maxWidth: 28 },
62
65
  { key: 'size', header: 'SIZE', maxWidth: 18 },
63
- { key: 'spend', header: 'MONTH/CAP', maxWidth: 24 },
66
+ { key: 'cap', header: 'MONTHLY CAP', maxWidth: 24 },
64
67
  { key: 'retention', header: 'RETENTION', maxWidth: 14 },
65
68
  { key: 'backup', header: 'LAST BACKUP', maxWidth: 24 },
66
69
  ], (line) => this.log(line));
@@ -61,6 +61,18 @@ class HostedMachinesList extends base_command_1.BaseCommand {
61
61
  { key: 'repos', header: 'REPOS', maxWidth: 36 },
62
62
  { key: 'preview', header: 'PREVIEW', maxWidth: 48 },
63
63
  ], (line) => this.log(line));
64
+ // How to find the same box in the other list. `machines list` identifies a
65
+ // hosted machine by `lease:<leaseId>` — a different random string from the
66
+ // `sbs_` id in the ID column above — so side by side the two commands read
67
+ // as two unrelated machines. Saying the mapping once, where both values are
68
+ // already on screen, is cheaper than a column nobody can interpret.
69
+ const withLease = sessions.filter((session) => session.machineLeaseId);
70
+ if (withLease.length > 0) {
71
+ this.log('');
72
+ this.log(`In \`${this.config.bin} machines list\` these appear under their LEASE id as ` +
73
+ `\`lease:<lease>\` — e.g. \`lease:${String(withLease[0].machineLeaseId)}\`. ` +
74
+ `\`machines show\` takes either.`);
75
+ }
64
76
  }
65
77
  /**
66
78
  * OSK-2045 — this command lists Hosted Machine *sessions* and nothing else,
@@ -78,6 +78,17 @@ class MachinesList extends base_command_1.BaseCommand {
78
78
  { key: 'platform', header: 'PLATFORM' },
79
79
  { key: 'seen', header: 'LAST SEEN' },
80
80
  ], (line) => this.log(line));
81
+ // Why a machine is not `online`, under the table. `degraded` on its own is
82
+ // unactionable — the dogfood note on this output was "I still do not know
83
+ // whether something is wrong with my laptop" — and it is usually one named
84
+ // row, not the machine.
85
+ const explained = shown.filter((m) => m.status !== 'online' && m.statusReason);
86
+ if (explained.length > 0) {
87
+ this.log('');
88
+ for (const m of explained) {
89
+ this.log(` ${m.preferences?.name || m.name}: ${m.statusReason}`);
90
+ }
91
+ }
81
92
  const hidden = all.length - shown.length;
82
93
  if (hidden > 0) {
83
94
  this.log('');
@@ -8,6 +8,7 @@ export default class MachinesShow extends BaseCommand {
8
8
  };
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
  }
@@ -9,6 +9,7 @@ class MachinesShow extends base_command_1.BaseCommand {
9
9
  static description = 'Show one machine — its identity, harnesses, and available actions';
10
10
  static examples = [
11
11
  '<%= config.bin %> machines show 03548116',
12
+ '<%= config.bin %> machines show 03548116 --all',
12
13
  '<%= config.bin %> machines show "frankdu@DS-3" --json',
13
14
  ];
14
15
  static args = {
@@ -18,7 +19,13 @@ class MachinesShow extends base_command_1.BaseCommand {
18
19
  required: true,
19
20
  }),
20
21
  };
21
- static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
22
+ static flags = {
23
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
24
+ all: core_1.Flags.boolean({
25
+ description: 'Include harness registrations superseded by newer ones',
26
+ default: false,
27
+ }),
28
+ };
22
29
  async run() {
23
30
  this.requireAuth();
24
31
  const { args, flags } = await this.parse(MachinesShow);
@@ -43,6 +50,10 @@ class MachinesShow extends base_command_1.BaseCommand {
43
50
  this.log(` id ${machine.id}`);
44
51
  this.log(` identity ${machine.identityKind}`);
45
52
  this.log(` status ${machine.status}`);
53
+ // The status alone does not say what to do. Naming the rows that decided it
54
+ // is the actionable half — bring that provider back, or forget the row.
55
+ if (machine.statusReason)
56
+ this.log(` why ${machine.statusReason}`);
46
57
  if (machine.machineUuid)
47
58
  this.log(` machineUuid ${machine.machineUuid}`);
48
59
  if (machine.daemonId)
@@ -60,20 +71,34 @@ class MachinesShow extends base_command_1.BaseCommand {
60
71
  const harnesses = machine.harnesses ?? [];
61
72
  const live = (0, machines_1.liveHarnessCount)(machine);
62
73
  const superseded = harnesses.length - live;
74
+ // Superseded registrations are history, not capabilities, and they are not
75
+ // counted as missing ones next to a status of `online`. They are hidden by
76
+ // default, the convention `machines list` already set for dead machines:
77
+ // printed, they were 35 of a machine's 42 rows, back two months, under a
78
+ // header whose count read like a summary of rows it then printed anyway
79
+ // (OSK-9389). `--all` shows them, and `--json` always carries every row.
80
+ const shown = flags.all ? harnesses : harnesses.filter((h) => !h.superseded);
63
81
  this.log('');
64
- // The superseded rows are still listed — labelled, not dropped, so they can
65
- // be seen and reaped — but they are not missing capabilities and must not
66
- // be counted as such next to a status of `online`.
67
82
  this.log(`Harnesses (${machine.onlineHarnessCount}/${live} online` +
68
- (superseded > 0 ? `, ${superseded} superseded by newer registrations` : '') +
83
+ (flags.all && superseded > 0 ? `, ${superseded} superseded by newer registrations` : '') +
69
84
  ')');
70
- if (harnesses.length > 0) {
71
- (0, format_1.renderTable)(harnesses.map((h) => ({
85
+ if (shown.length > 0) {
86
+ (0, format_1.renderTable)(shown.map((h) => ({
72
87
  id: (0, format_1.formatCell)(h.id),
73
88
  provider: (0, format_1.formatCell)(h.provider),
74
- // A superseded row that merely says `offline` reads as a harness
75
- // that went down. Naming it is what explains the count above.
76
- status: h.superseded ? 'superseded' : (0, format_1.formatCell)(h.status),
89
+ // Liveness only. Whether a newer registration replaced the row is a
90
+ // different fact — a superseded row can have been online — so it
91
+ // gets its own column rather than overwriting this one.
92
+ status: (0, format_1.formatCell)(h.status),
93
+ registration: h.superseded ? 'superseded' : 'current',
94
+ // `authStatus` is the DAEMON's standing with skrr, not this
95
+ // provider's standing with its vendor. It is written across every row
96
+ // of a daemon at once (`HarnessService.markAuthOkAfterRegister` does
97
+ // one `updateMany({owner, daemonId})`), so it is structurally the
98
+ // same value for all of them and can never answer "is this harness
99
+ // signed in". Under a bare `AUTH` header it read as the second
100
+ // question and answered `ok` for a Devin whose own CLI said
101
+ // "Not logged in." The header now names what the value is.
77
102
  auth: (0, format_1.formatCell)(h.authStatus),
78
103
  seen: h.lastSeenAt
79
104
  ? new Date(h.lastSeenAt).toISOString().slice(0, 16).replace('T', ' ')
@@ -82,10 +107,14 @@ class MachinesShow extends base_command_1.BaseCommand {
82
107
  { key: 'id', header: 'HARNESS ID' },
83
108
  { key: 'provider', header: 'PROVIDER' },
84
109
  { key: 'status', header: 'STATUS' },
85
- { key: 'auth', header: 'AUTH' },
110
+ ...(flags.all ? [{ key: 'registration', header: 'REGISTRATION' }] : []),
111
+ { key: 'auth', header: 'DAEMON AUTH' },
86
112
  { key: 'seen', header: 'LAST SEEN' },
87
113
  ], (line) => this.log(` ${line}`));
88
114
  }
115
+ if (!flags.all && superseded > 0) {
116
+ this.log(` ${superseded} superseded registration(s) hidden — pass --all to include them.`);
117
+ }
89
118
  // Print what is PERMITTED, not the whole control map: a list of six
90
119
  // booleans mostly reading false is noise, and the useful question is
91
120
  // "what can I do to this machine right now".
@@ -0,0 +1,23 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * Which stored sessions exist on this machine.
4
+ *
5
+ * Every command advertises `--profile` and nothing could tell you what to pass
6
+ * it. Profiles are additive and stored on disk — the default at
7
+ * `<root>/cli-config.json`, named ones at `<root>/profiles/<name>/cli-config.json`
8
+ * — so the list was always knowable; there was simply no way to ask.
9
+ *
10
+ * That is not only a discoverability nit. A second profile is how one machine
11
+ * holds a second identity, which is what any test of a NON-OWNER path needs. A
12
+ * dogfood run of the `force`-past-a-required-Review flow stalled on exactly
13
+ * this: the flag was there, the second identity was reachable, and nothing named
14
+ * it.
15
+ */
16
+ export default class ProfilesList extends BaseCommand {
17
+ static description: string;
18
+ static examples: string[];
19
+ static flags: {
20
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ };
22
+ run(): Promise<void>;
23
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_fs_1 = __importDefault(require("node:fs"));
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const core_1 = require("@oclif/core");
9
+ const base_command_1 = require("../../base-command");
10
+ const config_1 = require("../../lib/config");
11
+ const keychain_1 = require("../../lib/keychain");
12
+ /**
13
+ * Which stored sessions exist on this machine.
14
+ *
15
+ * Every command advertises `--profile` and nothing could tell you what to pass
16
+ * it. Profiles are additive and stored on disk — the default at
17
+ * `<root>/cli-config.json`, named ones at `<root>/profiles/<name>/cli-config.json`
18
+ * — so the list was always knowable; there was simply no way to ask.
19
+ *
20
+ * That is not only a discoverability nit. A second profile is how one machine
21
+ * holds a second identity, which is what any test of a NON-OWNER path needs. A
22
+ * dogfood run of the `force`-past-a-required-Review flow stalled on exactly
23
+ * this: the flag was there, the second identity was reachable, and nothing named
24
+ * it.
25
+ */
26
+ class ProfilesList extends base_command_1.BaseCommand {
27
+ static description = 'List the stored sessions (profiles) on this machine';
28
+ static examples = [
29
+ '<%= config.bin %> profiles list',
30
+ '<%= config.bin %> profiles list --json',
31
+ '<%= config.bin %> login --profile reviewer',
32
+ ];
33
+ static flags = {
34
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
35
+ };
36
+ async run() {
37
+ const { flags } = await this.parse(ProfilesList);
38
+ const root = (0, config_1.configRoot)();
39
+ const active = (0, keychain_1.getActiveProfile)() || keychain_1.DEFAULT_PROFILE;
40
+ const rows = [];
41
+ const seen = new Set();
42
+ const describe = (name, configFile) => {
43
+ /*
44
+ * Deliberately NOT a "signed in" column.
45
+ *
46
+ * The credential does not live in `cli-config.json` — it is in the
47
+ * keychain — so this file can only report the deployment. A first draft
48
+ * inferred sign-in from a `token` key that is never there and printed
49
+ * `no` for the profile the caller was actively authenticated as: a
50
+ * confident wrong answer, which is worse than an absent one and exactly
51
+ * the failure this whole surface keeps making. Read the server, say the
52
+ * server.
53
+ */
54
+ if (seen.has(name))
55
+ return;
56
+ seen.add(name);
57
+ let server;
58
+ try {
59
+ const raw = JSON.parse(node_fs_1.default.readFileSync(configFile, 'utf8'));
60
+ server = typeof raw.baseURL === 'string' ? raw.baseURL : undefined;
61
+ }
62
+ catch {
63
+ // A profile directory with no readable config is still a profile; say
64
+ // so rather than hiding it, because a half-written one is exactly what
65
+ // someone would be trying to diagnose.
66
+ }
67
+ rows.push({ name, active: name === active, server });
68
+ };
69
+ describe(keychain_1.DEFAULT_PROFILE, node_path_1.default.join(root, 'cli-config.json'));
70
+ try {
71
+ for (const entry of node_fs_1.default.readdirSync(node_path_1.default.join(root, 'profiles'), { withFileTypes: true })) {
72
+ if (!entry.isDirectory())
73
+ continue;
74
+ describe(entry.name, node_path_1.default.join(root, 'profiles', entry.name, 'cli-config.json'));
75
+ }
76
+ }
77
+ catch {
78
+ // No `profiles/` directory means no named profiles — not an error.
79
+ }
80
+ if (flags.json) {
81
+ this.log(JSON.stringify({ root, active, profiles: rows }, null, 2));
82
+ return;
83
+ }
84
+ // Width from the DATA, so a long profile name cannot run into the next
85
+ // column — `df-memory-verify-isolated` did exactly that at a fixed 24.
86
+ const width = Math.max(4, ...rows.map((row) => row.name.length)) + 2;
87
+ this.log('NAME'.padEnd(width) + 'ACTIVE'.padEnd(9) + 'SERVER');
88
+ for (const row of rows) {
89
+ this.log(row.name.padEnd(width) + (row.active ? 'yes' : '-').padEnd(9) + (row.server || '-'));
90
+ }
91
+ this.log('');
92
+ this.log(`Add one: ${this.config.bin} login --profile <name>`);
93
+ this.log(`Use one: ${this.config.bin} <command> --profile <name> (or OVERSKY_PROFILE)`);
94
+ }
95
+ }
96
+ exports.default = ProfilesList;
@@ -2,9 +2,19 @@ import { BaseCommand } from '../../base-command';
2
2
  export default class SearchSkills extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
+ /**
6
+ * `skills search` and `search skills` are the SAME search, and both spellings
7
+ * stay. Neither said so, their one-liners differed, and only one carried short
8
+ * flags — so a reader picked one at random with no way to tell whether they had
9
+ * picked a deprecated spelling. Duplication that is deliberate should say so;
10
+ * duplication that is silent reads as a mistake either way.
11
+ */
12
+ static args: {
13
+ query: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
14
+ };
5
15
  static flags: {
6
16
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
- query: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ query: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
18
  category: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
19
  limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
10
20
  };
@@ -4,19 +4,36 @@ 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
6
  const format_1 = require("../../lib/format");
7
+ const search_query_1 = require("../../lib/search-query");
7
8
  class SearchSkills extends base_command_1.BaseCommand {
8
- static description = 'Search skills in the skrr store';
9
+ static description = 'Search skills in the skrr store by keyword (same as `skills search`)';
9
10
  static examples = [
10
- '<%= config.bin %> search skills --query "web scraping"',
11
- '<%= config.bin %> search skills --query summarize --category productivity',
12
- '<%= config.bin %> search skills --query translate --limit 10 --json',
11
+ '<%= config.bin %> search skills "web scraping"',
12
+ '<%= config.bin %> search skills summarize --category productivity',
13
+ '<%= config.bin %> search skills translate --limit 10 --json',
14
+ '<%= config.bin %> search skills -q translate',
13
15
  ];
16
+ /**
17
+ * `skills search` and `search skills` are the SAME search, and both spellings
18
+ * stay. Neither said so, their one-liners differed, and only one carried short
19
+ * flags — so a reader picked one at random with no way to tell whether they had
20
+ * picked a deprecated spelling. Duplication that is deliberate should say so;
21
+ * duplication that is silent reads as a mistake either way.
22
+ */
23
+ static args = {
24
+ query: core_1.Args.string({
25
+ description: 'Search term (same value as --query; pass either, not both)',
26
+ required: false,
27
+ ignoreStdin: true,
28
+ }),
29
+ };
14
30
  static flags = {
15
31
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
32
+ // See `skills search` — the positional satisfies it and the flag stays so
33
+ // existing scripts keep working.
16
34
  query: core_1.Flags.string({
17
35
  char: 'q',
18
36
  description: 'Search query',
19
- required: true,
20
37
  }),
21
38
  category: core_1.Flags.string({
22
39
  char: 'c',
@@ -29,11 +46,12 @@ class SearchSkills extends base_command_1.BaseCommand {
29
46
  };
30
47
  async run() {
31
48
  this.requireAuth();
32
- const { flags } = await this.parse(SearchSkills);
49
+ const { args, flags } = await this.parse(SearchSkills);
50
+ const query = (0, search_query_1.resolveSearchQuery)(this, args.query, flags.query, 'search skills');
33
51
  let response;
34
52
  try {
35
53
  response = await data_provider_1.dataService.getStoreSkills({
36
- q: flags.query,
54
+ q: query,
37
55
  category: flags.category,
38
56
  limit: flags.limit,
39
57
  });
@@ -48,7 +66,7 @@ class SearchSkills extends base_command_1.BaseCommand {
48
66
  const r = response;
49
67
  const items = Array.isArray(r) ? r : (r?.data ?? []);
50
68
  if (items.length === 0) {
51
- this.log('No skills found.');
69
+ this.log(`No skills found matching '${query}'.`);
52
70
  return;
53
71
  }
54
72
  const rows = items.map((s) => ({