@skrr-ai/cli 0.1.38 → 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.
@@ -245,6 +245,14 @@ function findAlternateCli({
245
245
  function missingLocalCommandBuild({ cliRoot, argv, fileSystem = fs, verifySourceIdentity = true }) {
246
246
  const commandsDirectory = path.join(cliRoot, 'dist', 'commands');
247
247
  const sourceCommandsDirectory = path.join(cliRoot, 'src', 'commands');
248
+ // Staleness is a relation between a build and its SOURCE, so a CLI without
249
+ // source — every installed npm package, which ships `dist` and no `src` — has
250
+ // nothing to be stale against. Every check below is a development-checkout
251
+ // guard. Only the per-command one happened to notice: it looks a source file
252
+ // up first. The `--version` branch did not, read the missing source-digest
253
+ // manifest as a missing build, and made an installed 0.1.38 report whatever
254
+ // other `skrr` was on the PATH, or fail (OSK-9883).
255
+ if (!fileSystem.existsSync(sourceCommandsDirectory)) return null;
248
256
  let commandBuildManifest;
249
257
  try {
250
258
  commandBuildManifest = JSON.parse(
@@ -72,11 +72,15 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
72
72
  if (flags.repository !== undefined && !(0, dedicated_machines_1.isDedicatedRepositoryRef)(flags.repository)) {
73
73
  this.error(`--repository must look like owner/repo, got "${flags.repository}".`, { exit: 2 });
74
74
  }
75
+ // The server trims the preset, so ' standard' buys a standard machine; the
76
+ // preview must judge the same value, or it calls a size it is about to buy
77
+ // "not in the catalog" and then buys it anyway (OSK-9592).
78
+ const sizePreset = flags['size-preset'].trim();
75
79
  const requestId = flags['request-id'] || newRequestId();
76
80
  const body = (0, dedicated_machines_1.dedicatedRuntimeCreateInput)({
77
81
  requestId,
78
82
  displayName: flags.name,
79
- sizePreset: flags['size-preset'],
83
+ sizePreset,
80
84
  monthlySpendingLimitCents: flags['spending-limit-cents'],
81
85
  regionClass: flags['region-class'],
82
86
  storageGb: flags['storage-gb'],
@@ -93,7 +97,7 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
93
97
  const catalog = await this.readCatalogForSummary();
94
98
  const summary = (0, dedicated_machines_1.dedicatedCreateSummary)(catalog, {
95
99
  name: flags.name,
96
- sizePreset: flags['size-preset'],
100
+ sizePreset,
97
101
  storageGb: flags['storage-gb'],
98
102
  retentionClass: flags['retention-class'],
99
103
  spendingLimitCents: flags['spending-limit-cents'],
@@ -73,16 +73,16 @@ class DedicatedMachinesDestroy extends dedicated_lease_command_1.DedicatedLeaseC
73
73
  else if (flags['retain-storage'])
74
74
  retentionIntent = 'retain';
75
75
  const response = await this.callDedicated(() => (0, dedicated_machines_1.destroyDedicatedRuntime)(leaseId, { ...this.scopeFrom(flags), retentionIntent }));
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
- }
76
+ const note = (0, dedicated_machines_1.dedicatedDestroyRetentionNote)(prior, {
77
+ purge: Boolean(flags['purge-storage']),
78
+ retain: Boolean(flags['retain-storage']),
79
+ bin: this.config.bin,
80
+ });
81
+ // A script reading --json is exactly the caller that never sees a printed
82
+ // line, so the kept-and-billing fact rides in the document too (OSK-9593).
83
+ await this.reportLeaseAction(response, flags, { kind: 'destroyed' }, 'Destroying', note ? { storageNote: note } : {});
84
+ if (!flags.json && note)
85
+ this.log(note);
86
86
  }
87
87
  }
88
88
  exports.default = DedicatedMachinesDestroy;
@@ -55,6 +55,8 @@ class DedicatedMachinesExec extends base_command_1.BaseCommand {
55
55
  // What a script depends on, in the help a script author reads (OSK-8703).
56
56
  static description = [
57
57
  'Runs the command once, as the workload user, in /workspace or --cwd, through a shell with no TTY. ' +
58
+ 'The workload user can read /workspace but writes only under user-data/ (its HOME) and repos/, so a ' +
59
+ 'command that writes to its starting directory needs --cwd user-data or a path under one of those. ' +
58
60
  'Give it after -- as words (each one quoted for you), or as one shell string with --command; not both. ' +
59
61
  'The agent shell policy does not apply. A start whose answer is lost is followed up under the same ' +
60
62
  'request id, never run a second time.',
@@ -95,7 +97,7 @@ class DedicatedMachinesExec extends base_command_1.BaseCommand {
95
97
  description: 'Command to run as one shell string, instead of the words after --',
96
98
  }),
97
99
  cwd: core_1.Flags.string({
98
- description: 'Working directory, relative to /workspace (for example repos/app)',
100
+ description: 'Working directory, relative to /workspace (for example repos/app or user-data). /workspace itself is not writable by the workload user.',
99
101
  }),
100
102
  timeout: core_1.Flags.string({
101
103
  description: `How long to let it run, such as 30s or 5m (default 2m, max ${dedicated_machines_1.DEDICATED_EXEC_MAX_TIMEOUT_MS / 60000}m). The process group is killed at the deadline.`,
@@ -0,0 +1,17 @@
1
+ import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
2
+ /**
3
+ * `skrr machines dedicated price-book` — the operator's answer to "under what
4
+ * policy, and at what margin, is each tier sold".
5
+ *
6
+ * `catalog` is the customer view: prices and the book version, never a cost or
7
+ * a policy. This is admin-only on the server, because costs and margins are not
8
+ * customer facts.
9
+ */
10
+ export default class DedicatedMachinesPriceBook extends DedicatedLeaseCommand {
11
+ static description: string;
12
+ static examples: string[];
13
+ static flags: {
14
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
5
+ const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
+ /**
7
+ * `skrr machines dedicated price-book` — the operator's answer to "under what
8
+ * policy, and at what margin, is each tier sold".
9
+ *
10
+ * `catalog` is the customer view: prices and the book version, never a cost or
11
+ * a policy. This is admin-only on the server, because costs and margins are not
12
+ * customer facts.
13
+ */
14
+ class DedicatedMachinesPriceBook extends dedicated_lease_command_1.DedicatedLeaseCommand {
15
+ static description = 'Show the Dedicated Runtime price book an operator audits: version, pricing policy, and each tier rate, cost and margin (admin only)';
16
+ static examples = [
17
+ '<%= config.bin %> machines dedicated price-book',
18
+ '<%= config.bin %> machines dedicated price-book --json',
19
+ ];
20
+ static flags = {
21
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
22
+ };
23
+ async run() {
24
+ this.requireAuth();
25
+ const { flags } = await this.parse(DedicatedMachinesPriceBook);
26
+ const read = await this.callDedicated(() => (0, dedicated_machines_1.getDedicatedOperatorPriceBook)());
27
+ if (flags.json) {
28
+ this.log(JSON.stringify(read, null, 2));
29
+ return;
30
+ }
31
+ for (const line of (0, dedicated_machines_1.dedicatedOperatorPriceBookLines)(read))
32
+ this.log(line);
33
+ }
34
+ }
35
+ exports.default = DedicatedMachinesPriceBook;
@@ -61,7 +61,9 @@ export declare abstract class DedicatedLeaseCommand extends BaseCommand {
61
61
  timeout?: string;
62
62
  interval?: number;
63
63
  workspace?: string;
64
- }, goal: DedicatedWaitGoal | null, verb: string): Promise<void>;
64
+ }, goal: DedicatedWaitGoal | null, verb: string,
65
+ /** Fields a `--json` caller needs that the server response does not carry. */
66
+ jsonExtra?: Record<string, unknown>): Promise<void>;
65
67
  }
66
68
  /** The same transient verdict BaseCommand gives an ordinary API call. */
67
69
  export declare function dedicatedRuntimeApiErrorRetryable(err: unknown): boolean;
@@ -145,7 +145,9 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
145
145
  * Print the lease an action returned, or wait for its operation to finish
146
146
  * first when `--wait` was passed.
147
147
  */
148
- async reportLeaseAction(response, flags, goal, verb) {
148
+ async reportLeaseAction(response, flags, goal, verb,
149
+ /** Fields a `--json` caller needs that the server response does not carry. */
150
+ jsonExtra = {}) {
149
151
  let lease = response?.lease;
150
152
  const id = (0, dedicated_machines_1.dedicatedLeaseId)(lease);
151
153
  if (flags.wait && goal && id) {
@@ -201,7 +203,7 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
201
203
  }
202
204
  }
203
205
  if (flags.json) {
204
- this.log(JSON.stringify({ ...(response || {}), lease }, null, 2));
206
+ this.log(JSON.stringify({ ...(response || {}), lease, ...jsonExtra }, null, 2));
205
207
  return;
206
208
  }
207
209
  this.log(`Dedicated Runtime ${id || 'unknown'} ${(0, dedicated_machines_1.dedicatedLeaseState)(lease)}.`);
@@ -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;
@@ -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;
@@ -157,6 +159,59 @@ async function createDedicatedRuntime(input) {
157
159
  const { requestId, workspaceId, ...body } = input;
158
160
  return (0, api_fetch_1.apiFetch)(withDedicatedScope('/api/machines/dedicated', { workspaceId }), { method: 'POST', body, headers: { 'Idempotency-Key': requestId } });
159
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
+ }
160
215
  async function listDedicatedRuntimes(options = {}) {
161
216
  const query = new URLSearchParams();
162
217
  if (options.limit)
@@ -411,10 +466,25 @@ function dedicatedSpendSummary(lease) {
411
466
  parts.push(`projected ${formatUsdCents(projected)}`);
412
467
  }
413
468
  if (billing.spendingLimitExceeded) {
414
- parts.push('OVER CAP — compute is stopped, files are kept');
469
+ parts.push(`OVER CAP — ${overCapComputeClause(lease.state)}, files are kept`);
415
470
  }
416
471
  return parts.join(' · ');
417
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
+ }
418
488
  /**
419
489
  * The human rendering of `machines dedicated show`, as lines.
420
490
  *