@skrr-ai/cli 0.1.12 → 0.1.14
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 +1 -39
- package/dist/base-command.js +36 -10
- package/dist/commands/agents/list.d.ts +10 -0
- package/dist/commands/agents/list.js +16 -1
- package/dist/commands/browser.js +5 -5
- package/dist/commands/commitments/create.d.ts +2 -0
- package/dist/commands/commitments/create.js +30 -13
- package/dist/commands/commitments/update.d.ts +2 -0
- package/dist/commands/commitments/update.js +24 -4
- package/dist/commands/daemon/byok.js +5 -5
- package/dist/commands/daemon/index.js +7 -4
- package/dist/commands/daemon/install.js +8 -5
- package/dist/commands/daemon/login.js +5 -5
- package/dist/commands/daemon/logs.js +5 -5
- package/dist/commands/daemon/restart.js +5 -5
- package/dist/commands/daemon/start.js +5 -5
- package/dist/commands/daemon/status.js +5 -5
- package/dist/commands/daemon/stop.js +5 -5
- package/dist/commands/daemon/uninstall.js +5 -5
- package/dist/commands/daemon/unlock.js +5 -5
- package/dist/commands/daemon/usage.js +6 -6
- package/dist/commands/login.js +7 -2
- package/dist/commands/spaces/list.d.ts +13 -0
- package/dist/commands/spaces/list.js +20 -1
- package/dist/commands/tasks/list.d.ts +11 -0
- package/dist/commands/tasks/list.js +82 -11
- package/dist/commands/wiki/cat.js +2 -1
- package/dist/commands/wiki/mv.js +5 -1
- package/dist/commands/wiki/write.js +5 -1
- package/dist/help.d.ts +20 -1
- package/dist/help.js +26 -1
- package/dist/lib/api-fetch.d.ts +2 -1
- package/dist/lib/api-fetch.js +9 -2
- package/dist/lib/commitments.d.ts +44 -0
- package/dist/lib/commitments.js +123 -0
- package/dist/lib/daemon-binding.d.ts +3 -4
- package/dist/lib/daemon-binding.js +11 -10
- package/dist/lib/daemon-handoff-opt-out.d.ts +8 -0
- package/dist/lib/daemon-handoff-opt-out.js +17 -0
- package/dist/lib/daemon-installer.d.ts +1 -1
- package/dist/lib/daemon-installer.js +2 -2
- package/dist/lib/daemon-setup.js +13 -7
- package/dist/lib/daemonHandoff.d.ts +1 -1
- package/dist/lib/daemonHandoff.js +20 -7
- package/dist/lib/delegated-cli.d.ts +0 -9
- package/dist/lib/delegated-cli.js +149 -6
- package/dist/lib/{exec-oversky.d.ts → exec-runtime-binary.d.ts} +15 -4
- package/dist/lib/{exec-oversky.js → exec-runtime-binary.js} +93 -37
- package/dist/lib/node-adapter.js +6 -0
- package/dist/lib/sky-code.d.ts +1 -1
- package/dist/lib/sky-code.js +1 -1
- package/dist/lib/usage-discovery.js +1 -1
- package/dist/lib/web-url.d.ts +16 -0
- package/dist/lib/web-url.js +25 -0
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3931 -3884
- package/oclif.manifest.json +9932 -9904
- package/package.json +2 -2
package/dist/lib/commitments.js
CHANGED
|
@@ -3,12 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.COMMITMENT_CLI_EVALUATORS = exports.COMMITMENT_CREATE_KEYS = exports.COMMITMENT_REMEDIATION_ACTIONS = exports.COMMITMENT_FEEDBACK_KINDS = exports.commitmentApi = exports.commitmentPackApi = exports.MAX_COMMITMENT_INTERVAL_MS = exports.MIN_COMMITMENT_INTERVAL_MS = exports.COMMITMENT_AUTONOMY_MODES = exports.COMMITMENT_STATUSES = exports.COMMITMENT_CADENCE_MAX_TIMES = exports.COMMITMENT_CADENCE_TIME_PATTERN = exports.COMMITMENT_CADENCE_MODES = exports.COMMITMENT_TEMPLATE_IDS = exports.COMMITMENT_WATCHED_SOURCES = void 0;
|
|
4
4
|
exports.normalizeCreateBody = normalizeCreateBody;
|
|
5
5
|
exports.parseIntervalToMs = parseIntervalToMs;
|
|
6
|
+
exports.buildWatchedSources = buildWatchedSources;
|
|
6
7
|
exports.describeEvidenceHealth = describeEvidenceHealth;
|
|
7
8
|
exports.describeIntervalMs = describeIntervalMs;
|
|
8
9
|
exports.buildSuccessCriteria = buildSuccessCriteria;
|
|
9
10
|
exports.cadenceLabel = cadenceLabel;
|
|
10
11
|
exports.renderCommitmentList = renderCommitmentList;
|
|
11
12
|
exports.effectiveAutonomyMode = effectiveAutonomyMode;
|
|
13
|
+
exports.extractStandstillFromCheck = extractStandstillFromCheck;
|
|
12
14
|
exports.describeAutonomyLine = describeAutonomyLine;
|
|
13
15
|
exports.renderCommitmentSummary = renderCommitmentSummary;
|
|
14
16
|
exports.describeWakeDecision = describeWakeDecision;
|
|
@@ -316,6 +318,66 @@ function parseIntervalToMs(input) {
|
|
|
316
318
|
}
|
|
317
319
|
return ms;
|
|
318
320
|
}
|
|
321
|
+
function buildWatchedSources(inputs) {
|
|
322
|
+
const watched = (inputs.watch ?? []).filter(Boolean);
|
|
323
|
+
const metricKeys = (inputs.metricKey ?? []).filter(Boolean);
|
|
324
|
+
const spaceIds = (inputs.spaceIds ?? []).filter(Boolean);
|
|
325
|
+
const monitorIds = (inputs.monitorIds ?? []).filter(Boolean);
|
|
326
|
+
const effective = new Set(watched);
|
|
327
|
+
if (spaceIds.length)
|
|
328
|
+
effective.add('space');
|
|
329
|
+
if (monitorIds.length)
|
|
330
|
+
effective.add('monitor');
|
|
331
|
+
if (effective.has('space') && spaceIds.length === 0) {
|
|
332
|
+
throw new Error('--watch space needs at least one --watch-space <space-id-or-ref>. ' +
|
|
333
|
+
'The `space` source requires a scope id, which the enum on its own cannot carry.');
|
|
334
|
+
}
|
|
335
|
+
if (effective.has('monitor') && monitorIds.length === 0) {
|
|
336
|
+
throw new Error('--watch monitor needs at least one --watch-monitor <monitor-id>. ' +
|
|
337
|
+
'The `monitor` source requires a scope id, which the enum on its own cannot carry.');
|
|
338
|
+
}
|
|
339
|
+
if (metricKeys.length && !effective.has('metrics')) {
|
|
340
|
+
throw new Error('--metric-key requires --watch metrics.');
|
|
341
|
+
}
|
|
342
|
+
if (effective.size === 0)
|
|
343
|
+
return undefined;
|
|
344
|
+
// Preserve caller order: --watch entries first (deduped), then any source
|
|
345
|
+
// that was only implied by a companion flag.
|
|
346
|
+
const orderedSources = [];
|
|
347
|
+
const seen = new Set();
|
|
348
|
+
for (const s of watched) {
|
|
349
|
+
if (!seen.has(s)) {
|
|
350
|
+
orderedSources.push(s);
|
|
351
|
+
seen.add(s);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
for (const implied of ['space', 'monitor']) {
|
|
355
|
+
if (effective.has(implied) && !seen.has(implied)) {
|
|
356
|
+
orderedSources.push(implied);
|
|
357
|
+
seen.add(implied);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const rows = [];
|
|
361
|
+
for (const source of orderedSources) {
|
|
362
|
+
if (source === 'space') {
|
|
363
|
+
for (const spaceId of spaceIds) {
|
|
364
|
+
rows.push({ source, enabled: true, config: { spaceId } });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else if (source === 'monitor') {
|
|
368
|
+
for (const monitorId of monitorIds) {
|
|
369
|
+
rows.push({ source, enabled: true, config: { monitorId } });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else if (source === 'metrics' && metricKeys.length) {
|
|
373
|
+
rows.push({ source, enabled: true, config: { metricKeys } });
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
rows.push({ source, enabled: true });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return rows;
|
|
380
|
+
}
|
|
319
381
|
/**
|
|
320
382
|
* One line an author can act on, or nothing.
|
|
321
383
|
*
|
|
@@ -480,6 +542,41 @@ function effectiveAutonomyMode(check) {
|
|
|
480
542
|
}
|
|
481
543
|
return undefined;
|
|
482
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* A `commitment_standstill` policyTrace entry, extracted from the latest
|
|
547
|
+
* check's decision. The arbiter appends it whenever a quiet posture is the
|
|
548
|
+
* ANSWER to "why nothing is happening" (`CommitmentControl/index.js` around
|
|
549
|
+
* the `record_only`/empty-action-plan branch). Every field except `reason` is
|
|
550
|
+
* optional, and old checks lack the entry entirely — so a missing entry means
|
|
551
|
+
* "the loop had nothing standstill-worthy to say", not "we don't know".
|
|
552
|
+
*
|
|
553
|
+
* Exported for tests.
|
|
554
|
+
*/
|
|
555
|
+
function extractStandstillFromCheck(check) {
|
|
556
|
+
const trace = check?.decision?.policyTrace;
|
|
557
|
+
if (!Array.isArray(trace))
|
|
558
|
+
return null;
|
|
559
|
+
for (const entry of trace) {
|
|
560
|
+
if (!entry || typeof entry !== 'object')
|
|
561
|
+
continue;
|
|
562
|
+
const e = entry;
|
|
563
|
+
if (e.rule !== 'commitment_standstill')
|
|
564
|
+
continue;
|
|
565
|
+
const reason = typeof e.reason === 'string' ? e.reason : '';
|
|
566
|
+
if (!reason)
|
|
567
|
+
continue;
|
|
568
|
+
return {
|
|
569
|
+
reason,
|
|
570
|
+
layer: typeof e.layer === 'string' ? e.layer : undefined,
|
|
571
|
+
owner: typeof e.owner === 'string' ? e.owner : undefined,
|
|
572
|
+
fix: typeof e.fix === 'string' ? e.fix : undefined,
|
|
573
|
+
alsoConstrained: Array.isArray(e.alsoConstrained)
|
|
574
|
+
? e.alsoConstrained
|
|
575
|
+
: undefined,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
483
580
|
/**
|
|
484
581
|
* The `Autonomy:` line — the stated mode, annotated when the latest check
|
|
485
582
|
* actually ran at a STRICTER one (OSK-4881). Without the caveat, a commitment
|
|
@@ -538,6 +635,32 @@ latestCheck) {
|
|
|
538
635
|
if (typeof commitment.state?.consecutiveUnresolvedChecks === 'number') {
|
|
539
636
|
log(`Unresolved checks in a row:${` ${commitment.state.consecutiveUnresolvedChecks}`}`);
|
|
540
637
|
}
|
|
638
|
+
// The load-bearing signal on a commitment that "reports active but never
|
|
639
|
+
// acts": the arbiter names WHY every recent check decided to do nothing, but
|
|
640
|
+
// that entry sat only in `policyTrace` on each check and was never rendered.
|
|
641
|
+
// Read at check time, unrolled here so a person reading the header sees the
|
|
642
|
+
// same conclusion the check already reached — and the `fix` string that was
|
|
643
|
+
// already written for them.
|
|
644
|
+
const standstill = extractStandstillFromCheck(latestCheck);
|
|
645
|
+
if (standstill) {
|
|
646
|
+
const streak = commitment.state?.consecutiveUnresolvedChecks;
|
|
647
|
+
const streakClause = typeof streak === 'number' && streak >= 2 ? `${streak} checks in a row: ` : '';
|
|
648
|
+
log(`Standstill:${` ${streakClause}${standstill.reason}`}`);
|
|
649
|
+
if (standstill.fix)
|
|
650
|
+
log(` Fix: ${standstill.fix}`);
|
|
651
|
+
if (standstill.alsoConstrained?.length) {
|
|
652
|
+
const also = standstill.alsoConstrained
|
|
653
|
+
.map((entry) => {
|
|
654
|
+
if (!entry || typeof entry !== 'object')
|
|
655
|
+
return null;
|
|
656
|
+
const reason = entry.reason;
|
|
657
|
+
return typeof reason === 'string' ? reason : null;
|
|
658
|
+
})
|
|
659
|
+
.filter((r) => Boolean(r));
|
|
660
|
+
if (also.length)
|
|
661
|
+
log(` Also constrained: ${also.join(', ')}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
541
664
|
const sources = (commitment.watchedSources || [])
|
|
542
665
|
.filter((s) => s.enabled !== false)
|
|
543
666
|
.map((s) => s.source);
|
|
@@ -21,9 +21,8 @@
|
|
|
21
21
|
/** Whether `argv` already carries `--name` or `--name=value` (or `-alias`). */
|
|
22
22
|
export declare function hasFlag(argv: readonly string[], name: string, alias?: string): boolean;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* while the CLI sits on dev.
|
|
24
|
+
* Fill `--server` / `--profile` from the CLI's own session when the caller did
|
|
25
|
+
* not pass them. The runtime's profile is global, so normalize it before the
|
|
26
|
+
* subcommand even when the caller supplied it after one.
|
|
28
27
|
*/
|
|
29
28
|
export declare function bindToCliSession(argv: readonly string[]): string[];
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.hasFlag = hasFlag;
|
|
4
4
|
exports.bindToCliSession = bindToCliSession;
|
|
5
5
|
const config_1 = require("./config");
|
|
6
|
+
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
6
7
|
const keychain_1 = require("./keychain");
|
|
7
8
|
/**
|
|
8
9
|
* Defaults for the flags that decide WHICH deployment and WHICH profile a
|
|
@@ -35,10 +36,9 @@ function hasFlag(argv, name, alias) {
|
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* while the CLI sits on dev.
|
|
39
|
+
* Fill `--server` / `--profile` from the CLI's own session when the caller did
|
|
40
|
+
* not pass them. The runtime's profile is global, so normalize it before the
|
|
41
|
+
* subcommand even when the caller supplied it after one.
|
|
42
42
|
*/
|
|
43
43
|
function bindToCliSession(argv) {
|
|
44
44
|
const extra = [];
|
|
@@ -51,13 +51,14 @@ function bindToCliSession(argv) {
|
|
|
51
51
|
extra.push('--server', baseURL);
|
|
52
52
|
}
|
|
53
53
|
if (!hasFlag(argv, '--profile')) {
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
54
|
+
// Direct daemon wrappers extend Command, so their caller-supplied argv is
|
|
55
|
+
// authoritative. Login's automatic setup has no forwarded argv because it
|
|
56
|
+
// is already inside a BaseCommand; fall back to that process's resolved
|
|
57
|
+
// active profile so it cannot silently install the default service after
|
|
58
|
+
// `skrr login --profile <name>`.
|
|
59
|
+
const profile = (0, keychain_1.resolveProfileFromArgv)(argv) ?? process.env.OVERSKY_PROFILE ?? (0, keychain_1.getActiveProfile)() ?? null;
|
|
59
60
|
if (profile && profile !== keychain_1.DEFAULT_PROFILE)
|
|
60
61
|
extra.push('--profile', profile);
|
|
61
62
|
}
|
|
62
|
-
return [...argv, ...extra];
|
|
63
|
+
return (0, exec_runtime_binary_1.normalizeRuntimeGlobalArgs)([...argv, ...extra]);
|
|
63
64
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the explicit operator switch that disables CLI-to-runtime handoff.
|
|
3
|
+
*
|
|
4
|
+
* `SKRR_*` is the current spelling, while the older `OVERSKY_*` name remains
|
|
5
|
+
* compatible. Returning the actual matched spelling keeps status output honest
|
|
6
|
+
* and makes the canonical alias win when an environment happens to carry both.
|
|
7
|
+
*/
|
|
8
|
+
export declare function daemonHandoffOptOut(env: NodeJS.ProcessEnv): string | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.daemonHandoffOptOut = daemonHandoffOptOut;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the explicit operator switch that disables CLI-to-runtime handoff.
|
|
6
|
+
*
|
|
7
|
+
* `SKRR_*` is the current spelling, while the older `OVERSKY_*` name remains
|
|
8
|
+
* compatible. Returning the actual matched spelling keeps status output honest
|
|
9
|
+
* and makes the canonical alias win when an environment happens to carry both.
|
|
10
|
+
*/
|
|
11
|
+
function daemonHandoffOptOut(env) {
|
|
12
|
+
if (env.SKRR_SKIP_DAEMON_HANDOFF === '1')
|
|
13
|
+
return 'SKRR_SKIP_DAEMON_HANDOFF';
|
|
14
|
+
if (env.OVERSKY_SKIP_DAEMON_HANDOFF === '1')
|
|
15
|
+
return 'OVERSKY_SKIP_DAEMON_HANDOFF';
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
@@ -31,7 +31,7 @@ export interface DaemonInstallOptions {
|
|
|
31
31
|
}
|
|
32
32
|
export declare function daemonFeedBase(env?: NodeJS.ProcessEnv): string;
|
|
33
33
|
/**
|
|
34
|
-
* Where the fetched binary lands. `exec-
|
|
34
|
+
* Where the fetched binary lands. `exec-runtime-binary.ts` already looks here, so a
|
|
35
35
|
* daemon installed by this path is found by every `skrr daemon *` command
|
|
36
36
|
* without the user touching PATH.
|
|
37
37
|
*/
|
|
@@ -15,7 +15,7 @@ exports.installDaemon = installDaemon;
|
|
|
15
15
|
*
|
|
16
16
|
* ## Why this exists
|
|
17
17
|
*
|
|
18
|
-
* `skrr daemon install` forwarded to `skrrd install`, and `
|
|
18
|
+
* `skrr daemon install` forwarded to `skrrd install`, and `findRuntimeBinary()`
|
|
19
19
|
* refused with "install it with brew" when there was no `skrrd` to forward to.
|
|
20
20
|
* So the one command whose job is to set the machine up required the machine to
|
|
21
21
|
* already be set up, and onboarding's first step was a second install from a
|
|
@@ -77,7 +77,7 @@ function daemonFeedBase(env = process.env) {
|
|
|
77
77
|
return (env[exports.DAEMON_FEED_BASE_ENV]?.trim() || exports.DEFAULT_DAEMON_FEED_BASE).replace(/\/+$/, '');
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
|
-
* Where the fetched binary lands. `exec-
|
|
80
|
+
* Where the fetched binary lands. `exec-runtime-binary.ts` already looks here, so a
|
|
81
81
|
* daemon installed by this path is found by every `skrr daemon *` command
|
|
82
82
|
* without the user touching PATH.
|
|
83
83
|
*/
|
package/dist/lib/daemon-setup.js
CHANGED
|
@@ -33,7 +33,8 @@ exports.offerDaemonSetup = offerDaemonSetup;
|
|
|
33
33
|
*/
|
|
34
34
|
const prompt_1 = require("./prompt");
|
|
35
35
|
const daemon_binding_1 = require("./daemon-binding");
|
|
36
|
-
const
|
|
36
|
+
const daemon_handoff_opt_out_1 = require("./daemon-handoff-opt-out");
|
|
37
|
+
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
37
38
|
const daemon_installer_1 = require("./daemon-installer");
|
|
38
39
|
const ssh_detect_1 = require("./ssh-detect");
|
|
39
40
|
/**
|
|
@@ -46,8 +47,9 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
46
47
|
// The same switch that turns the credential handoff off. An operator who has
|
|
47
48
|
// said "do not touch my daemon" has not asked a narrower question, and a
|
|
48
49
|
// second variable for the same intent is a second thing to remember.
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const optOut = (0, daemon_handoff_opt_out_1.daemonHandoffOptOut)(env);
|
|
51
|
+
if (optOut) {
|
|
52
|
+
return { status: 'skipped', detail: `skipped by ${optOut}` };
|
|
51
53
|
}
|
|
52
54
|
const interactive = opts.interactive ?? !(0, ssh_detect_1.isNonInteractive)();
|
|
53
55
|
if (!interactive) {
|
|
@@ -62,7 +64,11 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
62
64
|
log('');
|
|
63
65
|
log(' This machine has no skrr runtime. It is what lets your agents work here —');
|
|
64
66
|
log(' running commands, editing files, and driving a browser on this machine.');
|
|
65
|
-
|
|
67
|
+
// This creates a persistent service with broad local capabilities, so a
|
|
68
|
+
// bare Enter must visibly and behaviorally decline it. `confirm` names its
|
|
69
|
+
// second argument `defaultNo`; passing false showed `[Y/n]` and accepted an
|
|
70
|
+
// accidental Enter, the opposite of this onboarding promise.
|
|
71
|
+
const yes = await confirmImpl(' Set it up now?', true);
|
|
66
72
|
if (!yes) {
|
|
67
73
|
return {
|
|
68
74
|
status: 'declined',
|
|
@@ -72,7 +78,7 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
72
78
|
// Fetch the signed binary only when there is none. When the binary is already
|
|
73
79
|
// present and only the service is missing, this step is skipped entirely —
|
|
74
80
|
// downloading over a working runtime is not what "set it up" asked for.
|
|
75
|
-
const binaryPresent = opts.binaryPresent ??
|
|
81
|
+
const binaryPresent = opts.binaryPresent ?? (0, exec_runtime_binary_1.isCurrentRuntimeBinary)((0, exec_runtime_binary_1.findRuntimeBinary)());
|
|
76
82
|
if (!binaryPresent) {
|
|
77
83
|
log(' Fetching the signed release...');
|
|
78
84
|
const fetched = await (0, daemon_installer_1.installDaemon)({ env });
|
|
@@ -85,13 +91,13 @@ async function offerDaemonSetup(opts = {}) {
|
|
|
85
91
|
// that just authenticated — which is the whole reason this runs here and not
|
|
86
92
|
// at install time.
|
|
87
93
|
try {
|
|
88
|
-
const code = await (0,
|
|
94
|
+
const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['install', ...(0, daemon_binding_1.bindToCliSession)([])]);
|
|
89
95
|
if (code !== 0) {
|
|
90
96
|
return { status: 'failed', detail: `\`skrrd install\` exited ${code}` };
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
catch (err) {
|
|
94
|
-
if (err.message === '
|
|
100
|
+
if (err.message === 'runtime-binary-not-installed') {
|
|
95
101
|
return {
|
|
96
102
|
status: 'failed',
|
|
97
103
|
detail: 'the runtime vanished between fetching and installing it',
|
|
@@ -40,7 +40,7 @@ export interface DaemonHandoffOutcome {
|
|
|
40
40
|
* The two absent cases are separated because they need different work — one
|
|
41
41
|
* needs a binary fetched, the other only needs the service registered.
|
|
42
42
|
*/
|
|
43
|
-
reason?: 'opted-out' | 'brokered' | 'no-binary' | 'unsupported-daemon' | 'no-service';
|
|
43
|
+
reason?: 'opted-out' | 'brokered' | 'no-binary' | 'legacy-binary' | 'unsupported-daemon' | 'no-service';
|
|
44
44
|
}
|
|
45
45
|
export declare function handOffToLocalDaemon(env?: NodeJS.ProcessEnv, opts?: {
|
|
46
46
|
loginFlow?: string;
|
|
@@ -4,7 +4,8 @@ exports.handOffToLocalDaemon = handOffToLocalDaemon;
|
|
|
4
4
|
const node_child_process_1 = require("node:child_process");
|
|
5
5
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
6
6
|
const config_1 = require("./config");
|
|
7
|
-
const
|
|
7
|
+
const daemon_handoff_opt_out_1 = require("./daemon-handoff-opt-out");
|
|
8
|
+
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
8
9
|
const keychain_1 = require("./keychain");
|
|
9
10
|
/**
|
|
10
11
|
* Ceilings, not expectations. Both calls finish in milliseconds when anything is
|
|
@@ -29,13 +30,13 @@ function runOversky(binary, args, opts) {
|
|
|
29
30
|
const { input, timeoutMs } = opts;
|
|
30
31
|
return new Promise((resolve) => {
|
|
31
32
|
// A Windows npm-global install ships a `.cmd` shim, which `spawn` cannot
|
|
32
|
-
// execute without a shell — Node fires ENOENT instead. `
|
|
33
|
+
// execute without a shell — Node fires ENOENT instead. `execRuntimeBinary` has
|
|
33
34
|
// handled this since it was written; this function did not, so the handoff
|
|
34
35
|
// failed on EVERY Windows login and reported it as the daemon refusing the
|
|
35
36
|
// bundle. Restricted to the shim case so real binaries keep the argv-array
|
|
36
37
|
// exec path, which needs no quoting rules to be right.
|
|
37
38
|
const isWinShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(binary);
|
|
38
|
-
const child = (0, node_child_process_1.spawn)(binary, args, {
|
|
39
|
+
const child = (0, node_child_process_1.spawn)(binary, (0, exec_runtime_binary_1.normalizeRuntimeGlobalArgs)(args), {
|
|
39
40
|
// stderr is CAPTURED, not discarded. Discarding it cost a debugging cycle
|
|
40
41
|
// on the first real run: the daemon exited non-zero with
|
|
41
42
|
// `OVERSKY_KEK_REQUIRED`, and all the user saw was "the daemon refused the
|
|
@@ -172,11 +173,12 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
172
173
|
// Mirrors `OVERSKY_SKIP_DAEMON_BROKER` on the other direction. An operator
|
|
173
174
|
// deliberately running a daemon against a different deployment than their CLI is
|
|
174
175
|
// doing a legitimate thing, and this must not overrule it.
|
|
175
|
-
|
|
176
|
+
const optOut = (0, daemon_handoff_opt_out_1.daemonHandoffOptOut)(env);
|
|
177
|
+
if (optOut) {
|
|
176
178
|
return {
|
|
177
179
|
status: 'skipped',
|
|
178
180
|
reason: 'opted-out',
|
|
179
|
-
detail:
|
|
181
|
+
detail: `skipped by ${optOut}`,
|
|
180
182
|
};
|
|
181
183
|
}
|
|
182
184
|
// The daemon brokered this login, which means it just used its OWN credential
|
|
@@ -194,15 +196,26 @@ async function handOffToLocalDaemon(env = process.env, opts = {}) {
|
|
|
194
196
|
detail: 'the local daemon brokered this login, so it already has a working credential',
|
|
195
197
|
};
|
|
196
198
|
}
|
|
197
|
-
const binary = (0,
|
|
199
|
+
const binary = (0, exec_runtime_binary_1.findRuntimeBinary)();
|
|
198
200
|
if (!binary) {
|
|
199
201
|
return { status: 'skipped', reason: 'no-binary', detail: 'no local daemon installed' };
|
|
200
202
|
}
|
|
203
|
+
// A legacy binary may remain on PATH after the product rename, but it cannot
|
|
204
|
+
// receive a skrr CLI handoff or reliably install the new profile-scoped
|
|
205
|
+
// service. Tell login to bootstrap a current signed runtime instead of
|
|
206
|
+
// probing the old binary and treating it as a usable local daemon.
|
|
207
|
+
if (!(0, exec_runtime_binary_1.isCurrentRuntimeBinary)(binary)) {
|
|
208
|
+
return {
|
|
209
|
+
status: 'skipped',
|
|
210
|
+
reason: 'legacy-binary',
|
|
211
|
+
detail: 'a legacy oversky runtime is installed; bootstrap a current skrr runtime',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
201
214
|
// An older daemon has no `accept-handoff`, and spawning it would fail with
|
|
202
215
|
// commander's "unknown command" rather than anything a reader could act on. A
|
|
203
216
|
// CLI that outruns the daemon on the machine is the normal case during a
|
|
204
217
|
// staged rollout, not an error.
|
|
205
|
-
const supported = await (0,
|
|
218
|
+
const supported = await (0, exec_runtime_binary_1.runtimeBinarySupportsSubcommand)('accept-handoff');
|
|
206
219
|
if (supported === false) {
|
|
207
220
|
return {
|
|
208
221
|
status: 'skipped',
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Daemon-owned delegated CLI credentials for autonomous Agent sessions.
|
|
3
|
-
*
|
|
4
|
-
* A spawned Agent never reads the human Keychain or a refresh token. Instead
|
|
5
|
-
* it holds a random loopback capability and asks its parent daemon for a
|
|
6
|
-
* short-lived normal `scope=cli` access token. The daemon rotates the refresh
|
|
7
|
-
* family, so each Sky invocation retains the full command surface for the
|
|
8
|
-
* duration of a long-running Agent session.
|
|
9
|
-
*/
|
|
10
1
|
import { type ResolvedCredential } from '@skrr-ai/auth-core';
|
|
11
2
|
export declare function isDelegatedCliSession(env?: Record<string, string | undefined>): boolean;
|
|
12
3
|
/** Drop the memoized credential. Exported for tests. */
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.isDelegatedCliSession = isDelegatedCliSession;
|
|
4
37
|
exports.resetDelegatedCliCredentialCache = resetDelegatedCliCredentialCache;
|
|
@@ -12,6 +45,9 @@ exports.fetchDelegatedCliCredential = fetchDelegatedCliCredential;
|
|
|
12
45
|
* family, so each Sky invocation retains the full command surface for the
|
|
13
46
|
* duration of a long-running Agent session.
|
|
14
47
|
*/
|
|
48
|
+
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const os = __importStar(require("node:os"));
|
|
50
|
+
const path = __importStar(require("node:path"));
|
|
15
51
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
16
52
|
function isDelegatedCliSession(env = process.env) {
|
|
17
53
|
return Boolean(env.OVERSKY_AUTONOMOUS_SESSION === '1' &&
|
|
@@ -40,6 +76,94 @@ let _cached = null;
|
|
|
40
76
|
function resetDelegatedCliCredentialCache() {
|
|
41
77
|
_cached = null;
|
|
42
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Walk `err.cause` / `err.errors[]` looking for a `code: 'ECONNREFUSED'`.
|
|
81
|
+
* Node's `fetch` throws a `TypeError('fetch failed')` whose `cause` is either
|
|
82
|
+
* a plain `Error` with `code` set, or an `AggregateError` whose `errors[]`
|
|
83
|
+
* carry it (dual-stack retries). Neither shape is visible at the top level.
|
|
84
|
+
*/
|
|
85
|
+
function isConnectionRefusedError(err) {
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const stack = [err];
|
|
88
|
+
while (stack.length) {
|
|
89
|
+
const cur = stack.pop();
|
|
90
|
+
if (!cur || typeof cur !== 'object' || seen.has(cur))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(cur);
|
|
93
|
+
const anyCur = cur;
|
|
94
|
+
if (anyCur.code === 'ECONNREFUSED')
|
|
95
|
+
return true;
|
|
96
|
+
if (anyCur.cause !== undefined)
|
|
97
|
+
stack.push(anyCur.cause);
|
|
98
|
+
if (Array.isArray(anyCur.errors))
|
|
99
|
+
for (const e of anyCur.errors)
|
|
100
|
+
stack.push(e);
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Best-effort probe of the daemon's bootstrap file — the CLI's
|
|
106
|
+
* `daemonBroker.ts` writes it and can auto-broker off the fresh port.
|
|
107
|
+
* Returned only for the actionable-error diagnostic; failures are silent
|
|
108
|
+
* because this is a display hint, not a decision.
|
|
109
|
+
*
|
|
110
|
+
* Kept inline rather than importing from `daemonBroker.ts` so this module
|
|
111
|
+
* stays small and free of the auth-storage / keychain dependency chain
|
|
112
|
+
* that a lazily-executed broker call would drag in on every `skrr` startup.
|
|
113
|
+
*/
|
|
114
|
+
function readLiveDaemonPort() {
|
|
115
|
+
const candidates = [];
|
|
116
|
+
if (process.platform === 'darwin') {
|
|
117
|
+
candidates.push(path.join(os.homedir(), 'Library', 'Application Support', 'skrr', 'daemon-island.default.json'));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
candidates.push(path.join(os.homedir(), '.skrr', 'profiles', 'default', 'daemon-island.json'), path.join(os.homedir(), '.skrr', 'daemon-island.json'));
|
|
121
|
+
}
|
|
122
|
+
for (const p of candidates) {
|
|
123
|
+
try {
|
|
124
|
+
if (!fs.existsSync(p))
|
|
125
|
+
continue;
|
|
126
|
+
const parsed = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
127
|
+
if (typeof parsed.port !== 'number' || !Number.isInteger(parsed.port))
|
|
128
|
+
continue;
|
|
129
|
+
if (typeof parsed.pid === 'number' && parsed.pid > 0) {
|
|
130
|
+
try {
|
|
131
|
+
process.kill(parsed.pid, 0);
|
|
132
|
+
}
|
|
133
|
+
catch (killErr) {
|
|
134
|
+
if (killErr.code === 'ESRCH')
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return parsed.port;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Ignore parse / IO errors — the caller has a fallback message.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* When the broker port refuses the connection, the daemon that issued the
|
|
148
|
+
* capability is gone (typical trigger: a self-upgrade restart). The successor
|
|
149
|
+
* daemon has no memory of this capability — it lived in the dead process's
|
|
150
|
+
* heap — so nothing on this side can revive it. Produce an error text that
|
|
151
|
+
* names the stale env vars and gives the exact recovery an operator can run
|
|
152
|
+
* without reading source. See OSK-6861.
|
|
153
|
+
*/
|
|
154
|
+
function composeStaleBrokerMessage(brokerUrl) {
|
|
155
|
+
const livePort = readLiveDaemonPort();
|
|
156
|
+
const liveHint = typeof livePort === 'number'
|
|
157
|
+
? ` The live daemon is bound to port ${livePort} (from daemon-island.json), which does not match the stale URL above.`
|
|
158
|
+
: '';
|
|
159
|
+
return (`delegated CLI broker at ${brokerUrl} refused connection. ` +
|
|
160
|
+
`The parent daemon that issued this capability is gone (typical cause: a self-upgrade restart), ` +
|
|
161
|
+
`and the capability lived in that process's memory — the successor daemon cannot honour it.` +
|
|
162
|
+
liveHint +
|
|
163
|
+
` To recover in this shell, run:\n` +
|
|
164
|
+
` unset OVERSKY_DELEGATED_CLI_BROKER_URL OVERSKY_DELEGATED_CLI_CAPABILITY OVERSKY_AUTONOMOUS_SESSION\n` +
|
|
165
|
+
`Subsequent \`skrr\` calls will then auto-broker a fresh credential via the live daemon.`);
|
|
166
|
+
}
|
|
43
167
|
/** Fetch a full-permission CLI access token from the local daemon. */
|
|
44
168
|
async function fetchDelegatedCliCredential(env = process.env, options = {}) {
|
|
45
169
|
if (!isDelegatedCliSession(env))
|
|
@@ -55,12 +179,31 @@ async function fetchDelegatedCliCredential(env = process.env, options = {}) {
|
|
|
55
179
|
const controller = new AbortController();
|
|
56
180
|
const timeout = setTimeout(() => controller.abort(), 5_000);
|
|
57
181
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
182
|
+
let res;
|
|
183
|
+
try {
|
|
184
|
+
res = await fetch(brokerUrl, {
|
|
185
|
+
method: 'POST',
|
|
186
|
+
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
capability,
|
|
189
|
+
sessionId,
|
|
190
|
+
forceRefresh: options.forceRefresh === true,
|
|
191
|
+
}),
|
|
192
|
+
signal: controller.signal,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
// A connection-refused throw here is not the same failure mode as a 5xx
|
|
197
|
+
// from a live daemon: it means the whole loopback listener is gone, so
|
|
198
|
+
// the capability is unrecoverable rather than transiently unavailable.
|
|
199
|
+
// Rewrite the message so `base-command` surfaces recovery text instead
|
|
200
|
+
// of a bare `fetch failed`. Any other network error (DNS, TLS,
|
|
201
|
+
// AbortError, etc.) propagates unchanged.
|
|
202
|
+
if (isConnectionRefusedError(err)) {
|
|
203
|
+
throw new Error(composeStaleBrokerMessage(brokerUrl));
|
|
204
|
+
}
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
64
207
|
if (!res.ok) {
|
|
65
208
|
// A DELIBERATE decline is not a broker failure — WL-5 Step 1.
|
|
66
209
|
//
|
|
@@ -29,9 +29,20 @@ export declare const RUNTIME_BINARY_NAMES: readonly ["skrrd", "oversky"];
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function forwardedFlagsNote(verb: string, opts?: {
|
|
31
31
|
binds?: boolean;
|
|
32
|
+
bootstrapsRuntime?: boolean;
|
|
32
33
|
}): string;
|
|
33
|
-
/** Resolve the
|
|
34
|
-
export declare function
|
|
34
|
+
/** Resolve the local runtime binary — `skrrd`, or the legacy `oversky` still on older PATHs. */
|
|
35
|
+
export declare function findRuntimeBinary(): string | null;
|
|
36
|
+
/** Whether a resolved path names the current runtime rather than legacy oversky. */
|
|
37
|
+
export declare function isCurrentRuntimeBinary(binary: string | null): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Commander reads runtime global options before the subcommand. The CLI
|
|
40
|
+
* wrappers naturally receive pass-through flags after a verb (`install
|
|
41
|
+
* --profile work`), which otherwise silently resolves the runtime profile as
|
|
42
|
+
* default. Move a well-formed profile flag ahead of the verb without changing
|
|
43
|
+
* the order of any command-specific flags or `--` payload.
|
|
44
|
+
*/
|
|
45
|
+
export declare function normalizeRuntimeGlobalArgs(args: readonly string[]): string[];
|
|
35
46
|
/**
|
|
36
47
|
* Pretty hint shown when the runtime isn't installed. Kept short — points
|
|
37
48
|
* users at the canonical install paths rather than enumerating them.
|
|
@@ -60,7 +71,7 @@ export declare function notInstalledMessage(): string;
|
|
|
60
71
|
* preserving "exit code = the daemon's exit code" semantics that scripts
|
|
61
72
|
* may depend on.
|
|
62
73
|
*/
|
|
63
|
-
export declare function
|
|
74
|
+
export declare function execRuntimeBinary(args: string[]): Promise<number>;
|
|
64
75
|
/**
|
|
65
76
|
* Check an installed daemon's top-level help before forwarding a command that
|
|
66
77
|
* was added after older daemon releases shipped. A false result is useful to
|
|
@@ -70,4 +81,4 @@ export declare function execOversky(args: string[]): Promise<number>;
|
|
|
70
81
|
* This invokes only `oversky --help`: it never starts, stops, configures, or
|
|
71
82
|
* otherwise mutates the local daemon.
|
|
72
83
|
*/
|
|
73
|
-
export declare function
|
|
84
|
+
export declare function runtimeBinarySupportsSubcommand(subcommand: string): Promise<boolean | null>;
|