@skrr-ai/cli 0.1.25 → 0.1.27

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 (80) hide show
  1. package/dist/base-command.js +29 -5
  2. package/dist/commands/code/install.js +13 -0
  3. package/dist/commands/harnesses/leases/billing.d.ts +9 -6
  4. package/dist/commands/harnesses/leases/billing.js +19 -24
  5. package/dist/commands/machines/dedicated/attach.d.ts +28 -0
  6. package/dist/commands/machines/dedicated/attach.js +76 -0
  7. package/dist/commands/machines/dedicated/catalog.d.ts +17 -0
  8. package/dist/commands/machines/dedicated/catalog.js +75 -0
  9. package/dist/commands/machines/dedicated/cp.d.ts +22 -0
  10. package/dist/commands/machines/dedicated/cp.js +76 -0
  11. package/dist/commands/machines/dedicated/create.d.ts +6 -2
  12. package/dist/commands/machines/dedicated/create.js +29 -20
  13. package/dist/commands/machines/dedicated/current.d.ts +19 -0
  14. package/dist/commands/machines/dedicated/current.js +49 -0
  15. package/dist/commands/machines/dedicated/destroy.d.ts +6 -2
  16. package/dist/commands/machines/dedicated/destroy.js +43 -25
  17. package/dist/commands/machines/dedicated/exec.d.ts +32 -0
  18. package/dist/commands/machines/dedicated/exec.js +122 -0
  19. package/dist/commands/machines/dedicated/grow.d.ts +6 -2
  20. package/dist/commands/machines/dedicated/grow.js +16 -20
  21. package/dist/commands/machines/dedicated/health-check.d.ts +2 -2
  22. package/dist/commands/machines/dedicated/health-check.js +18 -15
  23. package/dist/commands/machines/dedicated/incident-close.d.ts +3 -2
  24. package/dist/commands/machines/dedicated/incident-close.js +13 -15
  25. package/dist/commands/machines/dedicated/index.js +39 -16
  26. package/dist/commands/machines/dedicated/list.d.ts +2 -2
  27. package/dist/commands/machines/dedicated/list.js +28 -17
  28. package/dist/commands/machines/dedicated/restart.d.ts +5 -2
  29. package/dist/commands/machines/dedicated/restart.js +10 -20
  30. package/dist/commands/machines/dedicated/restore.d.ts +26 -0
  31. package/dist/commands/machines/dedicated/restore.js +64 -0
  32. package/dist/commands/machines/dedicated/show.d.ts +2 -2
  33. package/dist/commands/machines/dedicated/show.js +30 -31
  34. package/dist/commands/machines/dedicated/sign-in.d.ts +19 -0
  35. package/dist/commands/machines/dedicated/sign-in.js +49 -0
  36. package/dist/commands/machines/dedicated/snapshot.d.ts +5 -2
  37. package/dist/commands/machines/dedicated/snapshot.js +13 -20
  38. package/dist/commands/machines/dedicated/spending-limit.d.ts +22 -0
  39. package/dist/commands/machines/dedicated/spending-limit.js +51 -0
  40. package/dist/commands/machines/dedicated/start.d.ts +5 -2
  41. package/dist/commands/machines/dedicated/start.js +10 -20
  42. package/dist/commands/machines/dedicated/stop.d.ts +5 -2
  43. package/dist/commands/machines/dedicated/stop.js +10 -20
  44. package/dist/commands/machines/dedicated/terminal.d.ts +22 -0
  45. package/dist/commands/machines/dedicated/terminal.js +42 -0
  46. package/dist/commands/tasks/list.js +27 -0
  47. package/dist/lib/daemonBroker.d.ts +8 -0
  48. package/dist/lib/daemonBroker.js +2 -2
  49. package/dist/lib/dedicated-copy.d.ts +22 -0
  50. package/dist/lib/dedicated-copy.js +115 -0
  51. package/dist/lib/dedicated-exec.d.ts +80 -0
  52. package/dist/lib/dedicated-exec.js +271 -0
  53. package/dist/lib/dedicated-lease-command.d.ts +59 -0
  54. package/dist/lib/dedicated-lease-command.js +181 -0
  55. package/dist/lib/dedicated-machines.d.ts +337 -12
  56. package/dist/lib/dedicated-machines.js +502 -25
  57. package/dist/lib/dedicated-terminal.d.ts +89 -0
  58. package/dist/lib/dedicated-terminal.js +275 -0
  59. package/dist/lib/dedicated-wait.d.ts +72 -0
  60. package/dist/lib/dedicated-wait.js +195 -0
  61. package/dist/lib/edge-error-page.d.ts +49 -0
  62. package/dist/lib/edge-error-page.js +120 -0
  63. package/dist/lib/harnesses.d.ts +15 -1
  64. package/dist/lib/harnesses.js +20 -2
  65. package/dist/lib/hosted-machines.js +7 -0
  66. package/dist/lib/node-adapter.js +1 -1
  67. package/dist/lib/sky-code.d.ts +30 -1
  68. package/dist/lib/sky-code.js +44 -2
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +2 -1
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +10 -0
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +17 -1
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +10 -0
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +16 -0
  77. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  78. package/dist/node_modules/@skrr-ai/data-provider/index.js +2625 -2608
  79. package/oclif.manifest.json +27851 -26936
  80. package/package.json +1 -1
@@ -1,11 +1,14 @@
1
- import { BaseCommand } from '../../../base-command';
2
- export default class DedicatedMachinesStop extends BaseCommand {
1
+ import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
2
+ export default class DedicatedMachinesStop extends DedicatedLeaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
6
6
  lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
7
  };
8
8
  static flags: {
9
+ wait: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ timeout: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ interval: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
12
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
13
  };
11
14
  run(): Promise<void>;
@@ -1,42 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
- const base_command_1 = require("../../../base-command");
4
+ const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
5
5
  const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
- class DedicatedMachinesStop extends base_command_1.BaseCommand {
7
- static description = 'Stop a Dedicated Runtime lease';
6
+ class DedicatedMachinesStop extends dedicated_lease_command_1.DedicatedLeaseCommand {
7
+ static description = 'Stop a Dedicated Runtime: compute billing ends, the workspace volume and its storage billing stay. Running processes end.';
8
8
  static examples = [
9
9
  '<%= config.bin %> machines dedicated stop <lease-id>',
10
- '<%= config.bin %> machines dedicated stop <lease-id> --json',
10
+ '<%= config.bin %> machines dedicated stop <lease-id> --wait',
11
+ '<%= config.bin %> machines dedicated stop <lease-id> --wait --timeout 10m --json',
11
12
  ];
12
13
  static args = {
13
14
  lease: core_1.Args.string({
14
- description: 'Dedicated Runtime lease id',
15
+ description: dedicated_lease_command_1.DEDICATED_LEASE_ARG_DESCRIPTION,
15
16
  required: true,
16
17
  ignoreStdin: true,
17
18
  }),
18
19
  };
19
20
  static flags = {
20
21
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
22
+ ...dedicated_lease_command_1.dedicatedWaitFlags,
21
23
  };
22
24
  async run() {
23
25
  this.requireAuth();
24
26
  const { args, flags } = await this.parse(DedicatedMachinesStop);
25
- let response;
26
- try {
27
- response = await (0, dedicated_machines_1.performDedicatedLeaseAction)(args.lease, 'stop');
28
- }
29
- catch (err) {
30
- const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err);
31
- if (message)
32
- this.error(message, { exit: 1 });
33
- this.handleApiError(err);
34
- }
35
- if (flags.json) {
36
- this.log(JSON.stringify(response, null, 2));
37
- return;
38
- }
39
- this.log(`Dedicated Runtime ${(0, dedicated_machines_1.dedicatedLeaseId)(response?.lease)} ${(0, dedicated_machines_1.dedicatedLeaseState)(response?.lease)}.`);
27
+ const leaseId = await this.resolveLeaseArg(args.lease);
28
+ const response = await this.callDedicated(() => (0, dedicated_machines_1.performDedicatedLeaseAction)(leaseId, 'stop', this.scopeFrom(flags)));
29
+ await this.reportLeaseAction(response, flags, { kind: 'stopped' }, 'Stopping');
40
30
  }
41
31
  }
42
32
  exports.default = DedicatedMachinesStop;
@@ -0,0 +1,22 @@
1
+ import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
2
+ /**
3
+ * `skrr machines dedicated terminal` — the web lease card's "Open terminal",
4
+ * from a terminal.
5
+ *
6
+ * `exec` has no TTY and cannot drive anything that prompts, which left
7
+ * `gh auth login`, a REPL, a dev server's interactive setup and `claude` itself
8
+ * reachable only from a browser. This opens the same real PTY the web does,
9
+ * through the same relay and the same admission.
10
+ */
11
+ export default class DedicatedMachinesTerminal extends DedicatedLeaseCommand {
12
+ static description: string;
13
+ static examples: string[];
14
+ static args: {
15
+ lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
16
+ };
17
+ static flags: {
18
+ cwd: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
+ command: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ };
21
+ run(): Promise<void>;
22
+ }
@@ -0,0 +1,42 @@
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
+ /**
6
+ * `skrr machines dedicated terminal` — the web lease card's "Open terminal",
7
+ * from a terminal.
8
+ *
9
+ * `exec` has no TTY and cannot drive anything that prompts, which left
10
+ * `gh auth login`, a REPL, a dev server's interactive setup and `claude` itself
11
+ * reachable only from a browser. This opens the same real PTY the web does,
12
+ * through the same relay and the same admission.
13
+ */
14
+ class DedicatedMachinesTerminal extends dedicated_lease_command_1.DedicatedLeaseCommand {
15
+ static description = 'Open an interactive shell on a Dedicated Runtime (a real PTY, as skrr-workload). Ctrl-] closes it.';
16
+ static examples = [
17
+ '<%= config.bin %> machines dedicated terminal <lease-id>',
18
+ '<%= config.bin %> machines dedicated terminal <lease-id> --cwd /workspace/repos/app',
19
+ '<%= config.bin %> machines dedicated terminal <lease-id> --command "npm run dev"',
20
+ ];
21
+ static args = {
22
+ lease: core_1.Args.string({
23
+ description: 'Dedicated Runtime lease id',
24
+ required: true,
25
+ ignoreStdin: true,
26
+ }),
27
+ };
28
+ static flags = {
29
+ cwd: core_1.Flags.string({
30
+ description: 'Directory to start in (defaults to HOME, /workspace/user-data)',
31
+ }),
32
+ command: core_1.Flags.string({
33
+ description: 'A command to type into the shell once it is ready, as if you typed it. The shell stays open after it.',
34
+ }),
35
+ };
36
+ async run() {
37
+ this.requireAuth();
38
+ const { args, flags } = await this.parse(DedicatedMachinesTerminal);
39
+ await this.openDedicatedTerminal(args.lease, flags, flags.command);
40
+ }
41
+ }
42
+ exports.default = DedicatedMachinesTerminal;
@@ -86,6 +86,33 @@ function emptyTasksMessage(flags, bin = 'skrr') {
86
86
  lines.push(' no longer has that status. Try `--execution-state active` (running now) or');
87
87
  lines.push(` \`--execution-state recovered\` (reaped, startable), or \`${bin} tasks active\`.`);
88
88
  }
89
+ else if (typeof flags.status === 'string' &&
90
+ flags.status.length > 0 &&
91
+ !flags['execution-state']) {
92
+ // A filter that matches nothing and a board that IS empty print the same
93
+ // thing, and the caller cannot tell which they are looking at. Observed:
94
+ // `--status todo` on a board whose open column is `backlog` said `No tasks.`
95
+ // while twelve were open, and it was believed.
96
+ //
97
+ // Not fixed by validating the value, for two reasons. A status is a
98
+ // Space-owned workflow-state id — TASK_STATUSES is a legacy default set, not
99
+ // a closed vocabulary — so a space may legitimately define one we have never
100
+ // heard of. And `todo` IS a built-in, so refusing unknown values would not
101
+ // have caught the case that prompted this. What distinguishes the two
102
+ // readings is naming the filter and how to drop it.
103
+ //
104
+ // Silent when `--execution-state` is also set, to leave the exact bare
105
+ // string the branch above deliberately produces for that combination. The
106
+ // same ambiguity exists there, but overturning a pinned decision is not
107
+ // this change's business.
108
+ lines.push('');
109
+ lines.push(`Note: nothing matched \`--status ${flags.status}\`. A status is a workflow-state id the`);
110
+ lines.push(' space owns, so a board need not use this one at all.');
111
+ lines.push(' → See what this scope actually holds: the same command without `--status`.');
112
+ if (!data_provider_1.TASK_STATUSES.includes(flags.status)) {
113
+ lines.push(` (built-in ids: ${data_provider_1.TASK_STATUSES.join(', ')})`);
114
+ }
115
+ }
89
116
  return lines.join('\n');
90
117
  }
91
118
  /**
@@ -36,6 +36,7 @@
36
36
  * liveness with `process.kill(pid, 0)` to filter stale files left behind
37
37
  * by a force-killed daemon.
38
38
  */
39
+ import { type AuthBackend } from './auth-storage';
39
40
  import { type CliConfig } from './config';
40
41
  /**
41
42
  * Wire-shape of the bootstrap file. Mirrors
@@ -86,6 +87,13 @@ export interface DaemonBrokerResult {
86
87
  * the mint/confirm split, where the mint still revoked eagerly.
87
88
  */
88
89
  familyId?: string;
90
+ /**
91
+ * Where `attemptDaemonBrokerLoginAndPersist` actually stored the credential.
92
+ * Callers label the token's source from this, not from the platform: Keychain
93
+ * only exists on macOS, and even there a failed Keychain write falls back to
94
+ * the file.
95
+ */
96
+ storedIn?: Exclude<AuthBackend, 'none'>;
89
97
  }
90
98
  /**
91
99
  * Explicit reasons the broker path could fail. These exist to keep the
@@ -527,7 +527,7 @@ async function attemptDaemonBrokerLoginAndPersist(opts) {
527
527
  if (!serverOrigin) {
528
528
  throw new Error('Daemon-broker credential persistence requires a valid CLI baseURL.');
529
529
  }
530
- (0, auth_storage_1.writeToBackend)({
530
+ const storedIn = (0, auth_storage_1.writeToBackend)({
531
531
  token: outcome.accessToken,
532
532
  refreshToken: outcome.refreshToken,
533
533
  ...(expiryToEpochMs(outcome.accessExpiresAt) !== undefined
@@ -548,7 +548,7 @@ async function attemptDaemonBrokerLoginAndPersist(opts) {
548
548
  familyId: outcome.familyId,
549
549
  });
550
550
  }
551
- return outcome;
551
+ return storedIn === 'none' ? outcome : { ...outcome, storedIn };
552
552
  }
553
553
  // ---------------------------------------------------------------------
554
554
  // Auto-broker on first command (BaseCommand.init hook)
@@ -0,0 +1,22 @@
1
+ import { readDedicatedRuntimeFileChunk, writeDedicatedRuntimeFileChunk, type DedicatedRuntimeScope } from './dedicated-machines';
2
+ /**
3
+ * The two copy loops behind `skrr machines dedicated cp`.
4
+ *
5
+ * One API request per 512 KiB chunk, so every chunk passes the same owner
6
+ * check, generation fence and daemon file policy as any other access to the
7
+ * machine. Neither end is replaced until the whole file has arrived: an upload
8
+ * stages beside its destination on the machine and the final chunk renames it;
9
+ * a download writes a temporary file beside its local destination and renames.
10
+ */
11
+ export interface DedicatedCopyIo {
12
+ readChunk: typeof readDedicatedRuntimeFileChunk;
13
+ writeChunk: typeof writeDedicatedRuntimeFileChunk;
14
+ chunkBytes: number;
15
+ maxBytes: number;
16
+ }
17
+ export interface DedicatedCopyResult {
18
+ bytes: number;
19
+ destination: string;
20
+ }
21
+ export declare function uploadToDedicatedRuntime(localPath: string, leaseId: string, remotePath: string, scope?: DedicatedRuntimeScope, io?: Partial<DedicatedCopyIo>): Promise<DedicatedCopyResult>;
22
+ export declare function downloadFromDedicatedRuntime(leaseId: string, remotePath: string, localPath: string, scope?: DedicatedRuntimeScope, io?: Partial<DedicatedCopyIo>): Promise<DedicatedCopyResult>;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uploadToDedicatedRuntime = uploadToDedicatedRuntime;
4
+ exports.downloadFromDedicatedRuntime = downloadFromDedicatedRuntime;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const promises_1 = require("node:fs/promises");
7
+ const node_path_1 = require("node:path");
8
+ const dedicated_machines_1 = require("./dedicated-machines");
9
+ const defaultIo = {
10
+ readChunk: dedicated_machines_1.readDedicatedRuntimeFileChunk,
11
+ writeChunk: dedicated_machines_1.writeDedicatedRuntimeFileChunk,
12
+ chunkBytes: dedicated_machines_1.DEDICATED_FILE_CHUNK_BYTES,
13
+ maxBytes: dedicated_machines_1.DEDICATED_FILE_MAX_BYTES,
14
+ };
15
+ function mib(bytes) {
16
+ return `${Math.round((bytes / (1024 * 1024)) * 10) / 10} MiB`;
17
+ }
18
+ async function uploadToDedicatedRuntime(localPath, leaseId, remotePath, scope = {}, io = {}) {
19
+ const { writeChunk, chunkBytes, maxBytes } = { ...defaultIo, ...io };
20
+ const info = await (0, promises_1.stat)(localPath);
21
+ if (!info.isFile()) {
22
+ throw new Error(`${localPath} is not a regular file`);
23
+ }
24
+ if (info.size > maxBytes) {
25
+ throw new Error(`${localPath} is ${mib(info.size)}; copy is limited to ${mib(maxBytes)}. Push it through Git or object storage instead.`);
26
+ }
27
+ const transferId = `cp-${Date.now().toString(36)}-${(0, node_crypto_1.randomBytes)(6).toString('hex')}`;
28
+ const handle = await (0, promises_1.open)(localPath, 'r');
29
+ try {
30
+ const buffer = Buffer.alloc(chunkBytes);
31
+ let offset = 0;
32
+ for (;;) {
33
+ const { bytesRead } = await handle.read(buffer, 0, buffer.byteLength, offset);
34
+ const final = offset + bytesRead >= info.size;
35
+ if (!final && bytesRead === 0) {
36
+ throw new Error(`${localPath} shrank while it was being copied`);
37
+ }
38
+ const result = await writeChunk(leaseId, {
39
+ path: remotePath,
40
+ transferId,
41
+ offset,
42
+ dataBase64: buffer.subarray(0, bytesRead).toString('base64'),
43
+ // A file that GREW is caught here: the machine refuses to commit
44
+ // when what it staged is not the size declared at the start.
45
+ ...(final ? { final: true, totalSize: info.size } : {}),
46
+ }, scope);
47
+ offset += bytesRead;
48
+ if (final) {
49
+ return { bytes: offset, destination: `${leaseId}:${result.path || remotePath}` };
50
+ }
51
+ }
52
+ }
53
+ catch (err) {
54
+ // Best effort: remove the staged partial so it does not sit beside the
55
+ // destination. The destination itself was never touched.
56
+ await writeChunk(leaseId, { path: remotePath, transferId, offset: 0, abort: true }, scope).catch(() => undefined);
57
+ throw err;
58
+ }
59
+ finally {
60
+ await handle.close();
61
+ }
62
+ }
63
+ async function downloadFromDedicatedRuntime(leaseId, remotePath, localPath, scope = {}, io = {}) {
64
+ const { readChunk, chunkBytes } = { ...defaultIo, ...io };
65
+ let target = localPath;
66
+ try {
67
+ if ((await (0, promises_1.stat)(localPath)).isDirectory()) {
68
+ target = (0, node_path_1.join)(localPath, (0, node_path_1.basename)(remotePath));
69
+ }
70
+ }
71
+ catch {
72
+ // Does not exist yet: it names the file.
73
+ }
74
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(target), { recursive: true });
75
+ const temporary = (0, node_path_1.join)((0, node_path_1.dirname)(target), `.${(0, node_path_1.basename)(target)}.skrr-download-${(0, node_crypto_1.randomBytes)(6).toString('hex')}`);
76
+ const handle = await (0, promises_1.open)(temporary, 'wx');
77
+ let closed = false;
78
+ let offset = 0;
79
+ let size = -1;
80
+ let mtimeMs = -1;
81
+ try {
82
+ for (;;) {
83
+ const chunk = await readChunk(leaseId, { path: remotePath, offset, length: chunkBytes }, scope);
84
+ if (size === -1) {
85
+ size = chunk.size;
86
+ mtimeMs = chunk.mtimeMs;
87
+ }
88
+ else if (chunk.size !== size || chunk.mtimeMs !== mtimeMs) {
89
+ // Chunks from two versions of a file splice into one that never existed.
90
+ throw new Error(`${remotePath} changed on the machine while it was being copied`);
91
+ }
92
+ const bytes = Buffer.from(chunk.dataBase64, 'base64');
93
+ if (bytes.byteLength !== chunk.bytesRead) {
94
+ throw new Error('A chunk arrived damaged; nothing was written to the destination');
95
+ }
96
+ await handle.write(bytes, 0, bytes.byteLength, offset);
97
+ offset += bytes.byteLength;
98
+ if (chunk.eof)
99
+ break;
100
+ if (bytes.byteLength === 0) {
101
+ throw new Error(`${remotePath} ended before its reported size`);
102
+ }
103
+ }
104
+ await handle.close();
105
+ closed = true;
106
+ await (0, promises_1.rename)(temporary, target);
107
+ return { bytes: offset, destination: target };
108
+ }
109
+ catch (err) {
110
+ if (!closed)
111
+ await handle.close().catch(() => undefined);
112
+ await (0, promises_1.unlink)(temporary).catch(() => undefined);
113
+ throw err;
114
+ }
115
+ }
@@ -0,0 +1,80 @@
1
+ import { execDedicatedRuntime, observeDedicatedRuntimeExecution, startDedicatedRuntimeExecution, type DedicatedRuntimeExecInput, type DedicatedRuntimeExecResult, type DedicatedRuntimeScope } from './dedicated-machines';
2
+ /**
3
+ * `skrr machines dedicated exec`, as a start and a series of short observations
4
+ * (OSK-8705).
5
+ *
6
+ * The command used to be one request held open for as long as it ran. Behind
7
+ * CloudFront that request died at 60 s with an HTML 504 while the command kept
8
+ * running, and the page's advice — try again — ran it twice. Now no request
9
+ * lasts longer than one bounded observation, so no proxy timeout in front of
10
+ * the API decides whether the caller learns the exit code:
11
+ *
12
+ * 1. START under one idempotency key. A start whose answer never arrived (a
13
+ * dropped connection, a gateway page) is sent again with the SAME key, and
14
+ * the server returns the execution it already started — never a second run.
15
+ * 2. OBSERVE, letting the server hold each answer up to 20 s, until the
16
+ * command ends. A failed observation is only a failed read; it is retried.
17
+ * 3. PRINT exactly what the synchronous route printed: the same result, the
18
+ * same refusal body, the same exit code.
19
+ *
20
+ * Against a server that predates `executions`, it falls back to the synchronous
21
+ * route and says what that costs — but only on the FIRST start of a request id
22
+ * this invocation minted. A 404 without a product code proves the task that
23
+ * answered has no such route, not that no other task started the command; in a
24
+ * mixed fleet (a rolling deploy, a rollback) only the first attempt can rely on
25
+ * it. Every other case retries the same id or stops with the id to repeat.
26
+ */
27
+ /** How long each observation lets the server hold the answer. */
28
+ export declare const EXEC_OBSERVE_WAIT_MS = 20000;
29
+ /** A request id is the idempotency key; the server accepts exactly this shape. */
30
+ export declare const EXEC_REQUEST_ID_PATTERN: RegExp;
31
+ export declare function newExecRequestId(): string;
32
+ /** The CLI stopped hearing about a command it started. The command may still run. */
33
+ export declare class DedicatedExecContactLostError extends Error {
34
+ readonly requestId: string;
35
+ readonly executionId: string;
36
+ readonly code = "DEDICATED_RUNTIME_EXECUTION_CONTACT_LOST";
37
+ constructor(requestId: string, executionId: string, cause: unknown);
38
+ }
39
+ /** No start request got an answer from the API, so whether the command started is unknown. */
40
+ export declare class DedicatedExecStartUnconfirmedError extends Error {
41
+ readonly requestId: string;
42
+ readonly code = "DEDICATED_RUNTIME_EXECUTION_START_UNCONFIRMED";
43
+ constructor(requestId: string, cause: unknown);
44
+ }
45
+ /**
46
+ * The server has no `executions` route, but this invocation carries a request id
47
+ * it chose: falling back to the single-request route would ignore that id, and a
48
+ * run it already started elsewhere would happen twice.
49
+ */
50
+ export declare class DedicatedExecIdempotencyUnsupportedError extends Error {
51
+ readonly requestId: string;
52
+ readonly code = "DEDICATED_RUNTIME_EXECUTION_REQUEST_ID_UNSUPPORTED";
53
+ constructor(requestId: string);
54
+ }
55
+ export interface DedicatedExecIo {
56
+ start: typeof startDedicatedRuntimeExecution;
57
+ observe: typeof observeDedicatedRuntimeExecution;
58
+ legacyExec: typeof execDedicatedRuntime;
59
+ sleep: (ms: number) => Promise<void>;
60
+ now: () => number;
61
+ /** A line about the invocation (not the command's output), for stderr. */
62
+ notice: (message: string) => void;
63
+ signal: (ms: number) => AbortSignal | undefined;
64
+ }
65
+ /** The server answered, but has no `executions` route: nothing was started. */
66
+ export declare function isMissingExecutionsEndpoint(err: unknown): boolean;
67
+ export interface RunDedicatedExecOptions extends DedicatedRuntimeScope {
68
+ /** Idempotency key. Pass one to be able to repeat the invocation safely. */
69
+ requestId?: string;
70
+ }
71
+ export declare function runDedicatedExec(leaseId: string, input: DedicatedRuntimeExecInput, options?: RunDedicatedExecOptions, io?: Partial<DedicatedExecIo>): Promise<DedicatedRuntimeExecResult>;
72
+ /**
73
+ * What was cut. A started command's stored outcome keeps at most the last
74
+ * 256 KiB of what the machine returned (`outputBytes` is the whole); the
75
+ * single-request route caps at 1 MB from the start.
76
+ */
77
+ export declare function describeTruncation(result: {
78
+ stdout: string;
79
+ outputBytes?: number;
80
+ }): string;