@skrr-ai/cli 0.1.113 → 0.1.115
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/bin/update-npm-runner.cjs +4 -1
- package/bin/update-worker.cjs +6 -3
- package/dist/commands/agents/chat.js +10 -1
- package/dist/commands/computer-use/audio.d.ts +6 -0
- package/dist/commands/computer-use/audio.js +9 -0
- package/dist/commands/computer-use/browser.d.ts +7 -0
- package/dist/commands/computer-use/browser.js +16 -0
- package/dist/commands/computer-use/disable.d.ts +6 -0
- package/dist/commands/computer-use/disable.js +9 -0
- package/dist/commands/computer-use/enable.d.ts +6 -0
- package/dist/commands/computer-use/enable.js +9 -0
- package/dist/commands/computer-use/index.d.ts +6 -0
- package/dist/commands/computer-use/index.js +9 -0
- package/dist/commands/computer-use/install.d.ts +6 -0
- package/dist/commands/computer-use/install.js +9 -0
- package/dist/commands/computer-use/recover.d.ts +6 -0
- package/dist/commands/computer-use/recover.js +9 -0
- package/dist/commands/computer-use/status.d.ts +6 -0
- package/dist/commands/computer-use/status.js +9 -0
- package/dist/commands/computer-use/stop.d.ts +6 -0
- package/dist/commands/computer-use/stop.js +9 -0
- package/dist/commands/daemon/login.js +1 -1
- package/dist/commands/followups/plans/cancel.js +10 -3
- package/dist/commands/followups/plans/capacity.d.ts +9 -0
- package/dist/commands/followups/plans/capacity.js +42 -0
- package/dist/commands/followups/plans/create.js +3 -0
- package/dist/commands/followups/plans/revise.js +13 -6
- package/dist/commands/followups/remind.js +13 -5
- package/dist/commands/followups/watch.js +2 -2
- package/dist/lib/agentic-stream.d.ts +5 -1
- package/dist/lib/agentic-stream.js +25 -9
- package/dist/lib/computer-use.d.ts +9 -0
- package/dist/lib/computer-use.js +41 -0
- package/dist/lib/lookbackPlans.d.ts +20 -6
- package/dist/lib/lookbackPlans.js +19 -5
- package/dist/lib/update-check.d.ts +11 -10
- package/dist/lib/update-check.js +41 -23
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerUse.d.ts +228 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerUse.js +375 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonScopes.d.ts +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonScopes.js +6 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +40 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopWire.d.ts +140 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopWire.js +46 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +4 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +8 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerUse.d.ts +228 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerUse.js +358 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonScopes.d.ts +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonScopes.js +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +40 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopWire.d.ts +140 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopWire.js +42 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +4 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +23687 -23456
- package/oclif.manifest.json +306 -26
- package/package.json +2 -2
|
@@ -740,6 +740,7 @@ class AgenticStreamClient {
|
|
|
740
740
|
textJoiner = (0, data_provider_1.createAssistantTextJoiner)();
|
|
741
741
|
/** Highest envelope `__seq` folded into `accumulated`, for gap detection. */
|
|
742
742
|
highestSeq = 0;
|
|
743
|
+
publicProjection;
|
|
743
744
|
pendingEnvelopes = new Map();
|
|
744
745
|
replayTimer = null;
|
|
745
746
|
replayFlight = null;
|
|
@@ -972,6 +973,7 @@ class AgenticStreamClient {
|
|
|
972
973
|
reason,
|
|
973
974
|
...(terminal.error ? { message: terminal.error, error: terminal.error } : {}),
|
|
974
975
|
...(typeof terminal.text === 'string' ? { result: terminal.text } : {}),
|
|
976
|
+
...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}),
|
|
975
977
|
});
|
|
976
978
|
return true;
|
|
977
979
|
}
|
|
@@ -1129,6 +1131,7 @@ class AgenticStreamClient {
|
|
|
1129
1131
|
type: 'execution:complete',
|
|
1130
1132
|
sessionId: this.options.sessionId,
|
|
1131
1133
|
result: reading.text,
|
|
1134
|
+
...(reading.publicProjection ? { publicProjection: reading.publicProjection } : {}),
|
|
1132
1135
|
repaired: true,
|
|
1133
1136
|
repairedFrom: 'durable_record',
|
|
1134
1137
|
overturned: { code: exports.WORKER_REPLACED_CODE, reason: exports.DAEMON_HANDOVER_REASON },
|
|
@@ -1146,6 +1149,7 @@ class AgenticStreamClient {
|
|
|
1146
1149
|
message: reading.error,
|
|
1147
1150
|
errors: [reading.error],
|
|
1148
1151
|
...(typeof reading.text === 'string' && reading.text ? { result: reading.text } : {}),
|
|
1152
|
+
...(reading.publicProjection ? { publicProjection: reading.publicProjection } : {}),
|
|
1149
1153
|
handover: { outcome: 'machine_failed', waitedMs },
|
|
1150
1154
|
});
|
|
1151
1155
|
return true;
|
|
@@ -1454,6 +1458,9 @@ class AgenticStreamClient {
|
|
|
1454
1458
|
if (fresh.length === 0)
|
|
1455
1459
|
return; // a pure re-delivery — drop it whole
|
|
1456
1460
|
const scoped = { ...event, envelopes: fresh };
|
|
1461
|
+
if (fresh.some((env) => env?.ev?.t === 'text' &&
|
|
1462
|
+
env.ev?.publicProjection === 'lookback_answer_v1'))
|
|
1463
|
+
this.publicProjection = 'lookback_answer_v1';
|
|
1457
1464
|
// Note what never arrived before folding this batch in (OSK-7631,
|
|
1458
1465
|
// OSK-13518). Checked against the FRESH set: a re-delivery carries old
|
|
1459
1466
|
// seqs and would read as a gap that never happened.
|
|
@@ -1539,6 +1546,8 @@ class AgenticStreamClient {
|
|
|
1539
1546
|
socket.on('message:stream', (event) => {
|
|
1540
1547
|
if (!match(event))
|
|
1541
1548
|
return;
|
|
1549
|
+
if (event.publicProjection === 'lookback_answer_v1')
|
|
1550
|
+
this.publicProjection = event.publicProjection;
|
|
1542
1551
|
const text = typeof event.text === 'string' ? event.text : '';
|
|
1543
1552
|
const delta = typeof event.delta === 'string' ? event.delta : '';
|
|
1544
1553
|
if (text && text.length >= this.accumulated.length)
|
|
@@ -1574,6 +1583,7 @@ class AgenticStreamClient {
|
|
|
1574
1583
|
if (!match(event))
|
|
1575
1584
|
return;
|
|
1576
1585
|
this.accumulated = '';
|
|
1586
|
+
this.publicProjection = undefined;
|
|
1577
1587
|
this.textJoiner.reset();
|
|
1578
1588
|
this.renderedText = '';
|
|
1579
1589
|
// Seqs restart with the stream, so a fresh low seq is not a gap.
|
|
@@ -2099,7 +2109,7 @@ class AgenticStreamClient {
|
|
|
2099
2109
|
const streamedText = (0, widgetTerminalText_1.widgetsAsText)(this.accumulated, { final: true });
|
|
2100
2110
|
void this.readRecordedText().then((recorded) => {
|
|
2101
2111
|
this.settleGapVerdict(streamedText, recorded);
|
|
2102
|
-
this.settle(status, event, recorded
|
|
2112
|
+
this.settle(status, event, recorded);
|
|
2103
2113
|
});
|
|
2104
2114
|
return;
|
|
2105
2115
|
}
|
|
@@ -2124,10 +2134,10 @@ class AgenticStreamClient {
|
|
|
2124
2134
|
try {
|
|
2125
2135
|
const terminal = await reconcile();
|
|
2126
2136
|
if (terminal && typeof terminal.text === 'string' && terminal.text) {
|
|
2127
|
-
return { text: terminal.text };
|
|
2137
|
+
return { text: terminal.text, ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}) };
|
|
2128
2138
|
}
|
|
2129
2139
|
if (terminal)
|
|
2130
|
-
empty = { text: '' };
|
|
2140
|
+
empty = { text: '', ...(terminal.publicProjection ? { publicProjection: terminal.publicProjection } : {}) };
|
|
2131
2141
|
}
|
|
2132
2142
|
catch {
|
|
2133
2143
|
// No evidence either way; try again, then settle on the stream.
|
|
@@ -2135,7 +2145,7 @@ class AgenticStreamClient {
|
|
|
2135
2145
|
}
|
|
2136
2146
|
return empty;
|
|
2137
2147
|
}
|
|
2138
|
-
settle(status, event,
|
|
2148
|
+
settle(status, event, recorded) {
|
|
2139
2149
|
// `execution:complete` carries the final answer as `result`, and it is
|
|
2140
2150
|
// AUTHORITATIVE — preferred over whatever the deltas added up to, not just
|
|
2141
2151
|
// used when they added up to nothing.
|
|
@@ -2146,6 +2156,8 @@ class AgenticStreamClient {
|
|
|
2146
2156
|
// `result` whenever the server sends one makes the returned text the
|
|
2147
2157
|
// agent's actual final message rather than however much of it arrived in
|
|
2148
2158
|
// time.
|
|
2159
|
+
if (event.publicProjection === 'lookback_answer_v1')
|
|
2160
|
+
this.publicProjection = event.publicProjection;
|
|
2149
2161
|
const finalText = typeof event.result === 'string' ? event.result : '';
|
|
2150
2162
|
// …EXCEPT when it is a strict suffix of what we accumulated.
|
|
2151
2163
|
//
|
|
@@ -2168,7 +2180,10 @@ class AgenticStreamClient {
|
|
|
2168
2180
|
this.accumulated.length > finalText.length &&
|
|
2169
2181
|
(this.accumulated.endsWith(finalText) ||
|
|
2170
2182
|
endsWithIgnoringWhitespace(this.accumulated, finalText));
|
|
2171
|
-
if (
|
|
2183
|
+
if (event.publicProjection === 'lookback_answer_v1' && typeof event.result === 'string') {
|
|
2184
|
+
this.accumulated = event.result;
|
|
2185
|
+
}
|
|
2186
|
+
else if (finalText && finalText !== this.accumulated && !accumulationIsSuperset) {
|
|
2172
2187
|
this.accumulated = finalText;
|
|
2173
2188
|
}
|
|
2174
2189
|
// The record wins over a stream known to have lost chunks, unless what
|
|
@@ -2176,9 +2191,10 @@ class AgenticStreamClient {
|
|
|
2176
2191
|
// other way round).
|
|
2177
2192
|
// (Compared as the reader sees it: the record holds a widget as its fallback sentence,
|
|
2178
2193
|
// which is shorter than the block the stream carried, and the raw length would never lose.)
|
|
2179
|
-
if (
|
|
2180
|
-
|
|
2181
|
-
this.accumulated =
|
|
2194
|
+
if (recorded && (recorded.publicProjection === 'lookback_answer_v1' ||
|
|
2195
|
+
recorded.text.length > (0, widgetTerminalText_1.widgetsAsText)(this.accumulated, { final: true }).length)) {
|
|
2196
|
+
this.accumulated = recorded.text;
|
|
2197
|
+
this.publicProjection = recorded.publicProjection;
|
|
2182
2198
|
}
|
|
2183
2199
|
// The finished answer, as every other surface shows it: the web applies
|
|
2184
2200
|
// this same sanitizer, so a scheduling JSON block or leaked function-call
|
|
@@ -2186,7 +2202,7 @@ class AgenticStreamClient {
|
|
|
2186
2202
|
// printed as part of the agent's answer.
|
|
2187
2203
|
// A widget is its fallback sentence in a terminal, first: the sanitizer must read
|
|
2188
2204
|
// prose and never a widget's script (the server projects before it sanitizes, too).
|
|
2189
|
-
this.accumulated = (0, data_provider_1.sanitizeFinalText)((0, widgetTerminalText_1.widgetsAsText)(this.accumulated, { final: true }));
|
|
2205
|
+
this.accumulated = (0, data_provider_1.sanitizeFinalText)((0, widgetTerminalText_1.widgetsAsText)(this.accumulated, { final: true }), this.publicProjection);
|
|
2190
2206
|
if (!this.options.quiet && !this.options.jsonl && this.accumulated) {
|
|
2191
2207
|
const output = this.options.output ?? process.stdout;
|
|
2192
2208
|
const { append, shown } = renderableDelta(this.renderedText, this.accumulated);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export declare function buildComputerUseArgs(action: string | undefined, argv: readonly string[]): string[];
|
|
3
|
+
/** The runtime owns local consent, installation and lifecycle; the REST CLI only dispatches. */
|
|
4
|
+
export declare function forwardComputerUseCommand(command: Command, action: string | undefined, argv: readonly string[]): Promise<void>;
|
|
5
|
+
export declare abstract class ComputerUseCommand extends Command {
|
|
6
|
+
static strict: boolean;
|
|
7
|
+
protected abstract action: string | undefined;
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ComputerUseCommand = void 0;
|
|
4
|
+
exports.buildComputerUseArgs = buildComputerUseArgs;
|
|
5
|
+
exports.forwardComputerUseCommand = forwardComputerUseCommand;
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const exec_runtime_binary_1 = require("./exec-runtime-binary");
|
|
8
|
+
function buildComputerUseArgs(action, argv) {
|
|
9
|
+
return ['computer-use', ...(action ? [action] : []), ...argv];
|
|
10
|
+
}
|
|
11
|
+
/** The runtime owns local consent, installation and lifecycle; the REST CLI only dispatches. */
|
|
12
|
+
async function forwardComputerUseCommand(command, action, argv) {
|
|
13
|
+
const fail = (message, code, exit) => {
|
|
14
|
+
if (argv.includes('--json')) {
|
|
15
|
+
command.log(JSON.stringify({ object: 'computer_use_control', schemaVersion: 1, action: action ?? argv[0] ?? 'help', ok: false, error: { code, message } }));
|
|
16
|
+
return command.exit(exit);
|
|
17
|
+
}
|
|
18
|
+
command.log(message);
|
|
19
|
+
return command.exit(exit);
|
|
20
|
+
};
|
|
21
|
+
try {
|
|
22
|
+
if ((await (0, exec_runtime_binary_1.runtimeBinarySupportsSubcommand)('computer-use')) === false) {
|
|
23
|
+
return fail('This local daemon does not support Computer Use. Update skrrd, then retry.', 'runtime_unsupported', 2);
|
|
24
|
+
}
|
|
25
|
+
command.exit(await (0, exec_runtime_binary_1.execRuntimeBinary)(buildComputerUseArgs(action, argv)));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error.message === 'runtime-binary-not-installed') {
|
|
29
|
+
return fail((0, exec_runtime_binary_1.notInstalledMessage)(), 'runtime_not_installed', 127);
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
class ComputerUseCommand extends core_1.Command {
|
|
35
|
+
static strict = false;
|
|
36
|
+
async run() {
|
|
37
|
+
// Never call parse(): unknown runtime flags must reach the authoritative parser unchanged.
|
|
38
|
+
await forwardComputerUseCommand(this, this.action, this.argv);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.ComputerUseCommand = ComputerUseCommand;
|
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
import { lookbackPlansApi, type CreateLookbackPlanRequest, type LookbackPlan, type LookbackPlanInput } from '@skrr-ai/data-provider';
|
|
2
|
-
export { lookbackPlansApi };
|
|
1
|
+
import { lookbackPlansApi, isLookbackMutationOutcomeUnknown, type CreateLookbackPlanRequest, type LookbackPlan, type LookbackPlanInput } from '@skrr-ai/data-provider';
|
|
2
|
+
export { lookbackPlansApi, isLookbackMutationOutcomeUnknown };
|
|
3
3
|
export declare function lookbackInputFailure(error: unknown): {
|
|
4
|
+
code: "LOOKBACK_INVALID_FIELD" | "LOOKBACK_INVALID_TIME" | "LOOKBACK_INVALID_TIMEZONE" | "LOOKBACK_AMBIGUOUS_TIME" | "LOOKBACK_NONEXISTENT_TIME" | "LOOKBACK_LIMIT_EXCEEDED";
|
|
5
|
+
message: string;
|
|
6
|
+
details: {
|
|
7
|
+
field?: string | undefined;
|
|
8
|
+
stage: string;
|
|
9
|
+
retryCondition: string;
|
|
10
|
+
remedy: string;
|
|
11
|
+
};
|
|
4
12
|
exit: number;
|
|
5
13
|
retryable: boolean;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
code: "LOOKBACK_INVALID_FIELD" | "LOOKBACK_INVALID_TIME" | "LOOKBACK_INVALID_TIMEZONE" | "LOOKBACK_AMBIGUOUS_TIME" | "LOOKBACK_NONEXISTENT_TIME" | "LOOKBACK_LIMIT_EXCEEDED";
|
|
14
|
+
};
|
|
15
|
+
export declare function lookbackUnconfirmedFailure(details: Record<string, unknown>): {
|
|
16
|
+
code: string;
|
|
10
17
|
message: string;
|
|
18
|
+
exit: number;
|
|
19
|
+
retryable: boolean;
|
|
20
|
+
details: {
|
|
21
|
+
stage: string;
|
|
22
|
+
retryCondition: string;
|
|
23
|
+
remedy: string;
|
|
24
|
+
};
|
|
11
25
|
};
|
|
12
26
|
export declare const lookbackPlanIdArg: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
13
27
|
/** JSON can be the plan itself or the canonical create envelope. Never copy identity fields. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.lookbackPlanIdArg = exports.lookbackPlansApi = void 0;
|
|
3
|
+
exports.lookbackPlanIdArg = exports.isLookbackMutationOutcomeUnknown = exports.lookbackPlansApi = void 0;
|
|
4
4
|
exports.lookbackInputFailure = lookbackInputFailure;
|
|
5
|
+
exports.lookbackUnconfirmedFailure = lookbackUnconfirmedFailure;
|
|
5
6
|
exports.lookbackCreateBody = lookbackCreateBody;
|
|
6
7
|
exports.lookbackInput = lookbackInput;
|
|
7
8
|
exports.lookbackPlanSummary = lookbackPlanSummary;
|
|
@@ -9,6 +10,7 @@ exports.lookbackArmingLine = lookbackArmingLine;
|
|
|
9
10
|
const core_1 = require("@oclif/core");
|
|
10
11
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
11
12
|
Object.defineProperty(exports, "lookbackPlansApi", { enumerable: true, get: function () { return data_provider_1.lookbackPlansApi; } });
|
|
13
|
+
Object.defineProperty(exports, "isLookbackMutationOutcomeUnknown", { enumerable: true, get: function () { return data_provider_1.isLookbackMutationOutcomeUnknown; } });
|
|
12
14
|
const followups_1 = require("./followups");
|
|
13
15
|
class LookbackValidationError extends Error {
|
|
14
16
|
issue;
|
|
@@ -18,12 +20,23 @@ class LookbackValidationError extends Error {
|
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
function lookbackInputFailure(error) {
|
|
23
|
+
const repair = { stage: 'validation', retryCondition: 'correct_input',
|
|
24
|
+
remedy: 'Correct the indicated input field before submitting again.',
|
|
25
|
+
...(error instanceof LookbackValidationError ? { field: error.issue.field } : {}),
|
|
26
|
+
};
|
|
21
27
|
return {
|
|
22
28
|
code: error instanceof LookbackValidationError ? error.issue.code : 'LOOKBACK_INVALID_FIELD',
|
|
23
29
|
message: error instanceof Error ? error.message : String(error),
|
|
24
|
-
|
|
25
|
-
exit: 2,
|
|
26
|
-
|
|
30
|
+
// BaseCommand's machine envelope carries additive domain facts in details.
|
|
31
|
+
details: repair, exit: 2, retryable: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function lookbackUnconfirmedFailure(details) {
|
|
35
|
+
return {
|
|
36
|
+
code: 'LOOKBACK_OUTCOME_UNCONFIRMED', message: 'The plan update outcome could not be confirmed. Read the original source or known plan before submitting another change.',
|
|
37
|
+
exit: 5, retryable: false,
|
|
38
|
+
details: { stage: 'lifecycle', retryCondition: 'read_original_source_or_known_plan',
|
|
39
|
+
remedy: 'Read the known plan or original source. Do not submit a replacement source or blindly repeat this mutation.', ...details },
|
|
27
40
|
};
|
|
28
41
|
}
|
|
29
42
|
exports.lookbackPlanIdArg = core_1.Args.string({
|
|
@@ -75,7 +88,8 @@ function lookbackInput(raw, allowPast = false) {
|
|
|
75
88
|
return raw;
|
|
76
89
|
}
|
|
77
90
|
function lookbackPlanSummary(plan) {
|
|
78
|
-
const next = plan.steps.
|
|
91
|
+
const next = plan.steps.filter((step) => step.state === 'pending')
|
|
92
|
+
.reduce((earliest, step) => !earliest || Date.parse(step.runAt) < Date.parse(earliest.runAt) ? step : earliest, undefined);
|
|
79
93
|
return `${plan.id} [${plan.state}] r${plan.revision} ${plan.title || plan.objective}` +
|
|
80
94
|
` ${plan.steps.length} lookbacks${next ? ` next:${next.runAt}` : ''}`;
|
|
81
95
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export type CliInstallKind = 'npm-global' | 'npm-local' | 'dev' | 'bundled' | 'image' | 'homebrew' | 'other';
|
|
2
|
+
export interface CliInstallDescriptor {
|
|
3
|
+
kind: CliInstallKind;
|
|
4
|
+
writable?: boolean;
|
|
5
|
+
npmPrefix?: string;
|
|
6
|
+
}
|
|
2
7
|
export interface CliUpdateAttempt {
|
|
3
8
|
status: 'queued' | 'running' | 'succeeded' | 'failed';
|
|
4
9
|
version: string;
|
|
@@ -10,6 +15,7 @@ export interface CliUpdateAttempt {
|
|
|
10
15
|
message?: string;
|
|
11
16
|
packageRoot?: string;
|
|
12
17
|
npmPath?: string;
|
|
18
|
+
npmPrefix?: string;
|
|
13
19
|
coordinationVersion?: number;
|
|
14
20
|
workerPid?: number;
|
|
15
21
|
interrupted?: boolean;
|
|
@@ -19,10 +25,7 @@ export interface CheckCache {
|
|
|
19
25
|
latest: string | null;
|
|
20
26
|
npmPackage: string | null;
|
|
21
27
|
baseURL: string;
|
|
22
|
-
install?:
|
|
23
|
-
kind: CliInstallKind;
|
|
24
|
-
writable?: boolean;
|
|
25
|
-
};
|
|
28
|
+
install?: CliInstallDescriptor;
|
|
26
29
|
packageRoot?: string;
|
|
27
30
|
npmGlobalRoot?: string | null;
|
|
28
31
|
autoUpdate?: {
|
|
@@ -45,6 +48,7 @@ export interface CliUpdateStatus {
|
|
|
45
48
|
installedVersion: string | null;
|
|
46
49
|
offeredVersion: string | null;
|
|
47
50
|
owner: CliInstallKind;
|
|
51
|
+
npmPrefix?: string;
|
|
48
52
|
state: 'up-to-date' | 'update-available' | 'pending' | 'restart-pending' | 'failed' | 'managed' | 'unknown' | 'check-failed';
|
|
49
53
|
autoUpdateAllowed: boolean;
|
|
50
54
|
autoUpdateReason: string | null;
|
|
@@ -76,12 +80,9 @@ export declare function resolveCliPackageDir(argv?: readonly string[]): string |
|
|
|
76
80
|
* dir beside `dist` exists only in a checkout — published packages ship
|
|
77
81
|
* `dist` and no `src`.
|
|
78
82
|
*/
|
|
79
|
-
export declare function classifyCliInstall(env: NodeJS.ProcessEnv, argv: readonly string[], npmGlobalRoot: string | null, probeWritable?: boolean):
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
};
|
|
83
|
-
export declare function cliUpdateCommand(owner: CliInstallKind): string;
|
|
84
|
-
export declare function buildNudgeLine(currentVersion: string, latest: string | null, _npmPackage: string | null, installKind?: CliInstallKind): string | null;
|
|
83
|
+
export declare function classifyCliInstall(env: NodeJS.ProcessEnv, argv: readonly string[], npmGlobalRoot: string | null, probeWritable?: boolean): CliInstallDescriptor;
|
|
84
|
+
export declare function cliUpdateCommand(owner: CliInstallKind, npmPrefix?: string): string;
|
|
85
|
+
export declare function buildNudgeLine(currentVersion: string, latest: string | null, _npmPackage: string | null, installKind?: CliInstallKind, npmPrefix?: string): string | null;
|
|
85
86
|
export type CliUpdateAction = {
|
|
86
87
|
type: 'nudge';
|
|
87
88
|
line: string;
|
package/dist/lib/update-check.js
CHANGED
|
@@ -207,6 +207,20 @@ function classifyCliInstall(env, argv, npmGlobalRoot, probeWritable = true) {
|
|
|
207
207
|
return { kind: 'other' };
|
|
208
208
|
}
|
|
209
209
|
const nodeModulesDir = path.dirname(path.dirname(packageDir)); // …/node_modules
|
|
210
|
+
const npmPrefix = process.platform !== 'win32' && path.basename(path.dirname(nodeModulesDir)) === 'lib'
|
|
211
|
+
? path.dirname(path.dirname(nodeModulesDir)) : undefined;
|
|
212
|
+
// npm's current default may differ from the prefix that installed this
|
|
213
|
+
// executable. A project directory named `lib` is insufficient evidence:
|
|
214
|
+
// the prefix's public bin must resolve to this exact installed entry.
|
|
215
|
+
let linkedGlobalPrefix = false;
|
|
216
|
+
if (npmPrefix) {
|
|
217
|
+
try {
|
|
218
|
+
linkedGlobalPrefix = !fs.existsSync(path.join(npmPrefix, 'package.json')) &&
|
|
219
|
+
fs.realpathSync(path.join(npmPrefix, 'bin', 'skrr')) ===
|
|
220
|
+
fs.realpathSync(path.join(packageDir, 'bin', 'run.js'));
|
|
221
|
+
}
|
|
222
|
+
catch { /* no executable ownership proof */ }
|
|
223
|
+
}
|
|
210
224
|
if (npmGlobalRoot) {
|
|
211
225
|
let resolvedGlobal = npmGlobalRoot;
|
|
212
226
|
try {
|
|
@@ -222,15 +236,13 @@ function classifyCliInstall(env, argv, npmGlobalRoot, probeWritable = true) {
|
|
|
222
236
|
catch {
|
|
223
237
|
/* compare the raw form */
|
|
224
238
|
}
|
|
225
|
-
if (resolvedNodeModules !== resolvedGlobal)
|
|
239
|
+
if (resolvedNodeModules !== resolvedGlobal && !linkedGlobalPrefix)
|
|
226
240
|
return { kind: 'npm-local' };
|
|
227
241
|
}
|
|
228
|
-
else if (
|
|
242
|
+
else if (!linkedGlobalPrefix) {
|
|
229
243
|
// `npm root -g` gave no answer, so prove the global shape from the path
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
// project-local `<project>/node_modules` never matches either — without
|
|
233
|
-
// proof we must not treat it as ours to auto-update.
|
|
244
|
+
// and matching public executable instead. Without ownership proof we
|
|
245
|
+
// must not treat a project's dependency as ours to auto-update.
|
|
234
246
|
return { kind: 'npm-local' };
|
|
235
247
|
}
|
|
236
248
|
let writable = false;
|
|
@@ -245,7 +257,7 @@ function classifyCliInstall(env, argv, npmGlobalRoot, probeWritable = true) {
|
|
|
245
257
|
/* read-only */
|
|
246
258
|
}
|
|
247
259
|
}
|
|
248
|
-
return { kind: 'npm-global', writable };
|
|
260
|
+
return { kind: 'npm-global', writable, ...(npmPrefix ? { npmPrefix } : {}) };
|
|
249
261
|
}
|
|
250
262
|
/**
|
|
251
263
|
* Resolve `npm root -g` for install classification. Runs ONLY inside the
|
|
@@ -269,12 +281,14 @@ async function resolveNpmGlobalRoot(env = process.env) {
|
|
|
269
281
|
return null;
|
|
270
282
|
}
|
|
271
283
|
}
|
|
272
|
-
function cliUpdateCommand(owner) {
|
|
284
|
+
function cliUpdateCommand(owner, npmPrefix) {
|
|
273
285
|
switch (owner) {
|
|
274
286
|
case 'npm-global':
|
|
275
|
-
return
|
|
287
|
+
return npmPrefix
|
|
288
|
+
? `npm install --global --prefix '${npmPrefix.replace(/'/g, "'\\''")}' ${CLI_PACKAGE}@latest`
|
|
289
|
+
: `npm install -g ${CLI_PACKAGE}@latest`;
|
|
276
290
|
case 'npm-local':
|
|
277
|
-
return `npm
|
|
291
|
+
return `Use npm in the project that owns ${CLI_PACKAGE}`;
|
|
278
292
|
case 'dev':
|
|
279
293
|
return 'npm --prefix cli run build';
|
|
280
294
|
case 'bundled':
|
|
@@ -287,16 +301,18 @@ function cliUpdateCommand(owner) {
|
|
|
287
301
|
return 'Update via your install method — see https://skrr.ai';
|
|
288
302
|
}
|
|
289
303
|
}
|
|
290
|
-
function buildNudgeLine(currentVersion, latest, _npmPackage, installKind = 'npm-global') {
|
|
304
|
+
function buildNudgeLine(currentVersion, latest, _npmPackage, installKind = 'npm-global', npmPrefix) {
|
|
291
305
|
if (!latest || !isOlderVersion(currentVersion, latest))
|
|
292
306
|
return null;
|
|
293
|
-
const command = installKind === 'npm-global'
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
307
|
+
const command = installKind === 'npm-global' && !npmPrefix
|
|
308
|
+
? `npm update -g ${CLI_PACKAGE}` : cliUpdateCommand(installKind, npmPrefix);
|
|
309
|
+
const context = installKind === 'dev'
|
|
310
|
+
? ' to rebuild your checkout'
|
|
311
|
+
: '';
|
|
312
|
+
const advice = installKind === 'npm-local'
|
|
313
|
+
? `Update ${CLI_PACKAGE} using the project that owns ${CLI_PACKAGE}.`
|
|
314
|
+
: `Run \`${command}\`${context}.`;
|
|
315
|
+
return `[skrr] Update available: ${currentVersion} → ${latest}. ${advice} Silence with OVERSKY_NO_UPDATE_NUDGE=1.\n`;
|
|
300
316
|
}
|
|
301
317
|
function decideCliUpdateAction(args) {
|
|
302
318
|
const { currentVersion, cache, env, isTTY } = args;
|
|
@@ -327,10 +343,10 @@ function decideCliUpdateAction(args) {
|
|
|
327
343
|
if (attempt?.status === 'failed')
|
|
328
344
|
return {
|
|
329
345
|
type: 'nudge',
|
|
330
|
-
line: `[skrr] CLI update to ${cache.latest} failed. Inspect \`skrr update --status\` or run \`${cliUpdateCommand(install.kind)}\`.\n`,
|
|
346
|
+
line: `[skrr] CLI update to ${cache.latest} failed. Inspect \`skrr update --status\` or run \`${cliUpdateCommand(install.kind, install.npmPrefix)}\`.\n`,
|
|
331
347
|
};
|
|
332
348
|
}
|
|
333
|
-
const line = buildNudgeLine(currentVersion, cache.latest, CLI_PACKAGE, install.kind);
|
|
349
|
+
const line = buildNudgeLine(currentVersion, cache.latest, CLI_PACKAGE, install.kind, install.npmPrefix);
|
|
334
350
|
return line ? { type: 'nudge', line } : null;
|
|
335
351
|
}
|
|
336
352
|
function readCache(env = process.env) {
|
|
@@ -462,10 +478,11 @@ function readCliUpdateStatus(opts) {
|
|
|
462
478
|
installedVersion,
|
|
463
479
|
offeredVersion,
|
|
464
480
|
owner: install.kind,
|
|
481
|
+
...(install.npmPrefix ? { npmPrefix: install.npmPrefix } : {}),
|
|
465
482
|
state,
|
|
466
483
|
autoUpdateAllowed: autoUpdateReason === null,
|
|
467
484
|
autoUpdateReason,
|
|
468
|
-
command: cliUpdateCommand(install.kind),
|
|
485
|
+
command: cliUpdateCommand(install.kind, install.npmPrefix),
|
|
469
486
|
lastCheckAt: matchingCache?.lastCheckAt ?? null,
|
|
470
487
|
lastAttempt,
|
|
471
488
|
logPath: path.join(directory, 'update.log'),
|
|
@@ -591,7 +608,7 @@ async function performCliUpdate(opts) {
|
|
|
591
608
|
env.SKRR_CLI_UPDATE_SESSION_UNAVAILABLE === '1') {
|
|
592
609
|
return {
|
|
593
610
|
...status,
|
|
594
|
-
message: `This CLI is owned by ${install.kind}${install.kind === 'npm-global' ? ' in a shared or external prefix' : ''}. ${cliUpdateCommand(install.kind)}.`,
|
|
611
|
+
message: `This CLI is owned by ${install.kind}${install.kind === 'npm-global' ? ' in a shared or external prefix' : ''}. ${cliUpdateCommand(install.kind, install.npmPrefix)}.`,
|
|
595
612
|
};
|
|
596
613
|
}
|
|
597
614
|
const recoverableAttempt = status.lastAttempt && ['queued', 'running'].includes(status.lastAttempt.status);
|
|
@@ -620,6 +637,7 @@ async function performCliUpdate(opts) {
|
|
|
620
637
|
attemptedAt: Date.now(),
|
|
621
638
|
packageRoot: packageRoot(argv),
|
|
622
639
|
npmPath,
|
|
640
|
+
...(install.npmPrefix ? { npmPrefix: install.npmPrefix } : {}),
|
|
623
641
|
coordinationVersion: api.COORDINATION_VERSION,
|
|
624
642
|
message: 'Waiting for active CLI sessions to exit; the next invocation uses the new version.',
|
|
625
643
|
});
|
|
@@ -649,7 +667,7 @@ async function maybeWarnOutdatedCli(opts) {
|
|
|
649
667
|
currentVersion: opts.currentVersion,
|
|
650
668
|
cache: {
|
|
651
669
|
...cache,
|
|
652
|
-
install: { kind: status.owner, writable: status.autoUpdateAllowed },
|
|
670
|
+
install: { kind: status.owner, writable: status.autoUpdateAllowed, npmPrefix: status.npmPrefix },
|
|
653
671
|
autoUpdate: status.lastAttempt
|
|
654
672
|
? {
|
|
655
673
|
attemptedAt: status.lastAttempt.attemptedAt,
|