@skrr-ai/cli 0.1.88 → 0.1.90
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/commands/machines/computer-adoption.d.ts +3 -2
- package/dist/commands/machines/computer-adoption.js +4 -3
- package/dist/commands/machines/dedicated/agent-browser.d.ts +25 -0
- package/dist/commands/machines/dedicated/agent-browser.js +65 -0
- package/dist/commands/machines/dedicated/exec.js +60 -14
- package/dist/commands/machines/dedicated/index.js +2 -0
- package/dist/commands/machines/dedicated/power/explain.d.ts +13 -0
- package/dist/commands/machines/dedicated/power/explain.js +36 -0
- package/dist/commands/machines/dedicated/power/index.d.ts +10 -0
- package/dist/commands/machines/dedicated/power/index.js +24 -0
- package/dist/commands/machines/dedicated/power/keep-awake.d.ts +20 -0
- package/dist/commands/machines/dedicated/power/keep-awake.js +69 -0
- package/dist/commands/machines/dedicated/power/set.d.ts +23 -0
- package/dist/commands/machines/dedicated/power/set.js +106 -0
- package/dist/commands/machines/dedicated/power/show.d.ts +13 -0
- package/dist/commands/machines/dedicated/power/show.js +36 -0
- package/dist/commands/machines/dedicated/show.js +17 -2
- package/dist/lib/commitments.d.ts +1 -1
- package/dist/lib/commitments.js +4 -2
- package/dist/lib/computer-adoption.d.ts +5 -0
- package/dist/lib/computer-adoption.js +6 -1
- package/dist/lib/computer-consent.d.ts +43 -1
- package/dist/lib/computer-consent.js +125 -4
- package/dist/lib/dedicated-keep-awake.d.ts +19 -0
- package/dist/lib/dedicated-keep-awake.js +64 -0
- package/dist/lib/dedicated-machines.d.ts +31 -0
- package/dist/lib/dedicated-machines.js +40 -0
- package/dist/lib/dedicated-power.d.ts +61 -0
- package/dist/lib/dedicated-power.js +243 -0
- package/dist/lib/exec-runtime-binary.d.ts +3 -1
- package/dist/lib/exec-runtime-binary.js +16 -2
- package/dist/lib/format.d.ts +13 -0
- package/dist/lib/format.js +51 -0
- package/dist/lib/keychain.js +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +135 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +166 -16
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeBusyWire.d.ts +107 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeBusyWire.js +151 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeWakeWire.d.ts +115 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dedicatedRuntimeWakeWire.js +117 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +8 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +31 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +149 -9
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +135 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +161 -13
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeBusyWire.d.ts +107 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeBusyWire.js +147 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeWakeWire.d.ts +115 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dedicatedRuntimeWakeWire.js +113 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +31 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +144 -8
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +6730 -6366
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/oclif.manifest.json +6303 -5852
- package/package.json +2 -2
|
@@ -744,7 +744,7 @@ export declare function describeAutonomyLine(commitment: CommitmentView, latestC
|
|
|
744
744
|
} | null): string;
|
|
745
745
|
/**
|
|
746
746
|
* One-line rendering of `commitment.executorBlocked` — the B4/A7 signal that
|
|
747
|
-
* the executor's targets are all unusable: `{reason:'quota'|'offline', until?,
|
|
747
|
+
* the executor's targets are all unusable: `{reason:'quota'|'offline'|'waiting_for_machine', until?,
|
|
748
748
|
* provider?}`.
|
|
749
749
|
*
|
|
750
750
|
* Defensive by contract: the field is being added server-side, so its absence
|
package/dist/lib/commitments.js
CHANGED
|
@@ -874,7 +874,7 @@ function describeAutonomyLine(commitment, latestCheck) {
|
|
|
874
874
|
}
|
|
875
875
|
/**
|
|
876
876
|
* One-line rendering of `commitment.executorBlocked` — the B4/A7 signal that
|
|
877
|
-
* the executor's targets are all unusable: `{reason:'quota'|'offline', until?,
|
|
877
|
+
* the executor's targets are all unusable: `{reason:'quota'|'offline'|'waiting_for_machine', until?,
|
|
878
878
|
* provider?}`.
|
|
879
879
|
*
|
|
880
880
|
* Defensive by contract: the field is being added server-side, so its absence
|
|
@@ -893,7 +893,9 @@ function describeExecutorBlocked(commitment) {
|
|
|
893
893
|
? `quota exhausted${provider ? ` on ${provider}` : ''}`
|
|
894
894
|
: reason === 'offline'
|
|
895
895
|
? 'executor runtime offline'
|
|
896
|
-
: reason
|
|
896
|
+
: reason === 'waiting_for_machine'
|
|
897
|
+
? 'executor machine is stopped and will be started for the work'
|
|
898
|
+
: reason || 'blocked';
|
|
897
899
|
return `${label}${until ? ` until ${until}` : ''}`;
|
|
898
900
|
}
|
|
899
901
|
function renderCommitmentSummary(commitment, log,
|
|
@@ -45,6 +45,11 @@ export interface ComputerAdoption {
|
|
|
45
45
|
oldFamilies: string[];
|
|
46
46
|
daemonHalfMet: boolean | null;
|
|
47
47
|
mobileHalfMet?: boolean | null;
|
|
48
|
+
/** Set once the families were retired; absent from a server before OSK-13509. */
|
|
49
|
+
retired?: {
|
|
50
|
+
on: string;
|
|
51
|
+
reason: string;
|
|
52
|
+
};
|
|
48
53
|
};
|
|
49
54
|
}
|
|
50
55
|
/**
|
|
@@ -61,7 +61,12 @@ function computerAdoptionLines(read) {
|
|
|
61
61
|
verdict = 'not measurable: no active daemons';
|
|
62
62
|
else if (criterion.daemonHalfMet)
|
|
63
63
|
verdict = 'MET';
|
|
64
|
-
lines.push('', `Retirement of ${criterion.oldFamilies.join(', ')}
|
|
64
|
+
lines.push('', `Retirement of ${criterion.oldFamilies.join(', ')}`);
|
|
65
|
+
if (criterion.retired) {
|
|
66
|
+
// The families are gone: the counts above are who it stranded, not who is still served.
|
|
67
|
+
lines.push(` RETIRED ${criterion.retired.on}: ${criterion.retired.reason}`, ' old-families-only daemons above can reach no terminal or browser; each is told to update when it registers');
|
|
68
|
+
}
|
|
69
|
+
lines.push(` daemon half (< ${(criterion.maxLegacyOnlyShare * 100).toFixed(0)}% of daemons active in ${criterion.windowDays} days on the old families only): ${verdict}`, mobileHalfLine(read));
|
|
65
70
|
lines.push(...clientLines(read));
|
|
66
71
|
return lines;
|
|
67
72
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
import { type RuntimeBinaryOutput } from './exec-runtime-binary';
|
|
2
3
|
/**
|
|
3
4
|
* The bridge for `skrr computer` — the machine-local consent for the
|
|
4
5
|
* computer's new surfaces on a personal machine (design §18, D11;
|
|
@@ -10,4 +11,45 @@ import { Command } from '@oclif/core';
|
|
|
10
11
|
* Keeping the CLI a thin forwarder preserves that boundary: `skrr` carries
|
|
11
12
|
* the verb to the same binary `skrrd` runs it on.
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
+
/** Bound on the `skrrd status --json` probe: it is a read, and must never stall the command. */
|
|
15
|
+
export declare const DAEMON_STATUS_PROBE_TIMEOUT_MS = 8000;
|
|
16
|
+
/** What `skrrd status --json` says about the daemon that would answer. */
|
|
17
|
+
export interface AnsweringDaemon {
|
|
18
|
+
/** The binary `skrr` forwards to. */
|
|
19
|
+
binary: string | null;
|
|
20
|
+
/** The on-disk binary's version (`statusCommandVersion`), when it said. */
|
|
21
|
+
binaryVersion: string | null;
|
|
22
|
+
/** The running daemon's version (`liveVersion`/`version`), when it said. */
|
|
23
|
+
runningVersion: string | null;
|
|
24
|
+
daemonId: string | null;
|
|
25
|
+
profile: string | null;
|
|
26
|
+
/** `update.state` — `source-build`, `update-available`, `restart-pending`, … */
|
|
27
|
+
updateState: string | null;
|
|
28
|
+
/** `update.commands`: the install-method-correct way to update THIS daemon. */
|
|
29
|
+
updateCommands: string[];
|
|
30
|
+
restartPending: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface ComputerConsentDeps {
|
|
33
|
+
supportsSubcommand: (subcommand: string) => Promise<boolean | null>;
|
|
34
|
+
exec: (args: string[]) => Promise<number>;
|
|
35
|
+
capture: (args: string[], opts?: {
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
}) => Promise<RuntimeBinaryOutput>;
|
|
38
|
+
findBinary: () => string | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read `skrrd status --json` for the facts a person needs to know WHICH daemon
|
|
42
|
+
* answered and how it updates. Null when the status cannot be read — the
|
|
43
|
+
* caller then says what it can without inventing the rest.
|
|
44
|
+
*/
|
|
45
|
+
export declare function readAnsweringDaemon(passthrough?: readonly string[], deps?: ComputerConsentDeps): Promise<AnsweringDaemon | null>;
|
|
46
|
+
/** "skrrd 0.8.103 at /path (daemon_…, running 0.8.90)" — whatever of that is known. */
|
|
47
|
+
export declare function describeAnsweringDaemon(daemon: AnsweringDaemon | null, binary: string | null): string;
|
|
48
|
+
/**
|
|
49
|
+
* What to tell a person whose local daemon has no `computer` verb: which
|
|
50
|
+
* daemon answered, and the update path for THAT install (OSK-13822). A source
|
|
51
|
+
* build updates by rebuilding its checkout — `skrrd status --json` already
|
|
52
|
+
* carries that recipe under `update.commands` — not by reinstalling.
|
|
53
|
+
*/
|
|
54
|
+
export declare function computerUnsupportedMessage(daemon: AnsweringDaemon | null, binary: string | null): string;
|
|
55
|
+
export declare function forwardComputerConsent(command: Command, passthrough: readonly string[], deps?: ComputerConsentDeps): Promise<void>;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DAEMON_STATUS_PROBE_TIMEOUT_MS = void 0;
|
|
4
|
+
exports.readAnsweringDaemon = readAnsweringDaemon;
|
|
5
|
+
exports.describeAnsweringDaemon = describeAnsweringDaemon;
|
|
6
|
+
exports.computerUnsupportedMessage = computerUnsupportedMessage;
|
|
3
7
|
exports.forwardComputerConsent = forwardComputerConsent;
|
|
4
8
|
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
5
9
|
/**
|
|
@@ -13,15 +17,132 @@ const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
|
13
17
|
* Keeping the CLI a thin forwarder preserves that boundary: `skrr` carries
|
|
14
18
|
* the verb to the same binary `skrrd` runs it on.
|
|
15
19
|
*/
|
|
16
|
-
|
|
20
|
+
/** Bound on the `skrrd status --json` probe: it is a read, and must never stall the command. */
|
|
21
|
+
exports.DAEMON_STATUS_PROBE_TIMEOUT_MS = 8_000;
|
|
22
|
+
const defaultDeps = {
|
|
23
|
+
supportsSubcommand: exec_runtime_binary_1.runtimeBinarySupportsSubcommand,
|
|
24
|
+
exec: (args) => (0, exec_runtime_binary_1.execRuntimeBinary)(args),
|
|
25
|
+
capture: (args, opts) => (0, exec_runtime_binary_1.execRuntimeBinaryCapture)(args, opts),
|
|
26
|
+
findBinary: exec_runtime_binary_1.findRuntimeBinary,
|
|
27
|
+
};
|
|
28
|
+
function text(value) {
|
|
29
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Read `skrrd status --json` for the facts a person needs to know WHICH daemon
|
|
33
|
+
* answered and how it updates. Null when the status cannot be read — the
|
|
34
|
+
* caller then says what it can without inventing the rest.
|
|
35
|
+
*/
|
|
36
|
+
async function readAnsweringDaemon(passthrough = [], deps = defaultDeps) {
|
|
37
|
+
let output;
|
|
38
|
+
try {
|
|
39
|
+
output = await deps.capture([...profileArgs(passthrough), 'status', '--json'], {
|
|
40
|
+
timeoutMs: exports.DAEMON_STATUS_PROBE_TIMEOUT_MS,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
let status;
|
|
47
|
+
try {
|
|
48
|
+
status = JSON.parse(output.stdout);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (!status || typeof status !== 'object')
|
|
54
|
+
return null;
|
|
55
|
+
const update = status.update && typeof status.update === 'object'
|
|
56
|
+
? status.update
|
|
57
|
+
: {};
|
|
58
|
+
return {
|
|
59
|
+
binary: deps.findBinary(),
|
|
60
|
+
binaryVersion: text(status.statusCommandVersion),
|
|
61
|
+
runningVersion: text(status.liveVersion) ?? text(status.version),
|
|
62
|
+
daemonId: text(status.daemonId),
|
|
63
|
+
profile: text(status.profile),
|
|
64
|
+
updateState: text(update.state),
|
|
65
|
+
updateCommands: Array.isArray(update.commands)
|
|
66
|
+
? update.commands.filter((c) => typeof c === 'string' && c.trim() !== '')
|
|
67
|
+
: [],
|
|
68
|
+
restartPending: update.restartPending === true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** `--profile <name>` from the pass-through, so the status probe asks the same daemon. */
|
|
72
|
+
function profileArgs(passthrough) {
|
|
73
|
+
for (let i = 0; i < passthrough.length; i += 1) {
|
|
74
|
+
const arg = passthrough[i];
|
|
75
|
+
if (arg === '--profile' && passthrough[i + 1])
|
|
76
|
+
return ['--profile', passthrough[i + 1]];
|
|
77
|
+
if (arg.startsWith('--profile='))
|
|
78
|
+
return ['--profile', arg.slice('--profile='.length)];
|
|
79
|
+
}
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
/** "skrrd 0.8.103 at /path (daemon_…, running 0.8.90)" — whatever of that is known. */
|
|
83
|
+
function describeAnsweringDaemon(daemon, binary) {
|
|
84
|
+
const where = daemon?.binary ?? binary;
|
|
85
|
+
const parts = [];
|
|
86
|
+
parts.push(`${where ?? 'skrrd'}${daemon?.binaryVersion ? ` (v${daemon.binaryVersion} on disk)` : ''}`);
|
|
87
|
+
if (daemon?.runningVersion && daemon.runningVersion !== daemon.binaryVersion) {
|
|
88
|
+
parts.push(`running daemon v${daemon.runningVersion}`);
|
|
89
|
+
}
|
|
90
|
+
else if (daemon?.runningVersion && !daemon.binaryVersion) {
|
|
91
|
+
parts.push(`v${daemon.runningVersion}`);
|
|
92
|
+
}
|
|
93
|
+
if (daemon?.daemonId)
|
|
94
|
+
parts.push(daemon.daemonId);
|
|
95
|
+
return parts.join(', ');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* What to tell a person whose local daemon has no `computer` verb: which
|
|
99
|
+
* daemon answered, and the update path for THAT install (OSK-13822). A source
|
|
100
|
+
* build updates by rebuilding its checkout — `skrrd status --json` already
|
|
101
|
+
* carries that recipe under `update.commands` — not by reinstalling.
|
|
102
|
+
*/
|
|
103
|
+
function computerUnsupportedMessage(daemon, binary) {
|
|
104
|
+
const lines = [
|
|
105
|
+
`The local daemon does not support \`skrr computer\` yet: ${describeAnsweringDaemon(daemon, binary)}.`,
|
|
106
|
+
];
|
|
107
|
+
if (daemon?.updateState === 'source-build') {
|
|
108
|
+
const recipe = daemon.updateCommands.length
|
|
109
|
+
? daemon.updateCommands
|
|
110
|
+
: ['cd <daemon-dir> && git pull && npm run build'];
|
|
111
|
+
lines.push('It runs from a source checkout, which updates by rebuilding:');
|
|
112
|
+
for (const command of recipe)
|
|
113
|
+
lines.push(` ${command}`);
|
|
114
|
+
lines.push('Then restart it:', ' skrr daemon restart');
|
|
115
|
+
}
|
|
116
|
+
else if (daemon?.restartPending) {
|
|
117
|
+
lines.push('A newer daemon is already on disk; the running one predates it. Restart it:', ' skrr daemon restart');
|
|
118
|
+
}
|
|
119
|
+
else if (daemon && daemon.updateCommands.length > 0) {
|
|
120
|
+
lines.push('Update it:');
|
|
121
|
+
for (const command of daemon.updateCommands)
|
|
122
|
+
lines.push(` ${command}`);
|
|
123
|
+
lines.push('Then restart it:', ' skrr daemon restart');
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
lines.push('Update it (`skrr daemon update`, or reinstall it), restart it (`skrr daemon restart`), then retry.');
|
|
127
|
+
}
|
|
128
|
+
return lines.join('\n');
|
|
129
|
+
}
|
|
130
|
+
async function forwardComputerConsent(command, passthrough, deps = defaultDeps) {
|
|
17
131
|
try {
|
|
18
132
|
// Older daemons treated unknown subcommands as a daemon startup in some
|
|
19
133
|
// releases. Probe the command list before forwarding so a consent verb
|
|
20
134
|
// cannot turn into a surprising service mutation.
|
|
21
|
-
if ((await
|
|
22
|
-
|
|
135
|
+
if ((await deps.supportsSubcommand('computer')) === false) {
|
|
136
|
+
const daemon = await readAnsweringDaemon(passthrough, deps);
|
|
137
|
+
command.error(computerUnsupportedMessage(daemon, deps.findBinary()), { exit: 2 });
|
|
138
|
+
}
|
|
139
|
+
if (passthrough.includes('status')) {
|
|
140
|
+
// Which daemon answered, before its answer — on stderr, so `--json`
|
|
141
|
+
// output stays exactly what the daemon printed.
|
|
142
|
+
const daemon = await readAnsweringDaemon(passthrough, deps);
|
|
143
|
+
command.logToStderr(`Answered by: ${describeAnsweringDaemon(daemon, deps.findBinary())}`);
|
|
23
144
|
}
|
|
24
|
-
command.exit(await
|
|
145
|
+
command.exit(await deps.exec(['computer', ...passthrough]));
|
|
25
146
|
}
|
|
26
147
|
catch (error) {
|
|
27
148
|
if (error.message === 'runtime-binary-not-installed') {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type DedicatedRuntimeKeepAwakeResponse, type DedicatedRuntimeKeepAwakeState, type SetDedicatedRuntimeKeepAwakeRequest } from '@skrr-ai/data-provider';
|
|
2
|
+
import { type WhenOptions } from './format';
|
|
3
|
+
import { type DedicatedRuntimeScope } from './dedicated-machines';
|
|
4
|
+
/**
|
|
5
|
+
* Domain lib for `skrr machines dedicated power keep-awake` (K1). Keep-awake is a
|
|
6
|
+
* temporary override that forbids AUTOMATIC stop; it never starts the machine.
|
|
7
|
+
* The web calls the same routes through `dataService.setDedicatedRuntimeKeepAwake`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function setDedicatedKeepAwake(leaseId: string, request: SetDedicatedRuntimeKeepAwakeRequest, scope?: DedicatedRuntimeScope): Promise<DedicatedRuntimeKeepAwakeResponse>;
|
|
10
|
+
export declare function clearDedicatedKeepAwake(leaseId: string, scope?: DedicatedRuntimeScope): Promise<DedicatedRuntimeKeepAwakeResponse>;
|
|
11
|
+
/**
|
|
12
|
+
* `--for` takes a whole number with a unit: `90m`, `2h`, `3d`. A bare number is
|
|
13
|
+
* minutes (the unit the API speaks). Returns minutes, or throws a plain message.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseKeepAwakeDuration(value: string): number;
|
|
16
|
+
export declare function assertKeepAwakeMinutes(minutes: number): void;
|
|
17
|
+
/** `--until` takes any ISO 8601 date. The server re-checks the bounds. */
|
|
18
|
+
export declare function parseKeepAwakeUntil(value: string): string;
|
|
19
|
+
export declare function describeKeepAwake(state: DedicatedRuntimeKeepAwakeState, when?: WhenOptions): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setDedicatedKeepAwake = setDedicatedKeepAwake;
|
|
4
|
+
exports.clearDedicatedKeepAwake = clearDedicatedKeepAwake;
|
|
5
|
+
exports.parseKeepAwakeDuration = parseKeepAwakeDuration;
|
|
6
|
+
exports.assertKeepAwakeMinutes = assertKeepAwakeMinutes;
|
|
7
|
+
exports.parseKeepAwakeUntil = parseKeepAwakeUntil;
|
|
8
|
+
exports.describeKeepAwake = describeKeepAwake;
|
|
9
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
10
|
+
const api_fetch_1 = require("./api-fetch");
|
|
11
|
+
const format_1 = require("./format");
|
|
12
|
+
const dedicated_machines_1 = require("./dedicated-machines");
|
|
13
|
+
/**
|
|
14
|
+
* Domain lib for `skrr machines dedicated power keep-awake` (K1). Keep-awake is a
|
|
15
|
+
* temporary override that forbids AUTOMATIC stop; it never starts the machine.
|
|
16
|
+
* The web calls the same routes through `dataService.setDedicatedRuntimeKeepAwake`.
|
|
17
|
+
*/
|
|
18
|
+
function setDedicatedKeepAwake(leaseId, request, scope = {}) {
|
|
19
|
+
return (0, api_fetch_1.apiFetch)((0, dedicated_machines_1.withDedicatedScope)(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/power/keep-awake`, scope), {
|
|
20
|
+
method: 'PUT',
|
|
21
|
+
body: request,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function clearDedicatedKeepAwake(leaseId, scope = {}) {
|
|
25
|
+
return (0, api_fetch_1.apiFetch)((0, dedicated_machines_1.withDedicatedScope)(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/power/keep-awake`, scope), {
|
|
26
|
+
method: 'DELETE',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `--for` takes a whole number with a unit: `90m`, `2h`, `3d`. A bare number is
|
|
31
|
+
* minutes (the unit the API speaks). Returns minutes, or throws a plain message.
|
|
32
|
+
*/
|
|
33
|
+
function parseKeepAwakeDuration(value) {
|
|
34
|
+
const match = value.trim().match(/^(\d+)\s*(m|h|d)?$/i);
|
|
35
|
+
if (!match) {
|
|
36
|
+
throw new Error(`Invalid --for value "${value}". Use a number with m, h or d, such as 90m, 2h or 3d.`);
|
|
37
|
+
}
|
|
38
|
+
const amount = Number(match[1]);
|
|
39
|
+
const unit = (match[2] || 'm').toLowerCase();
|
|
40
|
+
const minutes = unit === 'd' ? amount * 1440 : unit === 'h' ? amount * 60 : amount;
|
|
41
|
+
assertKeepAwakeMinutes(minutes);
|
|
42
|
+
return minutes;
|
|
43
|
+
}
|
|
44
|
+
function assertKeepAwakeMinutes(minutes) {
|
|
45
|
+
if (minutes < data_provider_1.DEDICATED_KEEP_AWAKE_MINUTES_MIN) {
|
|
46
|
+
throw new Error(`Keep-awake must last at least ${data_provider_1.DEDICATED_KEEP_AWAKE_MINUTES_MIN} minutes.`);
|
|
47
|
+
}
|
|
48
|
+
if (minutes > data_provider_1.DEDICATED_KEEP_AWAKE_MINUTES_MAX) {
|
|
49
|
+
throw new Error(`Keep-awake can last at most ${data_provider_1.DEDICATED_KEEP_AWAKE_MINUTES_MAX / 1440} days. For longer, use \`power set --policy always_on\`.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** `--until` takes any ISO 8601 date. The server re-checks the bounds. */
|
|
53
|
+
function parseKeepAwakeUntil(value) {
|
|
54
|
+
const at = new Date(value);
|
|
55
|
+
if (Number.isNaN(at.getTime())) {
|
|
56
|
+
throw new Error(`Invalid --until value "${value}". Use an ISO date such as 2026-10-10T18:00:00Z.`);
|
|
57
|
+
}
|
|
58
|
+
return at.toISOString();
|
|
59
|
+
}
|
|
60
|
+
function describeKeepAwake(state, when = {}) {
|
|
61
|
+
return state.active && state.until
|
|
62
|
+
? `is kept awake until ${(0, format_1.formatWhen)(state.until, when)}`
|
|
63
|
+
: 'has no keep-awake; it follows its power policy';
|
|
64
|
+
}
|
|
@@ -358,6 +358,34 @@ export declare function restoreDeletedDedicatedRuntime(leaseId: string, options?
|
|
|
358
358
|
* on the next reconciler pass.
|
|
359
359
|
*/
|
|
360
360
|
export declare function setDedicatedRuntimeSpendingLimit(leaseId: string, spendingLimitCents: number | null, scope?: DedicatedRuntimeScope): Promise<DedicatedRuntimeResponse>;
|
|
361
|
+
/**
|
|
362
|
+
* Whether the agents working on a Dedicated Runtime get a browser (OSK-13809).
|
|
363
|
+
*
|
|
364
|
+
* `state` is the machine's setting; `offered` is whether its CONNECTED daemon
|
|
365
|
+
* offers the browser right now (null: nothing connected, or unreadable). The
|
|
366
|
+
* two differ after a change until the daemon reconnects, and on an image
|
|
367
|
+
* without Chrome, which never offers it.
|
|
368
|
+
*/
|
|
369
|
+
export type DedicatedAgentBrowserState = {
|
|
370
|
+
leaseId: string;
|
|
371
|
+
state: 'on' | 'off' | 'pending_first_start';
|
|
372
|
+
enabled: boolean;
|
|
373
|
+
offered: boolean | null;
|
|
374
|
+
capability: string;
|
|
375
|
+
changed?: boolean;
|
|
376
|
+
effectiveOnNextReconnect?: boolean;
|
|
377
|
+
};
|
|
378
|
+
export declare function getDedicatedRuntimeAgentBrowser(leaseId: string, scope?: DedicatedRuntimeScope): Promise<DedicatedAgentBrowserState>;
|
|
379
|
+
/** Turn the agents' browser on or off. An actual change is in the machine's audit record. */
|
|
380
|
+
export declare function setDedicatedRuntimeAgentBrowser(leaseId: string, enabled: boolean, scope?: DedicatedRuntimeScope): Promise<DedicatedAgentBrowserState>;
|
|
381
|
+
/**
|
|
382
|
+
* One line for a person. Says the bad cases out loud: a setting that is on
|
|
383
|
+
* while the connected daemon does not offer it yet, and a setting we could
|
|
384
|
+
* not read at all.
|
|
385
|
+
*/
|
|
386
|
+
export declare function describeDedicatedAgentBrowser(value: DedicatedAgentBrowserState | {
|
|
387
|
+
error: string;
|
|
388
|
+
} | null | undefined, leaseId: string): string;
|
|
361
389
|
export declare function closeDedicatedRuntimeIncidentHold(leaseId: string, options?: {
|
|
362
390
|
requestId?: string;
|
|
363
391
|
} & DedicatedRuntimeScope): Promise<DedicatedRuntimeResponse>;
|
|
@@ -627,6 +655,9 @@ export declare function dedicatedSpendSummary(lease: DedicatedLease): string;
|
|
|
627
655
|
export declare function describeDedicatedDeletion(deletion: DedicatedLeaseDeletion, leaseId?: string): string;
|
|
628
656
|
export declare function describeDedicatedLease(lease: DedicatedLease, harnesses?: readonly Harness[], opts?: {
|
|
629
657
|
latestDaemonRelease?: LatestDaemonRelease | null;
|
|
658
|
+
agentBrowser?: DedicatedAgentBrowserState | {
|
|
659
|
+
error: string;
|
|
660
|
+
} | null;
|
|
630
661
|
}): string[];
|
|
631
662
|
/** "2026-09-30 14:00 UTC (in 17 days)" — a deadline readable without converting it. */
|
|
632
663
|
export declare function formatDedicatedDeadline(value: string | null | undefined, now?: Date): string;
|
|
@@ -17,6 +17,9 @@ exports.performDedicatedLeaseAction = performDedicatedLeaseAction;
|
|
|
17
17
|
exports.growDedicatedRuntime = growDedicatedRuntime;
|
|
18
18
|
exports.restoreDeletedDedicatedRuntime = restoreDeletedDedicatedRuntime;
|
|
19
19
|
exports.setDedicatedRuntimeSpendingLimit = setDedicatedRuntimeSpendingLimit;
|
|
20
|
+
exports.getDedicatedRuntimeAgentBrowser = getDedicatedRuntimeAgentBrowser;
|
|
21
|
+
exports.setDedicatedRuntimeAgentBrowser = setDedicatedRuntimeAgentBrowser;
|
|
22
|
+
exports.describeDedicatedAgentBrowser = describeDedicatedAgentBrowser;
|
|
20
23
|
exports.closeDedicatedRuntimeIncidentHold = closeDedicatedRuntimeIncidentHold;
|
|
21
24
|
exports.destroyDedicatedRuntime = destroyDedicatedRuntime;
|
|
22
25
|
exports.execDedicatedRuntime = execDedicatedRuntime;
|
|
@@ -368,6 +371,40 @@ async function restoreDeletedDedicatedRuntime(leaseId, options = {}) {
|
|
|
368
371
|
async function setDedicatedRuntimeSpendingLimit(leaseId, spendingLimitCents, scope = {}) {
|
|
369
372
|
return (0, api_fetch_1.apiFetch)(withDedicatedScope(`/api/machines/leases/${encodeURIComponent(leaseId)}/billing`, scope), { method: 'PATCH', body: { spendingLimitCents } });
|
|
370
373
|
}
|
|
374
|
+
async function getDedicatedRuntimeAgentBrowser(leaseId, scope = {}) {
|
|
375
|
+
return (0, api_fetch_1.apiFetch)(withDedicatedScope(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/agent-browser`, scope));
|
|
376
|
+
}
|
|
377
|
+
/** Turn the agents' browser on or off. An actual change is in the machine's audit record. */
|
|
378
|
+
async function setDedicatedRuntimeAgentBrowser(leaseId, enabled, scope = {}) {
|
|
379
|
+
return (0, api_fetch_1.apiFetch)(withDedicatedScope(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/agent-browser`, scope), { method: 'PUT', body: { enabled } });
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* One line for a person. Says the bad cases out loud: a setting that is on
|
|
383
|
+
* while the connected daemon does not offer it yet, and a setting we could
|
|
384
|
+
* not read at all.
|
|
385
|
+
*/
|
|
386
|
+
function describeDedicatedAgentBrowser(value, leaseId) {
|
|
387
|
+
if (!value)
|
|
388
|
+
return 'unknown';
|
|
389
|
+
if ('error' in value)
|
|
390
|
+
return `unknown (could not read: ${value.error})`;
|
|
391
|
+
if (value.state === 'pending_first_start') {
|
|
392
|
+
return value.enabled
|
|
393
|
+
? 'on when the machine first starts (the default for new machines)'
|
|
394
|
+
: 'off when the machine first starts';
|
|
395
|
+
}
|
|
396
|
+
if (value.state === 'off') {
|
|
397
|
+
return value.offered
|
|
398
|
+
? `off — sessions already running keep theirs until the daemon reconnects (\`skrr machines dedicated restart ${leaseId}\`)`
|
|
399
|
+
: `off — turn on with \`skrr machines dedicated agent-browser ${leaseId} --on\``;
|
|
400
|
+
}
|
|
401
|
+
if (value.offered === true)
|
|
402
|
+
return 'on — every agent session can open one; you can watch and take it over';
|
|
403
|
+
if (value.offered === false) {
|
|
404
|
+
return `on, but the connected daemon does not offer it yet: it applies when the daemon reconnects (\`skrr machines dedicated restart ${leaseId}\`), and an image without Chrome never offers it`;
|
|
405
|
+
}
|
|
406
|
+
return 'on (not connected now, so whether the machine offers it is unknown)';
|
|
407
|
+
}
|
|
371
408
|
async function closeDedicatedRuntimeIncidentHold(leaseId, options = {}) {
|
|
372
409
|
return (0, api_fetch_1.apiFetch)(withDedicatedScope(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/incident-hold/close`, options), {
|
|
373
410
|
method: 'POST',
|
|
@@ -813,6 +850,9 @@ function describeDedicatedLease(lease, harnesses = [], opts = {}) {
|
|
|
813
850
|
if (checkpoint)
|
|
814
851
|
lines.push(` ${''.padEnd(13)}${checkpoint.sentence}`);
|
|
815
852
|
}
|
|
853
|
+
if (opts.agentBrowser !== undefined) {
|
|
854
|
+
row('agent browser', describeDedicatedAgentBrowser(opts.agentBrowser, dedicatedLeaseId(lease)));
|
|
855
|
+
}
|
|
816
856
|
const engines = dedicatedLeaseHarnesses(lease, harnesses);
|
|
817
857
|
if (engines.length > 0) {
|
|
818
858
|
row('engines', engines.map((harness) => `${harness.provider} (${harness.status || 'unknown'})`).join(', '));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type DedicatedRuntimePowerSummary, type DedicatedPowerPolicy, type DedicatedRuntimePowerPolicyState, type OnDemandIneligibleError, type SetDedicatedRuntimePowerPolicyResponse } from '@skrr-ai/data-provider';
|
|
2
|
+
import { type WhenOptions } from './format';
|
|
3
|
+
import { type DedicatedRuntimeScope } from './dedicated-machines';
|
|
4
|
+
/**
|
|
5
|
+
* Domain lib for `skrr machines dedicated power …` (W6). Like its siblings in
|
|
6
|
+
* `dedicated-machines.ts` it speaks `apiFetch` so every refusal arrives as an
|
|
7
|
+
* `ApiFetchError` the Dedicated commands already know how to word; the route is
|
|
8
|
+
* covered by the machines parity spec through this file. The web calls the same
|
|
9
|
+
* route through `dataService.setDedicatedRuntimePowerPolicy`.
|
|
10
|
+
*/
|
|
11
|
+
export type SetPowerOutcome = {
|
|
12
|
+
ok: true;
|
|
13
|
+
response: SetDedicatedRuntimePowerPolicyResponse;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
refusal: OnDemandIneligibleError;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Set the policy. An ineligible `on_demand` is a RESULT, not an exception: the
|
|
20
|
+
* refusal is the answer the caller must explain (reason + fix). Every other
|
|
21
|
+
* failure is rethrown for the command's usual error handling.
|
|
22
|
+
*/
|
|
23
|
+
export declare function setDedicatedPowerPolicy(leaseId: string, request: {
|
|
24
|
+
policy?: DedicatedPowerPolicy;
|
|
25
|
+
idleAfterMinutes?: number;
|
|
26
|
+
budgetHoursPerMonth?: number | null;
|
|
27
|
+
}, scope?: DedicatedRuntimeScope): Promise<SetPowerOutcome>;
|
|
28
|
+
/**
|
|
29
|
+
* The policy in words. `on_demand` promises a stop only when the platform stops idle machines at all
|
|
30
|
+
* (`autoStopMode`); an unknown mode is never read as on, so the sentence never promises one.
|
|
31
|
+
*/
|
|
32
|
+
export declare function describePolicyWords(policy: DedicatedPowerPolicy, idleAfterMinutes: number, autoStopMode?: 'off' | 'canary' | 'on'): string;
|
|
33
|
+
export declare function describePowerPolicy(power: DedicatedRuntimePowerPolicyState, autoStopMode?: 'off' | 'canary' | 'on'): string;
|
|
34
|
+
/** `GET /api/machines/dedicated/:leaseId/power` — the read-only power card. */
|
|
35
|
+
export declare function getDedicatedPowerSummary(leaseId: string, scope?: DedicatedRuntimeScope): Promise<DedicatedRuntimePowerSummary>;
|
|
36
|
+
type PowerDecisionView = NonNullable<DedicatedRuntimePowerSummary['decision']>;
|
|
37
|
+
/** The stay/stop/wake decision in words: the shared reason copy backs the server's detail. */
|
|
38
|
+
export declare function describePowerDecisionLines(decision: PowerDecisionView): {
|
|
39
|
+
headline: string;
|
|
40
|
+
detail: string;
|
|
41
|
+
fix: string | null;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The last automatic stop and how it was judged (A3). Every word comes from the shared copy
|
|
45
|
+
* (`describeAutoStopVerdict`, `describeMachineCheckpointOutcome`); nothing is composed here.
|
|
46
|
+
* `verdict: null` is "not judged yet", and is said so: it is never rendered as clean. A sentence
|
|
47
|
+
* the decision text already carries (the server appends it while the machine is stopped) is not
|
|
48
|
+
* repeated.
|
|
49
|
+
*/
|
|
50
|
+
export declare function describeLastAutoStopLines(last: DedicatedRuntimePowerSummary['lastAutoStop'], decisionDetail?: string, when?: WhenOptions): {
|
|
51
|
+
lines: string[];
|
|
52
|
+
fix: string | null;
|
|
53
|
+
};
|
|
54
|
+
/** The latest wake decision and who asked, as lines; empty when there is none. */
|
|
55
|
+
export declare function describeLastWakeDecisionLines(wake: DedicatedRuntimePowerSummary['lastWakeDecision'], when?: WhenOptions): string[];
|
|
56
|
+
/** The lines of `power show`. */
|
|
57
|
+
export declare function describePowerSummary(summary: DedicatedRuntimePowerSummary, when?: WhenOptions): string[];
|
|
58
|
+
/** The lines of `power explain`; the fix is always last. */
|
|
59
|
+
export declare function describePowerExplanation(summary: DedicatedRuntimePowerSummary, when?: WhenOptions): string[];
|
|
60
|
+
export declare function describeBudget(power: DedicatedRuntimePowerPolicyState): string;
|
|
61
|
+
export {};
|