@skrr-ai/cli 0.1.27 → 0.1.29
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.
- package/dist/base-command.d.ts +15 -0
- package/dist/base-command.js +49 -0
- package/dist/commands/agents/chat.d.ts +2 -0
- package/dist/commands/agents/chat.js +40 -2
- package/dist/commands/balance/show.d.ts +2 -3
- package/dist/commands/balance/show.js +2 -3
- package/dist/commands/balance/usage.d.ts +3 -11
- package/dist/commands/balance/usage.js +19 -72
- package/dist/commands/code/index.d.ts +4 -0
- package/dist/commands/code/index.js +9 -0
- package/dist/commands/code/install.d.ts +10 -2
- package/dist/commands/code/install.js +38 -28
- package/dist/commands/harnesses/leases/show.js +7 -4
- package/dist/commands/inbox/index.d.ts +15 -0
- package/dist/commands/inbox/index.js +52 -20
- package/dist/commands/instructions/install.d.ts +12 -0
- package/dist/commands/instructions/install.js +59 -14
- package/dist/commands/login.js +6 -0
- package/dist/commands/machines/dedicated/attach.js +1 -1
- package/dist/commands/machines/dedicated/cp.d.ts +1 -0
- package/dist/commands/machines/dedicated/cp.js +63 -9
- package/dist/commands/machines/dedicated/create.d.ts +1 -1
- package/dist/commands/machines/dedicated/create.js +7 -2
- package/dist/commands/machines/dedicated/exec.d.ts +23 -1
- package/dist/commands/machines/dedicated/exec.js +67 -7
- package/dist/commands/machines/dedicated/index.js +2 -0
- package/dist/commands/machines/dedicated/restore.d.ts +6 -0
- package/dist/commands/machines/dedicated/restore.js +7 -1
- package/dist/commands/machines/dedicated/sign-in.d.ts +4 -3
- package/dist/commands/machines/dedicated/sign-in.js +4 -3
- package/dist/commands/machines/dedicated/terminal.js +1 -1
- package/dist/commands/machines/dedicated/update-image.d.ts +15 -0
- package/dist/commands/machines/dedicated/update-image.js +38 -0
- package/dist/lib/balance.d.ts +2 -2
- package/dist/lib/balance.js +6 -4
- package/dist/lib/daemon-target.d.ts +103 -0
- package/dist/lib/daemon-target.js +110 -0
- package/dist/lib/dedicated-copy.d.ts +92 -2
- package/dist/lib/dedicated-copy.js +223 -18
- package/dist/lib/dedicated-lease-command.d.ts +7 -1
- package/dist/lib/dedicated-lease-command.js +16 -3
- package/dist/lib/dedicated-machines.d.ts +130 -8
- package/dist/lib/dedicated-machines.js +277 -16
- package/dist/lib/dedicated-terminal.d.ts +5 -25
- package/dist/lib/dedicated-terminal.js +45 -73
- package/dist/lib/dedicated-wait.d.ts +10 -0
- package/dist/lib/dedicated-wait.js +52 -0
- package/dist/lib/device-code.d.ts +12 -1
- package/dist/lib/device-code.js +44 -9
- package/dist/lib/harnesses.d.ts +13 -0
- package/dist/lib/harnesses.js +24 -0
- package/dist/lib/login.js +8 -7
- package/dist/lib/sky-code-broker.d.ts +46 -5
- package/dist/lib/sky-code-broker.js +96 -26
- package/dist/lib/sky-code.d.ts +33 -0
- package/dist/lib/sky-code.js +56 -8
- package/dist/lib/task-instruction-offer.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +67 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +124 -12
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +67 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +123 -11
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3061 -2876
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/index.js +3 -1
- package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/loopback-server.js +64 -1
- package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/managed-inference-broker.d.ts +30 -0
- package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/managed-inference-broker.js +43 -1
- package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/loopback-server.js +64 -1
- package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/managed-inference-broker.d.ts +30 -0
- package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/managed-inference-broker.js +41 -0
- package/dist/node_modules/@skrr-ai/inference-broker/package.json +1 -1
- package/oclif.manifest.json +3113 -3011
- package/package.json +1 -1
|
@@ -3,9 +3,10 @@ import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
|
|
|
3
3
|
* `skrr machines dedicated sign-in` — the lease card's Sign in menu.
|
|
4
4
|
*
|
|
5
5
|
* Opens the machine's terminal and types the tool's sign-in command once the
|
|
6
|
-
* shell is
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* shell is reading a command line, by the rule the web terminal uses
|
|
7
|
+
* (`createPendingShellCommand`). The commands are the SAME fixed strings the
|
|
8
|
+
* web offers, declared once in `@skrr-ai/data-provider`; each is a device-code
|
|
9
|
+
* or paste-a-code flow, because the machine has no browser.
|
|
9
10
|
*/
|
|
10
11
|
export default class DedicatedMachinesSignIn extends DedicatedLeaseCommand {
|
|
11
12
|
static description: string;
|
|
@@ -8,9 +8,10 @@ const TOOLS = data_provider_1.DEDICATED_RUNTIME_SIGN_IN_PRESETS.map((preset) =>
|
|
|
8
8
|
* `skrr machines dedicated sign-in` — the lease card's Sign in menu.
|
|
9
9
|
*
|
|
10
10
|
* Opens the machine's terminal and types the tool's sign-in command once the
|
|
11
|
-
* shell is
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* shell is reading a command line, by the rule the web terminal uses
|
|
12
|
+
* (`createPendingShellCommand`). The commands are the SAME fixed strings the
|
|
13
|
+
* web offers, declared once in `@skrr-ai/data-provider`; each is a device-code
|
|
14
|
+
* or paste-a-code flow, because the machine has no browser.
|
|
14
15
|
*/
|
|
15
16
|
class DedicatedMachinesSignIn extends dedicated_lease_command_1.DedicatedLeaseCommand {
|
|
16
17
|
static description = 'Sign in to GitHub CLI, Claude Code or Codex on a Dedicated Runtime, in an interactive terminal. The sign-in is shared by everything that runs on the machine, agents included.';
|
|
@@ -30,7 +30,7 @@ class DedicatedMachinesTerminal extends dedicated_lease_command_1.DedicatedLease
|
|
|
30
30
|
description: 'Directory to start in (defaults to HOME, /workspace/user-data)',
|
|
31
31
|
}),
|
|
32
32
|
command: core_1.Flags.string({
|
|
33
|
-
description: 'A command to type into the shell once it
|
|
33
|
+
description: 'A command to type into the shell once it shows its prompt, as if you typed it. Not typed if you type first. The shell stays open after it.',
|
|
34
34
|
}),
|
|
35
35
|
};
|
|
36
36
|
async run() {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
|
|
2
|
+
export default class DedicatedMachinesUpdateImage extends DedicatedLeaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
lease: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
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>;
|
|
12
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
class DedicatedMachinesUpdateImage extends dedicated_lease_command_1.DedicatedLeaseCommand {
|
|
7
|
+
static description = 'Move a Dedicated Runtime onto the current root image now, instead of at its next start or restart. The machine is replaced: files under the workspace are kept; memory, shells and background jobs are not. If the new image does not come back healthy, the machine returns to its previous image. With --wait, succeeds only once the move is judged healthy.';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> machines dedicated update-image <lease-id>',
|
|
10
|
+
'<%= config.bin %> machines dedicated update-image <lease-id> --wait',
|
|
11
|
+
'<%= config.bin %> machines dedicated update-image <lease-id> --wait --timeout 20m --json',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
lease: core_1.Args.string({
|
|
15
|
+
description: dedicated_lease_command_1.DEDICATED_LEASE_ARG_DESCRIPTION,
|
|
16
|
+
required: true,
|
|
17
|
+
ignoreStdin: true,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
22
|
+
...dedicated_lease_command_1.dedicatedWaitFlags,
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
this.requireAuth();
|
|
26
|
+
const { args, flags } = await this.parse(DedicatedMachinesUpdateImage);
|
|
27
|
+
const leaseId = await this.resolveLeaseArg(args.lease);
|
|
28
|
+
const response = await this.callDedicated(() => (0, dedicated_machines_1.performDedicatedLeaseAction)(leaseId, 'update-image', this.scopeFrom(flags)));
|
|
29
|
+
// A machine that rolled back to its previous image is running too, so the
|
|
30
|
+
// state cannot say whether the update worked. The update's own record can:
|
|
31
|
+
// the 202 carries the previous one, and success is a newer one, judged healthy.
|
|
32
|
+
await this.reportLeaseAction(response, flags, {
|
|
33
|
+
kind: 'image_updated',
|
|
34
|
+
previousUpdateStartedAt: response?.lease?.health?.image?.lastUpdate?.startedAt ?? null,
|
|
35
|
+
}, 'Updating image of');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.default = DedicatedMachinesUpdateImage;
|
package/dist/lib/balance.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TSkyCodeUsageSnapshot } from '@skrr-ai/data-provider';
|
|
1
2
|
/**
|
|
2
3
|
* balance.ts — request client for `/api/balance`.
|
|
3
4
|
*
|
|
@@ -77,8 +78,7 @@ export interface BalanceHistory {
|
|
|
77
78
|
}
|
|
78
79
|
/** The account's current credits, limit and period usage. */
|
|
79
80
|
export declare function getBalance(): Promise<BalanceStatus>;
|
|
80
|
-
|
|
81
|
-
export declare function getBalanceUsage(): Promise<Record<string, unknown>>;
|
|
81
|
+
export declare function getSkyCodeUsage(workspaceId?: string): Promise<TSkyCodeUsageSnapshot>;
|
|
82
82
|
/** Recent token transactions, newest first. Cursor is an ISO createdAt. */
|
|
83
83
|
export declare function getBalanceHistory(limit: number, cursor?: string): Promise<BalanceHistory>;
|
|
84
84
|
/**
|
package/dist/lib/balance.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CREDITS_PER_USD = void 0;
|
|
4
4
|
exports.getBalance = getBalance;
|
|
5
|
-
exports.
|
|
5
|
+
exports.getSkyCodeUsage = getSkyCodeUsage;
|
|
6
6
|
exports.getBalanceHistory = getBalanceHistory;
|
|
7
7
|
exports.formatCredits = formatCredits;
|
|
8
8
|
exports.getWorkspaceBalance = getWorkspaceBalance;
|
|
@@ -21,9 +21,11 @@ const api_fetch_1 = require("./api-fetch");
|
|
|
21
21
|
async function getBalance() {
|
|
22
22
|
return (0, api_fetch_1.apiFetch)('/api/balance');
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
async function getSkyCodeUsage(workspaceId) {
|
|
25
|
+
const path = workspaceId
|
|
26
|
+
? `/api/balance/usage/sky-code/workspace/${encodeURIComponent(workspaceId)}`
|
|
27
|
+
: '/api/balance/usage/sky-code';
|
|
28
|
+
return (0, api_fetch_1.apiFetch)(path);
|
|
27
29
|
}
|
|
28
30
|
/** Recent token transactions, newest first. Cursor is an ISO createdAt. */
|
|
29
31
|
async function getBalanceHistory(limit, cursor) {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which daemon a command acts on when the caller does not name one.
|
|
3
|
+
*
|
|
4
|
+
* ── Why this is not just "the only active daemon" ──────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* A daemon list is an ACCOUNT fact: every machine the owner has ever connected —
|
|
7
|
+
* a laptop, a Hosted Machine, an E2B sandbox, each Dedicated Runtime. On a
|
|
8
|
+
* laptop that is a fine basis for a default, because the machine a person is
|
|
9
|
+
* typing on is one row among the others and nothing local says which. A
|
|
10
|
+
* Dedicated Runtime guest is different: the CLI there knows, from the machine
|
|
11
|
+
* itself, that it is running on one particular daemon's machine. Choosing from
|
|
12
|
+
* the account list anyway is what OSK-8759 was — `skrr code install` typed into
|
|
13
|
+
* a guest terminal answered `AMBIGUOUS_DAEMON` and offered the owner's laptop and
|
|
14
|
+
* two sandboxes as install targets for a machine that was right there.
|
|
15
|
+
*
|
|
16
|
+
* So the default is the machine this process runs on WHEN the machine can say so,
|
|
17
|
+
* and the account list otherwise. Laptop behaviour is unchanged: nothing below
|
|
18
|
+
* reads anything on a machine that is not a guest.
|
|
19
|
+
*
|
|
20
|
+
* ── The two local signals, and why these two ───────────────────────────────
|
|
21
|
+
*
|
|
22
|
+
* 1. "This process is tenant code on a Dedicated Runtime guest" —
|
|
23
|
+
* {@link isDedicatedRuntimeWorkload}: the workload exec wrapper stamps
|
|
24
|
+
* `SKRR_WORKLOAD_UID` into everything it launches and the process must also
|
|
25
|
+
* BE that uid. Every tenant lane (terminal, Bash tool, `machines dedicated
|
|
26
|
+
* exec`, an engine session) passes through that wrapper, and it is the test
|
|
27
|
+
* the daemon applies itself.
|
|
28
|
+
* 2. "…and this is its daemon" — the daemon's workload CLI hand-off descriptor
|
|
29
|
+
* (`daemon/src/dedicated-cli-handoff.ts`). The daemon writes it into its own
|
|
30
|
+
* unit RuntimeDirectory, which tenant code can read (group `skrr-workspace`)
|
|
31
|
+
* and can neither create nor replace, and it carries the id that daemon
|
|
32
|
+
* registered with. It disappears when the daemon stops.
|
|
33
|
+
*
|
|
34
|
+
* Not used, deliberately: `/etc/skrr-dedicated-runtime/attestation.json` is the
|
|
35
|
+
* IMAGE's record — it proves the image is a Dedicated Runtime and names engine
|
|
36
|
+
* versions, but it is identical on every machine built from that image and names
|
|
37
|
+
* no daemon. The daemon id in the unit's environment (`OVERSKY_DAEMON_ID`) never
|
|
38
|
+
* reaches tenant lanes: the spawn sanitizer strips `OVERSKY_*`, and the human
|
|
39
|
+
* terminal's environment is an allow-list.
|
|
40
|
+
*
|
|
41
|
+
* None of this authorizes anything. The server decides whether this caller may
|
|
42
|
+
* act on that daemon; a wrong answer here only changes which question is asked.
|
|
43
|
+
*/
|
|
44
|
+
import type { ResolvedCredential } from './credential-resolver';
|
|
45
|
+
/** What this process can establish, locally, about the machine it runs on. */
|
|
46
|
+
export interface LocalMachine {
|
|
47
|
+
/** True when this process is tenant code on a Dedicated Runtime guest. */
|
|
48
|
+
dedicatedRuntime: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The id this machine's daemon registered with, when the daemon has published
|
|
51
|
+
* it where this process can read it and for the deployment this CLI talks to.
|
|
52
|
+
* Null otherwise — including on every machine that is not a guest.
|
|
53
|
+
*/
|
|
54
|
+
daemonId: string | null;
|
|
55
|
+
}
|
|
56
|
+
export interface LocalMachineOptions {
|
|
57
|
+
env?: NodeJS.ProcessEnv;
|
|
58
|
+
getuid?: (() => number) | undefined;
|
|
59
|
+
/** The CLI's own base URL. A daemon signed in to another deployment is not one of this account's rows. */
|
|
60
|
+
baseURL?: string;
|
|
61
|
+
/** Test seam only. Production reads the fixed, root-controlled path. */
|
|
62
|
+
descriptorPath?: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function localMachine(options?: LocalMachineOptions): LocalMachine;
|
|
65
|
+
export interface ActiveDaemonRow {
|
|
66
|
+
id: string;
|
|
67
|
+
name?: string;
|
|
68
|
+
isActive?: boolean;
|
|
69
|
+
}
|
|
70
|
+
export type DaemonTarget =
|
|
71
|
+
/**
|
|
72
|
+
* The daemon of the machine this CLI runs on. `explicit` says whether the
|
|
73
|
+
* caller named it. `daemonId` is null when the machine is known to be a guest
|
|
74
|
+
* but its daemon has not published an id this process can read — a command
|
|
75
|
+
* that needs the id must then ask for `--daemon`.
|
|
76
|
+
*/
|
|
77
|
+
{
|
|
78
|
+
kind: 'this-machine';
|
|
79
|
+
machine: 'dedicated-runtime';
|
|
80
|
+
daemonId: string | null;
|
|
81
|
+
explicit: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** A daemon that is not known to be this machine's. */
|
|
84
|
+
| {
|
|
85
|
+
kind: 'daemon';
|
|
86
|
+
daemonId: string;
|
|
87
|
+
explicit: boolean;
|
|
88
|
+
} | {
|
|
89
|
+
kind: 'none';
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'ambiguous';
|
|
92
|
+
daemons: ActiveDaemonRow[];
|
|
93
|
+
};
|
|
94
|
+
export interface ResolveDaemonTargetInput {
|
|
95
|
+
/** The `--daemon` value as typed, decoration and all. */
|
|
96
|
+
explicit?: string;
|
|
97
|
+
local: LocalMachine;
|
|
98
|
+
/** Only consulted when neither the caller nor the machine settles it. */
|
|
99
|
+
listActiveDaemons: () => Promise<ActiveDaemonRow[]>;
|
|
100
|
+
}
|
|
101
|
+
export declare function resolveDaemonTarget(input: ResolveDaemonTargetInput): Promise<DaemonTarget>;
|
|
102
|
+
/** The same list `skrr list-daemons` prints, so a refusal can name values that work. */
|
|
103
|
+
export declare function listActiveDaemons(credential: ResolvedCredential): Promise<ActiveDaemonRow[]>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Which daemon a command acts on when the caller does not name one.
|
|
4
|
+
*
|
|
5
|
+
* ── Why this is not just "the only active daemon" ──────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* A daemon list is an ACCOUNT fact: every machine the owner has ever connected —
|
|
8
|
+
* a laptop, a Hosted Machine, an E2B sandbox, each Dedicated Runtime. On a
|
|
9
|
+
* laptop that is a fine basis for a default, because the machine a person is
|
|
10
|
+
* typing on is one row among the others and nothing local says which. A
|
|
11
|
+
* Dedicated Runtime guest is different: the CLI there knows, from the machine
|
|
12
|
+
* itself, that it is running on one particular daemon's machine. Choosing from
|
|
13
|
+
* the account list anyway is what OSK-8759 was — `skrr code install` typed into
|
|
14
|
+
* a guest terminal answered `AMBIGUOUS_DAEMON` and offered the owner's laptop and
|
|
15
|
+
* two sandboxes as install targets for a machine that was right there.
|
|
16
|
+
*
|
|
17
|
+
* So the default is the machine this process runs on WHEN the machine can say so,
|
|
18
|
+
* and the account list otherwise. Laptop behaviour is unchanged: nothing below
|
|
19
|
+
* reads anything on a machine that is not a guest.
|
|
20
|
+
*
|
|
21
|
+
* ── The two local signals, and why these two ───────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* 1. "This process is tenant code on a Dedicated Runtime guest" —
|
|
24
|
+
* {@link isDedicatedRuntimeWorkload}: the workload exec wrapper stamps
|
|
25
|
+
* `SKRR_WORKLOAD_UID` into everything it launches and the process must also
|
|
26
|
+
* BE that uid. Every tenant lane (terminal, Bash tool, `machines dedicated
|
|
27
|
+
* exec`, an engine session) passes through that wrapper, and it is the test
|
|
28
|
+
* the daemon applies itself.
|
|
29
|
+
* 2. "…and this is its daemon" — the daemon's workload CLI hand-off descriptor
|
|
30
|
+
* (`daemon/src/dedicated-cli-handoff.ts`). The daemon writes it into its own
|
|
31
|
+
* unit RuntimeDirectory, which tenant code can read (group `skrr-workspace`)
|
|
32
|
+
* and can neither create nor replace, and it carries the id that daemon
|
|
33
|
+
* registered with. It disappears when the daemon stops.
|
|
34
|
+
*
|
|
35
|
+
* Not used, deliberately: `/etc/skrr-dedicated-runtime/attestation.json` is the
|
|
36
|
+
* IMAGE's record — it proves the image is a Dedicated Runtime and names engine
|
|
37
|
+
* versions, but it is identical on every machine built from that image and names
|
|
38
|
+
* no daemon. The daemon id in the unit's environment (`OVERSKY_DAEMON_ID`) never
|
|
39
|
+
* reaches tenant lanes: the spawn sanitizer strips `OVERSKY_*`, and the human
|
|
40
|
+
* terminal's environment is an allow-list.
|
|
41
|
+
*
|
|
42
|
+
* None of this authorizes anything. The server decides whether this caller may
|
|
43
|
+
* act on that daemon; a wrong answer here only changes which question is asked.
|
|
44
|
+
*/
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.localMachine = localMachine;
|
|
47
|
+
exports.resolveDaemonTarget = resolveDaemonTarget;
|
|
48
|
+
exports.listActiveDaemons = listActiveDaemons;
|
|
49
|
+
const api_fetch_1 = require("./api-fetch");
|
|
50
|
+
const daemonBroker_1 = require("./daemonBroker");
|
|
51
|
+
const daemon_ref_1 = require("./daemon-ref");
|
|
52
|
+
const sky_code_1 = require("./sky-code");
|
|
53
|
+
function localMachine(options = {}) {
|
|
54
|
+
const env = options.env ?? process.env;
|
|
55
|
+
const getuid = 'getuid' in options ? options.getuid : process.getuid?.bind(process);
|
|
56
|
+
if (!(0, sky_code_1.isDedicatedRuntimeWorkload)(env, getuid))
|
|
57
|
+
return { dedicatedRuntime: false, daemonId: null };
|
|
58
|
+
return { dedicatedRuntime: true, daemonId: publishedDaemonId(options) };
|
|
59
|
+
}
|
|
60
|
+
function publishedDaemonId(options) {
|
|
61
|
+
// `readBootstrap` is the parser the hand-off itself uses, so "a descriptor this
|
|
62
|
+
// CLI would broker through" and "a descriptor this CLI reads an id from" cannot
|
|
63
|
+
// disagree about what a valid one looks like — including a stale one whose
|
|
64
|
+
// daemon has exited.
|
|
65
|
+
const descriptor = (0, daemonBroker_1.readBootstrap)(options.descriptorPath ?? daemonBroker_1.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR);
|
|
66
|
+
const daemonId = typeof descriptor?.daemonId === 'string' ? descriptor.daemonId.trim() : '';
|
|
67
|
+
if (!daemonId)
|
|
68
|
+
return null;
|
|
69
|
+
const published = (0, daemonBroker_1.normalizeBaseUrl)(descriptor?.serverUrl);
|
|
70
|
+
const ours = (0, daemonBroker_1.normalizeBaseUrl)(options.baseURL);
|
|
71
|
+
if (published && ours && published !== ours)
|
|
72
|
+
return null;
|
|
73
|
+
return daemonId;
|
|
74
|
+
}
|
|
75
|
+
async function resolveDaemonTarget(input) {
|
|
76
|
+
const explicit = input.explicit?.trim() ? (0, daemon_ref_1.normalizeDaemonRef)(input.explicit) : '';
|
|
77
|
+
if (explicit) {
|
|
78
|
+
// Exact equality only. The display form is an 8-character prefix, and every
|
|
79
|
+
// guest daemon id begins `dedicated-runtime-`, so a prefix match would call
|
|
80
|
+
// any of the owner's guests "this machine".
|
|
81
|
+
if (input.local.dedicatedRuntime && input.local.daemonId === explicit) {
|
|
82
|
+
return {
|
|
83
|
+
kind: 'this-machine',
|
|
84
|
+
machine: 'dedicated-runtime',
|
|
85
|
+
daemonId: explicit,
|
|
86
|
+
explicit: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { kind: 'daemon', daemonId: explicit, explicit: true };
|
|
90
|
+
}
|
|
91
|
+
if (input.local.dedicatedRuntime) {
|
|
92
|
+
return {
|
|
93
|
+
kind: 'this-machine',
|
|
94
|
+
machine: 'dedicated-runtime',
|
|
95
|
+
daemonId: input.local.daemonId,
|
|
96
|
+
explicit: false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const daemons = (await input.listActiveDaemons()).filter((d) => d.isActive !== false);
|
|
100
|
+
if (daemons.length === 1)
|
|
101
|
+
return { kind: 'daemon', daemonId: daemons[0].id, explicit: false };
|
|
102
|
+
if (daemons.length === 0)
|
|
103
|
+
return { kind: 'none' };
|
|
104
|
+
return { kind: 'ambiguous', daemons };
|
|
105
|
+
}
|
|
106
|
+
/** The same list `skrr list-daemons` prints, so a refusal can name values that work. */
|
|
107
|
+
async function listActiveDaemons(credential) {
|
|
108
|
+
const res = await (0, api_fetch_1.apiFetch)('/api/daemon-resources?status=active&limit=50', { credential });
|
|
109
|
+
return res.daemons ?? [];
|
|
110
|
+
}
|
|
@@ -7,16 +7,106 @@ import { readDedicatedRuntimeFileChunk, writeDedicatedRuntimeFileChunk, type Ded
|
|
|
7
7
|
* machine. Neither end is replaced until the whole file has arrived: an upload
|
|
8
8
|
* stages beside its destination on the machine and the final chunk renames it;
|
|
9
9
|
* a download writes a temporary file beside its local destination and renames.
|
|
10
|
+
*
|
|
11
|
+
* And neither leaves its partial behind when it is cancelled (OSK-8708).
|
|
12
|
+
* `listenForCopyInterrupt` turns SIGINT, SIGTERM and SIGHUP into an
|
|
13
|
+
* AbortSignal the loops honour; a download also removes its temporary file from
|
|
14
|
+
* a process exit handler, for the exits nothing waits for.
|
|
10
15
|
*/
|
|
11
16
|
export interface DedicatedCopyIo {
|
|
12
17
|
readChunk: typeof readDedicatedRuntimeFileChunk;
|
|
13
18
|
writeChunk: typeof writeDedicatedRuntimeFileChunk;
|
|
14
19
|
chunkBytes: number;
|
|
15
20
|
maxBytes: number;
|
|
21
|
+
/** Run `fn` if the process exits; returns the unregister. Injectable for tests. */
|
|
22
|
+
onProcessExit: (fn: () => void) => () => void;
|
|
23
|
+
}
|
|
24
|
+
export interface DedicatedCopyOptions extends Partial<DedicatedCopyIo> {
|
|
25
|
+
/** Cancels the copy, which then removes its partial file before it rejects. */
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
/** Called after every chunk with the bytes copied so far and the file's size. */
|
|
28
|
+
onProgress?: (copied: number, total: number) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A refusal the CLI makes itself, with the code the server uses for the same
|
|
32
|
+
* cause. A file over the limit, a file that changed and a file that is missing
|
|
33
|
+
* each read one way and carry one code whichever side found it; the server's
|
|
34
|
+
* sentences are in `DedicatedRuntimeMachineAccess.mapFileTransferError`
|
|
35
|
+
* (OSK-8714). Before, these local refusals had no code at all.
|
|
36
|
+
*/
|
|
37
|
+
export declare class DedicatedCopyError extends Error {
|
|
38
|
+
readonly code: 'DEDICATED_RUNTIME_FILE_TOO_LARGE' | 'DEDICATED_RUNTIME_FILE_CHANGED' | 'DEDICATED_RUNTIME_FILE_NOT_FOUND' | 'DEDICATED_RUNTIME_PATH_NOT_A_FILE' | 'DEDICATED_RUNTIME_FILE_TRANSFER_FAILED';
|
|
39
|
+
constructor(code: 'DEDICATED_RUNTIME_FILE_TOO_LARGE' | 'DEDICATED_RUNTIME_FILE_CHANGED' | 'DEDICATED_RUNTIME_FILE_NOT_FOUND' | 'DEDICATED_RUNTIME_PATH_NOT_A_FILE' | 'DEDICATED_RUNTIME_FILE_TRANSFER_FAILED', message: string);
|
|
40
|
+
}
|
|
41
|
+
declare const SIGNAL_NUMBERS: {
|
|
42
|
+
readonly SIGHUP: 1;
|
|
43
|
+
readonly SIGINT: 2;
|
|
44
|
+
readonly SIGTERM: 15;
|
|
45
|
+
};
|
|
46
|
+
export type CopyInterruptSignal = keyof typeof SIGNAL_NUMBERS;
|
|
47
|
+
/** A copy stopped by SIGINT, SIGTERM or SIGHUP. Exits 128 + the signal's number, as a shell would. */
|
|
48
|
+
export declare class DedicatedCopyCancelledError extends Error {
|
|
49
|
+
readonly signalName: CopyInterruptSignal;
|
|
50
|
+
readonly code = "DEDICATED_RUNTIME_COPY_CANCELLED";
|
|
51
|
+
readonly exitCode: number;
|
|
52
|
+
constructor(signalName: CopyInterruptSignal);
|
|
16
53
|
}
|
|
54
|
+
export interface CopyInterruptListener {
|
|
55
|
+
/** Aborted by the first SIGINT, SIGTERM or SIGHUP, with a DedicatedCopyCancelledError as its reason. */
|
|
56
|
+
signal: AbortSignal;
|
|
57
|
+
/** The signal that cancelled the copy, if one did. */
|
|
58
|
+
interruptedBy: () => CopyInterruptSignal | null;
|
|
59
|
+
/** Stop listening. Call it once the copy has settled. */
|
|
60
|
+
dispose: () => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Listen for SIGINT, SIGTERM and SIGHUP for the length of one copy.
|
|
64
|
+
*
|
|
65
|
+
* An ordinary command-level listener, nothing more. The lock-release handler
|
|
66
|
+
* every command inherits defers to it (`installLockExitHandlers`,
|
|
67
|
+
* packages/auth-core/src/refresh.ts): the first signal reaches this listener,
|
|
68
|
+
* which aborts the copy so the loop removes its partial and rejects; a second
|
|
69
|
+
* signal, or a cleanup that hangs past auth-core's fallback, exits the process,
|
|
70
|
+
* and a download's exit handler still removes its temporary file then.
|
|
71
|
+
*/
|
|
72
|
+
export declare function listenForCopyInterrupt({ proc, notice, }?: {
|
|
73
|
+
proc?: Pick<NodeJS.EventEmitter, 'on' | 'removeListener'>;
|
|
74
|
+
notice?: (message: string) => void;
|
|
75
|
+
}): CopyInterruptListener;
|
|
17
76
|
export interface DedicatedCopyResult {
|
|
18
77
|
bytes: number;
|
|
19
78
|
destination: string;
|
|
20
79
|
}
|
|
21
|
-
|
|
22
|
-
|
|
80
|
+
/**
|
|
81
|
+
* One line of progress for a terminal, redrawn in place: a 60 MiB copy takes
|
|
82
|
+
* about forty seconds, and printed nothing until it ended (OSK-8714). The
|
|
83
|
+
* caller decides whether there is a terminal to draw on.
|
|
84
|
+
*/
|
|
85
|
+
export declare function createCopyProgress({ label, write, now, }: {
|
|
86
|
+
label: string;
|
|
87
|
+
write: (text: string) => void;
|
|
88
|
+
now?: () => number;
|
|
89
|
+
}): {
|
|
90
|
+
update: (copied: number, total: number) => void;
|
|
91
|
+
clear: () => void;
|
|
92
|
+
};
|
|
93
|
+
/** Whether a CI system is running this. Progress is for a person watching. */
|
|
94
|
+
export declare function isContinuousIntegration(env?: NodeJS.ProcessEnv): boolean;
|
|
95
|
+
/** `dir/`, `dir/.` or `.`: a machine path that names a directory to copy into. */
|
|
96
|
+
export declare function namesRemoteDirectory(remotePath: string): boolean;
|
|
97
|
+
/** `name` inside the machine directory `directory`, however it was spelled. */
|
|
98
|
+
export declare function intoRemoteDirectory(directory: string, name: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Upload one file. A destination that is a directory — spelled `dir/`, or an
|
|
101
|
+
* existing directory named without the slash — receives the file under its own
|
|
102
|
+
* name, as `cp` and `scp` do and as a download into a local directory already
|
|
103
|
+
* did. Before, `cp tiny.txt <lease>:repos/scout-a/` sent the whole file and then
|
|
104
|
+
* failed the machine's final rename with EISDIR (OSK-8713).
|
|
105
|
+
*
|
|
106
|
+
* `dir/` is resolved here, without asking. A directory named without the slash
|
|
107
|
+
* is found out by the machine, which refuses it on the first chunk before
|
|
108
|
+
* anything is staged; the copy then goes into it, once.
|
|
109
|
+
*/
|
|
110
|
+
export declare function uploadToDedicatedRuntime(localPath: string, leaseId: string, remotePath: string, scope?: DedicatedRuntimeScope, io?: DedicatedCopyOptions): Promise<DedicatedCopyResult>;
|
|
111
|
+
export declare function downloadFromDedicatedRuntime(leaseId: string, remotePath: string, localPath: string, scope?: DedicatedRuntimeScope, io?: DedicatedCopyOptions): Promise<DedicatedCopyResult>;
|
|
112
|
+
export {};
|