@skrr-ai/cli 0.1.57 → 0.1.59
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 +0 -16
- package/dist/base-command.js +65 -11
- package/dist/commands/agents/show.js +13 -2
- package/dist/commands/harnesses/ping.d.ts +2 -0
- package/dist/commands/harnesses/ping.js +10 -1
- package/dist/commands/login.js +6 -1
- package/dist/commands/logout.d.ts +0 -46
- package/dist/commands/logout.js +28 -2
- package/dist/commands/pair.js +1 -1
- package/dist/lib/agentic-stream.d.ts +54 -0
- package/dist/lib/agentic-stream.js +153 -15
- package/dist/lib/api-fetch.d.ts +11 -0
- package/dist/lib/api-fetch.js +98 -1
- package/dist/lib/auth-storage.d.ts +27 -0
- package/dist/lib/auth-storage.js +42 -0
- package/dist/lib/commitments.js +1 -0
- package/dist/lib/daemonBroker.d.ts +5 -1
- package/dist/lib/daemonBroker.js +13 -2
- package/dist/lib/daemonBrokerRefusal.d.ts +21 -0
- package/dist/lib/daemonBrokerRefusal.js +87 -2
- package/dist/lib/daemonHandoff.d.ts +1 -1
- package/dist/lib/daemonHandoff.js +18 -0
- package/dist/lib/dedicated-guest.d.ts +13 -0
- package/dist/lib/dedicated-guest.js +22 -0
- package/dist/lib/dedicated-machines.js +17 -1
- package/dist/lib/dedicated-service.d.ts +1 -13
- package/dist/lib/dedicated-service.js +7 -19
- package/dist/lib/login.js +12 -3
- package/dist/lib/oauthLogin.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +41 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +147 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +17 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +38 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +5 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.d.ts +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +40 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +41 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +147 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +17 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +38 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.d.ts +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +39 -6
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +4388 -4349
- package/oclif.manifest.json +26425 -26425
- package/package.json +1 -1
package/dist/base-command.d.ts
CHANGED
|
@@ -20,22 +20,6 @@ type OclifFlagOutput = {
|
|
|
20
20
|
type OclifArgOutput = {
|
|
21
21
|
[name: string]: unknown;
|
|
22
22
|
};
|
|
23
|
-
/**
|
|
24
|
-
* Base class for every CLI command.
|
|
25
|
-
*
|
|
26
|
-
* Responsibilities:
|
|
27
|
-
* - Run the one-shot auth-out-of-config migration (Phase H).
|
|
28
|
-
* - Parse global flags (`--bare`, `--token`) from argv once and thread
|
|
29
|
-
* them through the credential resolver and the HTTP adapter.
|
|
30
|
-
* - Resolve the active credential via the CredentialResolver chain —
|
|
31
|
-
* flag → env → helper → keychain → file (Phase H.1). Returns the
|
|
32
|
-
* source name for display in `skrr whoami` / `skrr doctor`.
|
|
33
|
-
* - Wire the Node HttpAdapter into @skrr-ai/data-provider so
|
|
34
|
-
* `dataService.*` calls use our fetch-based transport with bearer
|
|
35
|
-
* auth that matches what the resolver surfaced.
|
|
36
|
-
* - Provide `requireAuth()` for commands that need a signed-in user,
|
|
37
|
-
* and `handleApiError()` for uniform error translation.
|
|
38
|
-
*/
|
|
39
23
|
export declare abstract class BaseCommand extends Command {
|
|
40
24
|
/** Add the owning agent's web detail link without overwriting resource URLs. */
|
|
41
25
|
protected withAgentContext<T>(agentId: string, value: T): T & {
|
package/dist/base-command.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.isDaemonScopedCredential = isDaemonScopedCredential;
|
|
|
16
16
|
exports.describeDaemonScopedCredential = describeDaemonScopedCredential;
|
|
17
17
|
exports.isTypedServerRefusal = isTypedServerRefusal;
|
|
18
18
|
exports.isRetryableFailure = isRetryableFailure;
|
|
19
|
+
const node_fs_1 = require("node:fs");
|
|
19
20
|
const core_1 = require("@oclif/core");
|
|
20
21
|
Object.defineProperty(exports, "Flags", { enumerable: true, get: function () { return core_1.Flags; } });
|
|
21
22
|
const option_hint_1 = require("./lib/option-hint");
|
|
@@ -51,6 +52,7 @@ const update_check_1 = require("./lib/update-check");
|
|
|
51
52
|
const web_url_1 = require("./lib/web-url");
|
|
52
53
|
const edge_error_page_1 = require("./lib/edge-error-page");
|
|
53
54
|
const delegated_cli_1 = require("./lib/delegated-cli");
|
|
55
|
+
const dedicated_guest_1 = require("./lib/dedicated-guest");
|
|
54
56
|
const tasks_1 = require("./lib/tasks");
|
|
55
57
|
function assigneePayloadNeedsResolution(payload) {
|
|
56
58
|
const fields = [
|
|
@@ -82,6 +84,25 @@ function assigneePayloadNeedsResolution(payload) {
|
|
|
82
84
|
* - Provide `requireAuth()` for commands that need a signed-in user,
|
|
83
85
|
* and `handleApiError()` for uniform error translation.
|
|
84
86
|
*/
|
|
87
|
+
/**
|
|
88
|
+
* On a Dedicated Runtime guest, the description of a daemon that did not
|
|
89
|
+
* answer the hand-off at all — revoked and exited, cycling under systemd, or
|
|
90
|
+
* gone behind a stale descriptor (OSK-12473). Null anywhere else, where "Not
|
|
91
|
+
* signed in. Run `skrr login` first." is the right sentence.
|
|
92
|
+
*/
|
|
93
|
+
function describeAbsentGuestDaemon(outcome, bin) {
|
|
94
|
+
const onGuest = outcome.dedicatedGuest === true || (0, dedicated_guest_1.runningOnDedicatedRuntimeGuest)();
|
|
95
|
+
if (!onGuest)
|
|
96
|
+
return null;
|
|
97
|
+
let descriptorPresent = false;
|
|
98
|
+
try {
|
|
99
|
+
descriptorPresent = (0, node_fs_1.existsSync)(daemonBroker_1.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* unreadable reads as absent */
|
|
103
|
+
}
|
|
104
|
+
return (0, daemonBrokerRefusal_1.describeGuestDaemonUnavailable)(outcome, { bin, onGuest, descriptorPresent });
|
|
105
|
+
}
|
|
85
106
|
class BaseCommand extends core_1.Command {
|
|
86
107
|
/** Add the owning agent's web detail link without overwriting resource URLs. */
|
|
87
108
|
withAgentContext(agentId, value) {
|
|
@@ -375,7 +396,9 @@ class BaseCommand extends core_1.Command {
|
|
|
375
396
|
}
|
|
376
397
|
: null;
|
|
377
398
|
}
|
|
378
|
-
else if ((this.brokerRefusal =
|
|
399
|
+
else if ((this.brokerRefusal =
|
|
400
|
+
(0, daemonBrokerRefusal_1.describeBrokerRefusal)(autoResult.outcome, this.config.bin) ??
|
|
401
|
+
describeAbsentGuestDaemon(autoResult.outcome, this.config.bin))) {
|
|
379
402
|
// Reported by requireAuth(); nothing to print here.
|
|
380
403
|
}
|
|
381
404
|
else if (autoResult.outcome.reason === 'rate_limited') {
|
|
@@ -1140,19 +1163,45 @@ class BaseCommand extends core_1.Command {
|
|
|
1140
1163
|
bin: this.config.bin,
|
|
1141
1164
|
cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
|
|
1142
1165
|
});
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1166
|
+
// OSK-12474 — `apiFetch` asked the local daemon for a hand-off after the
|
|
1167
|
+
// refused refresh, and the daemon refused too. Its reason decides the
|
|
1168
|
+
// repair (OSK-12149, OSK-12151), and on a Dedicated guest there is no
|
|
1169
|
+
// login to run, so its remedy replaces the generic "Run login".
|
|
1170
|
+
const brokerRefusal = presentedCiToken || !e.brokerFailure
|
|
1171
|
+
? null
|
|
1172
|
+
: (0, daemonBrokerRefusal_1.describeBrokerRefusal)(e.brokerFailure, this.config.bin);
|
|
1173
|
+
const renewal = refreshRefusal
|
|
1174
|
+
? refreshRefusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
|
|
1175
|
+
: "This CLI's own sign-in could not be renewed.";
|
|
1176
|
+
const details = {
|
|
1177
|
+
...(refreshRefusal && bodyCode ? { accessTokenCode: bodyCode } : {}),
|
|
1178
|
+
...(brokerRefusal
|
|
1179
|
+
? { broker: brokerRefusal.broker, suggestion: brokerRefusal.remedy }
|
|
1180
|
+
: {}),
|
|
1181
|
+
};
|
|
1182
|
+
let authMessage;
|
|
1183
|
+
if (presentedCiToken) {
|
|
1184
|
+
authMessage =
|
|
1185
|
+
`Authentication failed: this is an \`osk_ci_*\` token, which only bootstraps a ` +
|
|
1146
1186
|
`daemon (\`OVERSKY_TOKEN=<token> skrrd start\`). ${this.config.bin} commands do ` +
|
|
1147
|
-
`not accept it — sign in with \`${this.config.bin} login\` instead
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1187
|
+
`not accept it — sign in with \`${this.config.bin} login\` instead.`;
|
|
1188
|
+
}
|
|
1189
|
+
else if (brokerRefusal) {
|
|
1190
|
+
authMessage = `Authentication failed. ${renewal} ${brokerRefusal.headline}\n→ ${brokerRefusal.remedy}`;
|
|
1191
|
+
}
|
|
1192
|
+
else if (refreshRefusal) {
|
|
1193
|
+
authMessage = `Authentication failed. ${refreshRefusal.message}`;
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
authMessage = `Authentication failed. Run \`${this.config.bin} login\` to sign in again.`;
|
|
1197
|
+
}
|
|
1198
|
+
this.failWithCliError({
|
|
1199
|
+
message: say(authMessage),
|
|
1151
1200
|
code: refreshRefusal?.code || bodyCode || 'AUTHENTICATION_FAILED',
|
|
1152
1201
|
status: e.status,
|
|
1153
1202
|
exit: exitCode,
|
|
1154
1203
|
retryable: true,
|
|
1155
|
-
...(
|
|
1204
|
+
...(Object.keys(details).length > 0 ? { details } : {}),
|
|
1156
1205
|
});
|
|
1157
1206
|
}
|
|
1158
1207
|
else if ((e.status === 403 || e.status === 504) &&
|
|
@@ -1438,9 +1487,14 @@ class BaseCommand extends core_1.Command {
|
|
|
1438
1487
|
bin: this.config.bin,
|
|
1439
1488
|
cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
|
|
1440
1489
|
});
|
|
1441
|
-
const
|
|
1490
|
+
const brokerRefusal = e.brokerFailure
|
|
1491
|
+
? (0, daemonBrokerRefusal_1.describeBrokerRefusal)(e.brokerFailure, this.config.bin)
|
|
1492
|
+
: null;
|
|
1493
|
+
const why = (refusal
|
|
1442
1494
|
? refusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
|
|
1443
|
-
: 'The server did not accept this sign-in.'
|
|
1495
|
+
: 'The server did not accept this sign-in.') +
|
|
1496
|
+
// OSK-12474 — the daemon was asked for a hand-off and refused; say why.
|
|
1497
|
+
(brokerRefusal ? ` ${brokerRefusal.headline}` : '');
|
|
1444
1498
|
this.failWithCliError({
|
|
1445
1499
|
message: `Authentication failed${statusSuffix}. ${why} Your ${write.summary} was not applied; it ` +
|
|
1446
1500
|
`was saved locally as ${queuedId} so it is not lost. Sign in with ` +
|
|
@@ -54,8 +54,19 @@ function agentPermissionLines(agent, harness) {
|
|
|
54
54
|
return [`Permission: ${stored} — enforced by the ${harness} harness`];
|
|
55
55
|
}
|
|
56
56
|
const lines = [];
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const from = PERMISSION_SOURCE_LABELS[effective.source] ?? effective.source;
|
|
58
|
+
if (typeof effective.refusedMode === 'string' && effective.refusedMode) {
|
|
59
|
+
// OSK-12316: a mode the harness could only honour by WIDENING it is refused
|
|
60
|
+
// at dispatch (OSK-12154); it does not "run as" anything.
|
|
61
|
+
const required = typeof effective.requiredMode === 'string' && effective.requiredMode
|
|
62
|
+
? effective.requiredMode
|
|
63
|
+
: null;
|
|
64
|
+
lines.push(`Permission: ${effective.refusedMode} (from ${from}) — turns are REFUSED: the ${harness} harness cannot run it without allowing more`);
|
|
65
|
+
if (required) {
|
|
66
|
+
lines.push(`${pad}Set ${required} to run (skrr agents update <agent> --default-permission-mode ${required}), or pass --permission-mode for a turn.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (typeof effective.mode === 'string' && effective.mode) {
|
|
59
70
|
lines.push(`Permission: ${effective.mode} (from ${from}) — enforced by the ${harness} harness`);
|
|
60
71
|
}
|
|
61
72
|
else {
|
|
@@ -8,6 +8,8 @@ import { BaseCommand } from '../../base-command';
|
|
|
8
8
|
* sends, and testing for them alone is what made the success branch dead code.
|
|
9
9
|
*/
|
|
10
10
|
export declare function isPingSuccess(status: string | undefined): boolean;
|
|
11
|
+
/** The server declared a completed probe failure, not a still-pending poll. */
|
|
12
|
+
export declare function describePingFailure(status: string, error?: string | null): string;
|
|
11
13
|
export default class HarnessesPing extends BaseCommand {
|
|
12
14
|
static description: string;
|
|
13
15
|
static examples: string[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isPingSuccess = isPingSuccess;
|
|
4
|
+
exports.describePingFailure = describePingFailure;
|
|
4
5
|
const core_1 = require("@oclif/core");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
const harnesses_1 = require("../../lib/harnesses");
|
|
@@ -31,6 +32,14 @@ const POLL_INTERVAL_MS = 300;
|
|
|
31
32
|
function isPingSuccess(status) {
|
|
32
33
|
return status === 'completed' || status === 'ok' || status === 'success';
|
|
33
34
|
}
|
|
35
|
+
/** The server declared a completed probe failure, not a still-pending poll. */
|
|
36
|
+
function describePingFailure(status, error) {
|
|
37
|
+
const detail = typeof error === 'string' && error.trim() ? `: ${error.trim()}` : '.';
|
|
38
|
+
if (status === 'timeout') {
|
|
39
|
+
return `Ping timed out${detail} The daemon is registered but may be offline or not responding.`;
|
|
40
|
+
}
|
|
41
|
+
return `Ping ${status}${detail} The daemon is registered but may be offline or not responding.`;
|
|
42
|
+
}
|
|
34
43
|
class HarnessesPing extends base_command_1.BaseCommand {
|
|
35
44
|
static description = 'Ping a harness and wait for its daemon to answer (local only)';
|
|
36
45
|
static examples = [
|
|
@@ -90,7 +99,7 @@ class HarnessesPing extends base_command_1.BaseCommand {
|
|
|
90
99
|
await this.logReadiness(args.harness);
|
|
91
100
|
}
|
|
92
101
|
else {
|
|
93
|
-
this.
|
|
102
|
+
this.error(describePingFailure(result.status, result.error), { exit: 1 });
|
|
94
103
|
}
|
|
95
104
|
return;
|
|
96
105
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -234,6 +234,11 @@ class Login extends base_command_1.BaseCommand {
|
|
|
234
234
|
if (handoff.status === 'delivered') {
|
|
235
235
|
this.log(`The local daemon was signed in too — ${handoff.detail}.`);
|
|
236
236
|
}
|
|
237
|
+
else if (handoff.reason === 'platform-managed') {
|
|
238
|
+
// A Dedicated guest: its daemon is the platform's, so there is
|
|
239
|
+
// nothing to install and nothing to hand over (OSK-12296).
|
|
240
|
+
this.log("This machine's daemon is managed by the platform; there is nothing to set up here.");
|
|
241
|
+
}
|
|
237
242
|
else if (handoff.reason === 'foreign-root') {
|
|
238
243
|
// Not a failure and not a hint to re-login anything: the daemon is
|
|
239
244
|
// the default root's, and this is a second root (OSK-12189).
|
|
@@ -391,7 +396,7 @@ class Login extends base_command_1.BaseCommand {
|
|
|
391
396
|
refreshExpiresAt: result.refreshExpiresAt
|
|
392
397
|
? new Date(result.refreshExpiresAt).getTime()
|
|
393
398
|
: undefined,
|
|
394
|
-
}, { serverOrigin: this.cliConfig.baseURL, clearReauth: true });
|
|
399
|
+
}, { serverOrigin: this.cliConfig.baseURL, clearReauth: true, provenance: 'user_login' });
|
|
395
400
|
this.log('');
|
|
396
401
|
this.log(`Signed in as ${result.user.email} via recovery code on ` +
|
|
397
402
|
`${(0, config_1.describeDeployment)(this.cliConfig.baseURL)}.`);
|
|
@@ -5,52 +5,6 @@ export declare function shouldSkipLocalLogout(input: {
|
|
|
5
5
|
hasUnreadableEncryptedToken: boolean;
|
|
6
6
|
allDevices: boolean;
|
|
7
7
|
}): boolean;
|
|
8
|
-
/**
|
|
9
|
-
* `skrr logout` / `skrr logout --all-devices`
|
|
10
|
-
*
|
|
11
|
-
* Default mode (no flags): revokes the current CLI refresh session on the
|
|
12
|
-
* server (best-effort) and clears every local backend (Keychain on macOS,
|
|
13
|
-
* cli-auth.json fallback, and any legacy token fields in cli-config.json).
|
|
14
|
-
* `baseURL` and `cliId` are preserved so the next `skrr login` defaults to
|
|
15
|
-
* the same host and reuses the per-machine identity (which becomes a new
|
|
16
|
-
* refresh family — server-side revocation invalidated the prior family).
|
|
17
|
-
*
|
|
18
|
-
* `--all-devices` (L7 fleet-grade logout): in addition to the local
|
|
19
|
-
* cleanup, iterate the user's registered daemons and revoke each one
|
|
20
|
-
* server-side. This is the threat-model-correct response for a stolen-
|
|
21
|
-
* laptop scenario where the user wants every persistent daemon credential
|
|
22
|
-
* gone immediately, regardless of which machine they're calling from.
|
|
23
|
-
*
|
|
24
|
-
* Architectural note: the L7 server-side has a bulk endpoint
|
|
25
|
-
* (`POST /api/auth/sessions/revoke-all`) but it requires a user-scope JWT.
|
|
26
|
-
* CLI sessions are minted with `scope: 'cli'` (interactive single-machine
|
|
27
|
-
* use), so they cannot directly hit the bulk endpoint — that's by design,
|
|
28
|
-
* preventing a leaked CLI credential from kicking the user out of every
|
|
29
|
-
* other device. We instead iterate `DELETE /api/daemon-resources/:id`
|
|
30
|
-
* which CLI scope CAN hit (per-resource ownership check, not scope check).
|
|
31
|
-
* N round trips for N daemons; for typical fleet sizes (< 20) this is
|
|
32
|
-
* sub-second. Each per-daemon DELETE produces its own audit row keyed by
|
|
33
|
-
* `'user_initiated_per_device'`, which preserves forensic granularity.
|
|
34
|
-
*
|
|
35
|
-
* The CLI's own refresh session is NOT a Daemon resource (it's a
|
|
36
|
-
* `scope='cli'` refresh-session row with no Daemon FK), so it's invisible
|
|
37
|
-
* to `/api/daemon-resources`. We revoke it separately via
|
|
38
|
-
* `revokeCurrentRefreshToken()`.
|
|
39
|
-
*
|
|
40
|
-
* Ordering rationale for `--all-devices`:
|
|
41
|
-
* 1. `runAllDevices` is attempted first (remote sweep).
|
|
42
|
-
* 2. If it throws (network error, API unreachable), the error is captured
|
|
43
|
-
* and a warning is emitted — but execution CONTINUES to step 3.
|
|
44
|
-
* Rationale: a user who runs `logout --all-devices` after a security
|
|
45
|
-
* incident must not be left with live tokens on disk just because the
|
|
46
|
-
* API was momentarily unreachable. Local cleanup is always the safer
|
|
47
|
-
* default; re-running --all-devices later from a connected machine is
|
|
48
|
-
* idempotent and cleans up whatever the first attempt missed.
|
|
49
|
-
* 3. If the user explicitly declines the confirmation prompt inside
|
|
50
|
-
* `runAllDevices`, that path calls `this.exit(1)` which invokes
|
|
51
|
-
* `process.exit` — the try/catch is never reached, so local cleanup
|
|
52
|
-
* is correctly skipped (user said "abort").
|
|
53
|
-
*/
|
|
54
8
|
export default class Logout extends BaseCommand {
|
|
55
9
|
static description: string;
|
|
56
10
|
static flags: {
|
package/dist/commands/logout.js
CHANGED
|
@@ -40,6 +40,7 @@ const config_1 = require("../lib/config");
|
|
|
40
40
|
const first_party_harness_managed_1 = require("../lib/first-party-harness-managed");
|
|
41
41
|
const auth_storage_1 = require("../lib/auth-storage");
|
|
42
42
|
const daemonBroker_1 = require("../lib/daemonBroker");
|
|
43
|
+
const dedicated_service_1 = require("../lib/dedicated-service");
|
|
43
44
|
const refresh_1 = require("../lib/refresh");
|
|
44
45
|
const api_fetch_1 = require("../lib/api-fetch");
|
|
45
46
|
const cred_envelope_1 = require("../lib/cred-envelope");
|
|
@@ -97,6 +98,18 @@ function shouldSkipLocalLogout(input) {
|
|
|
97
98
|
* `process.exit` — the try/catch is never reached, so local cleanup
|
|
98
99
|
* is correctly skipped (user said "abort").
|
|
99
100
|
*/
|
|
101
|
+
/**
|
|
102
|
+
* True only on a Dedicated Runtime guest whose platform hand-off can still
|
|
103
|
+
* serve this CLI. A laptop daemon's island file ALSO advertises
|
|
104
|
+
* `access_token`, so the descriptor alone is not evidence of a platform-managed
|
|
105
|
+
* machine: on a laptop, gating on it alone told a person who had just signed
|
|
106
|
+
* out that "skrr keeps working here through the platform's managed hand-off"
|
|
107
|
+
* (OSK-12467). The guest markers are what make that sentence true.
|
|
108
|
+
*/
|
|
109
|
+
function platformManagedHandoff() {
|
|
110
|
+
return (0, dedicated_service_1.runningOnDedicatedRuntimeGuest)() && Boolean((0, daemonBroker_1.findBrokeredHandoffDescriptor)());
|
|
111
|
+
}
|
|
112
|
+
const SIGN_IN_AGAIN = 'To sign in again, run `skrr login`.';
|
|
100
113
|
class Logout extends base_command_1.BaseCommand {
|
|
101
114
|
static description = 'Sign out and clear locally-stored tokens. ' +
|
|
102
115
|
'--all-devices also revokes every registered daemon for this account.';
|
|
@@ -134,12 +147,12 @@ class Logout extends base_command_1.BaseCommand {
|
|
|
134
147
|
// `access_token`) there IS no local credential to clear — the CLI holds
|
|
135
148
|
// a per-process token redeemed over loopback and nothing is persisted.
|
|
136
149
|
// Say that honestly rather than reporting a logout that did not happen.
|
|
137
|
-
if ((
|
|
150
|
+
if (platformManagedHandoff()) {
|
|
138
151
|
this.log("This machine is signed in through the platform's managed hand-off; " +
|
|
139
152
|
'there is no local credential to clear.');
|
|
140
153
|
}
|
|
141
154
|
else {
|
|
142
|
-
this.log(
|
|
155
|
+
this.log(`Not signed in. Nothing to do. ${SIGN_IN_AGAIN}`);
|
|
143
156
|
}
|
|
144
157
|
return;
|
|
145
158
|
}
|
|
@@ -257,6 +270,19 @@ class Logout extends base_command_1.BaseCommand {
|
|
|
257
270
|
: 'Signed out from all devices.'
|
|
258
271
|
: 'Signed out.';
|
|
259
272
|
this.log(finalMessage);
|
|
273
|
+
// On a brokered Dedicated guest the sign-in just removed was the person's
|
|
274
|
+
// OWN (an explicit `skrr login`); the machine's platform-managed access is
|
|
275
|
+
// untouched and skrr keeps working through it. Say both, so "Signed out."
|
|
276
|
+
// is not read as "this machine can no longer reach skrr" (OSK-12043).
|
|
277
|
+
// Anywhere else there is no such hand-off, so say how to sign back in
|
|
278
|
+
// instead (OSK-12467).
|
|
279
|
+
if (platformManagedHandoff()) {
|
|
280
|
+
this.log('Removed your own sign-in from this machine. ' +
|
|
281
|
+
"skrr keeps working here through the platform's managed hand-off.");
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
this.log(SIGN_IN_AGAIN);
|
|
285
|
+
}
|
|
260
286
|
if (sweptLegacy.length > 0) {
|
|
261
287
|
this.log(`Also removed ${sweptLegacy.length} credential${sweptLegacy.length === 1 ? '' : 's'} left by the previous product identity:`);
|
|
262
288
|
for (const service of sweptLegacy)
|
package/dist/commands/pair.js
CHANGED
|
@@ -273,7 +273,7 @@ function persistPairCredentials(plaintext, serverOrigin) {
|
|
|
273
273
|
(0, auth_storage_1.writeToBackend)({
|
|
274
274
|
token: plaintext.token,
|
|
275
275
|
...(plaintext.refreshToken ? { refreshToken: plaintext.refreshToken } : {}),
|
|
276
|
-
}, { serverOrigin, clearReauth: true });
|
|
276
|
+
}, { serverOrigin, clearReauth: true, provenance: 'user_login' });
|
|
277
277
|
if (plaintext.dataEncryptionKey) {
|
|
278
278
|
// The unified `writeToBackend` API only carries token+refresh
|
|
279
279
|
// today. For now, persist the DEK as a sibling field on the next
|
|
@@ -93,7 +93,35 @@ export type AgenticStreamOptions = {
|
|
|
93
93
|
idleTerminalProbeMs?: number;
|
|
94
94
|
/** How long to keep trying to attach before reporting a detach. Default 30s. */
|
|
95
95
|
attachTimeoutMs?: number;
|
|
96
|
+
/**
|
|
97
|
+
* How long a prompt listens to (and throws away) input before it is shown.
|
|
98
|
+
* Default {@link TYPE_AHEAD_DISCARD_MS}. See {@link discardTypeAhead}.
|
|
99
|
+
*/
|
|
100
|
+
typeAheadDiscardMs?: number;
|
|
96
101
|
};
|
|
102
|
+
/**
|
|
103
|
+
* How long input arriving just before a prompt is shown is thrown away.
|
|
104
|
+
*
|
|
105
|
+
* Long enough for bytes already typed into the terminal to be read out of the
|
|
106
|
+
* kernel once stdin starts flowing (one event-loop turn in practice), short
|
|
107
|
+
* enough that nobody waits for it.
|
|
108
|
+
*/
|
|
109
|
+
export declare const TYPE_AHEAD_DISCARD_MS = 60;
|
|
110
|
+
/**
|
|
111
|
+
* Throw away whatever was typed BEFORE a prompt is shown (OSK-12468).
|
|
112
|
+
*
|
|
113
|
+
* A key pressed while the turn was still streaming sits in the stream's buffer
|
|
114
|
+
* or the terminal's, and the first `readline.question` read it as the answer:
|
|
115
|
+
* an owner typed `y` before any `[y/N]` was visible and approved a Bash command
|
|
116
|
+
* they had never seen. An approval is only consent to what was on the screen,
|
|
117
|
+
* so every prompt now starts from an empty input: bytes the stream already
|
|
118
|
+
* holds are read and dropped, then stdin flows into a discarding listener for
|
|
119
|
+
* `windowMs` so the terminal's own buffer is drained too. Raw mode is switched
|
|
120
|
+
* on for that window because a half-typed line (`y`, no Enter) is invisible to
|
|
121
|
+
* a read in canonical mode and would otherwise surface as soon as readline
|
|
122
|
+
* enters raw mode itself. A Ctrl-C in the discarded input is still honoured.
|
|
123
|
+
*/
|
|
124
|
+
export declare function discardTypeAhead(input: NodeJS.ReadableStream, windowMs?: number): Promise<void>;
|
|
97
125
|
/**
|
|
98
126
|
* What a permission request would DO, in one line a person can judge.
|
|
99
127
|
*
|
|
@@ -371,6 +399,12 @@ export declare class AgenticStreamClient {
|
|
|
371
399
|
* (OSK-12197) instead of leaving `[y/N]` open after the run stopped asking.
|
|
372
400
|
*/
|
|
373
401
|
private readonly openPermissionPrompts;
|
|
402
|
+
/**
|
|
403
|
+
* Question prompts on the terminal still waiting for an answer, by
|
|
404
|
+
* questionId, so a server `question:cancel` can close the one it names
|
|
405
|
+
* (OSK-12208) the same way `permission:cancel` closes a permission prompt.
|
|
406
|
+
*/
|
|
407
|
+
private readonly openQuestionPrompts;
|
|
374
408
|
/** Text already written to the terminal, for suffix-only rendering. */
|
|
375
409
|
private renderedText;
|
|
376
410
|
private idleTimer;
|
|
@@ -455,7 +489,27 @@ export declare class AgenticStreamClient {
|
|
|
455
489
|
* and close the terminal prompt if this client is still showing it.
|
|
456
490
|
*/
|
|
457
491
|
private handlePermissionCancel;
|
|
492
|
+
/**
|
|
493
|
+
* The server closed a question (OSK-12208): nobody answered it in time, or
|
|
494
|
+
* the turn ended first. Say so, and close the terminal prompt if this
|
|
495
|
+
* client is still showing it — the question twin of handlePermissionCancel.
|
|
496
|
+
*/
|
|
497
|
+
private handleQuestionCancel;
|
|
498
|
+
/** Prompts run one at a time: two readlines on one stdin would both read a line. */
|
|
499
|
+
private askQueue;
|
|
458
500
|
private ask;
|
|
501
|
+
/**
|
|
502
|
+
* One prompt, on a readline that exists only while the prompt is on screen.
|
|
503
|
+
*
|
|
504
|
+
* The interface used to be created at the first prompt and kept until the
|
|
505
|
+
* run closed, and it was created AFTER stdin had been accumulating whatever
|
|
506
|
+
* the person typed while the turn streamed — so the first question answered
|
|
507
|
+
* itself from that type-ahead (OSK-12468). Now the input is emptied first
|
|
508
|
+
* ({@link discardTypeAhead}), only then is the question shown, and the
|
|
509
|
+
* interface is closed with the answer, so nothing typed between two prompts
|
|
510
|
+
* can answer the second one either.
|
|
511
|
+
*/
|
|
512
|
+
private askNow;
|
|
459
513
|
/**
|
|
460
514
|
* True the first time this (kind, identity) pair is seen; false afterwards.
|
|
461
515
|
* The identity is the event's OWN — a tool-use id, a message id — never a
|