@skrr-ai/cli 0.1.65 → 0.1.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agents/visibility.d.ts +9 -0
- package/dist/commands/agents/visibility.js +30 -0
- package/dist/commands/logout.js +32 -63
- package/dist/commands/memory/mounts/resolve.js +1 -2
- package/dist/commands/memory/save.d.ts +1 -0
- package/dist/commands/memory/save.js +16 -55
- package/dist/commands/memory/stores/list.js +1 -2
- package/dist/commands/store/admissions/approve.js +7 -5
- package/dist/commands/store/admissions/list.js +17 -3
- package/dist/commands/store/admissions/refuse.js +7 -5
- package/dist/commands/store/install.js +5 -5
- package/dist/commands/store/update.js +5 -1
- package/dist/commands/store/updates.d.ts +1 -1
- package/dist/commands/store/updates.js +3 -3
- package/dist/lib/auth-core-init.js +21 -15
- package/dist/lib/keychain.d.ts +7 -5
- package/dist/lib/keychain.js +10 -10
- package/dist/lib/refresh.d.ts +2 -0
- package/dist/lib/refresh.js +20 -2
- package/dist/lib/scoped-memory-command.d.ts +4 -2
- package/dist/lib/scoped-memory-command.js +27 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +7 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +36 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +7 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +36 -4
- package/dist/node_modules/@skrr-ai/data-provider/index.js +12 -4
- package/oclif.manifest.json +28195 -28186
- package/package.json +1 -1
|
@@ -17,11 +17,10 @@ exports.__resetPrefetchCliAuthForTest = __resetPrefetchCliAuthForTest;
|
|
|
17
17
|
* Runs as a side-effect import from base-command.ts so every command path
|
|
18
18
|
* (including future ones) gets the same DI setup. Idempotent and cheap.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
20
|
+
* Runtime scope:
|
|
21
21
|
* - Logger: a thin shim over `console`. CLI is a one-shot process; we
|
|
22
22
|
* don't ship winston for this.
|
|
23
|
-
* - configDir:
|
|
24
|
-
* ~/.skrr/ with a one-shot read-shim migration.
|
|
23
|
+
* - configDir: the shared ~/.skrr/ root, with profile state below it.
|
|
25
24
|
* - Kill switches: respect OVERSKY_DISABLE_REFRESH_SCHEDULER for parity
|
|
26
25
|
* with the daemon's rollback knob.
|
|
27
26
|
* - binaryName: 'skrr' so reauth banners say `skrr login` not `oversky login`.
|
|
@@ -42,6 +41,7 @@ const auth_core_1 = require("@skrr-ai/auth-core");
|
|
|
42
41
|
const cred_envelope_1 = require("./cred-envelope");
|
|
43
42
|
const device_identity_1 = require("./device-identity");
|
|
44
43
|
const auth_storage_1 = require("./auth-storage");
|
|
44
|
+
const keychain_1 = require("./keychain");
|
|
45
45
|
function envFlag(name) {
|
|
46
46
|
const v = process.env[name];
|
|
47
47
|
return v === '1' || v === 'true' || v === 'TRUE';
|
|
@@ -165,12 +165,6 @@ const cliLogger = {
|
|
|
165
165
|
},
|
|
166
166
|
error: (...args) => console.error('[auth-core error]', ...args),
|
|
167
167
|
};
|
|
168
|
-
/**
|
|
169
|
-
* Profile name in shared on-disk paths. Pre-Phase-K every consumer
|
|
170
|
-
* resolves to `'default'`; matches `daemon/src/auth.ts:DEFAULT_PROFILE`
|
|
171
|
-
* so cross-binary file paths align byte-for-byte.
|
|
172
|
-
*/
|
|
173
|
-
const DEFAULT_PROFILE = 'default';
|
|
174
168
|
const DEDICATED_AUTH_GENERATION_MARKER = 'dedicated-runtime-auth-generation';
|
|
175
169
|
/**
|
|
176
170
|
* A Dedicated Runtime's workspace survives replacement but `/etc/machine-id`
|
|
@@ -192,7 +186,11 @@ async function prepareDedicatedRuntimeAuthGeneration(options = {}) {
|
|
|
192
186
|
return false;
|
|
193
187
|
const configDir = options.configDir ?? (0, auth_core_1.resolveConfigRoot)();
|
|
194
188
|
node_fs_1.default.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
195
|
-
const
|
|
189
|
+
const profile = (0, keychain_1.getActiveProfile)();
|
|
190
|
+
const markerName = profile === keychain_1.DEFAULT_PROFILE
|
|
191
|
+
? DEDICATED_AUTH_GENERATION_MARKER
|
|
192
|
+
: `${DEDICATED_AUTH_GENERATION_MARKER}.${profile}`;
|
|
193
|
+
const markerPath = node_path_1.default.join(configDir, markerName);
|
|
196
194
|
const release = await proper_lockfile_1.default.lock(configDir, {
|
|
197
195
|
lockfilePath: `${markerPath}.lock`,
|
|
198
196
|
realpath: false,
|
|
@@ -212,12 +210,12 @@ async function prepareDedicatedRuntimeAuthGeneration(options = {}) {
|
|
|
212
210
|
(options.clearStoredAuth ?? auth_storage_1.clearAllBackends)();
|
|
213
211
|
await (options.clearDeviceIdentity ??
|
|
214
212
|
(() => (0, device_identity_1.resetDeviceIdentity)({
|
|
215
|
-
profile
|
|
213
|
+
profile,
|
|
216
214
|
clearOnDisk: true,
|
|
217
215
|
})))();
|
|
218
216
|
await (options.clearCredentialEnvelope ??
|
|
219
217
|
(() => (0, cred_envelope_1.resetCredEnvelope)({
|
|
220
|
-
profile
|
|
218
|
+
profile,
|
|
221
219
|
clearOnDisk: true,
|
|
222
220
|
})))();
|
|
223
221
|
const temporary = `${markerPath}.tmp.${process.pid}.${Date.now()}`;
|
|
@@ -298,6 +296,7 @@ function ensureAuthCoreConfigured() {
|
|
|
298
296
|
let _prefetchPromise = null;
|
|
299
297
|
let _credEnvelopeInitPromise = null;
|
|
300
298
|
let _credEnvelopeInitStartedAt = 0;
|
|
299
|
+
let _prefetchProfile = null;
|
|
301
300
|
class CliCredentialInitializationError extends Error {
|
|
302
301
|
code = 'CREDENTIAL_INITIALIZATION_UNAVAILABLE';
|
|
303
302
|
exitCode = 75;
|
|
@@ -342,15 +341,21 @@ function encryptedCredentialInitError(timedOut, cause) {
|
|
|
342
341
|
'The credential remains protected; unlock or repair the OS credential store and retry.');
|
|
343
342
|
}
|
|
344
343
|
function ensureCliAuthPrefetchStarted() {
|
|
345
|
-
|
|
344
|
+
const profile = (0, keychain_1.getActiveProfile)();
|
|
345
|
+
if (_prefetchPromise) {
|
|
346
|
+
if (_prefetchProfile !== profile) {
|
|
347
|
+
throw new CliCredentialInitializationError(`Credential initialization already belongs to profile ${_prefetchProfile}; start a new skrr process for profile ${profile}.`);
|
|
348
|
+
}
|
|
346
349
|
return;
|
|
350
|
+
}
|
|
351
|
+
_prefetchProfile = profile;
|
|
347
352
|
_credEnvelopeInitStartedAt = Date.now();
|
|
348
|
-
const envelopeInit = (0, cred_envelope_1.initCredEnvelope)(
|
|
353
|
+
const envelopeInit = (0, cred_envelope_1.initCredEnvelope)(profile);
|
|
349
354
|
_credEnvelopeInitPromise = envelopeInit;
|
|
350
355
|
_prefetchPromise = (async () => {
|
|
351
356
|
try {
|
|
352
357
|
await envelopeInit;
|
|
353
|
-
await (0, device_identity_1.initDeviceIdentity)(
|
|
358
|
+
await (0, device_identity_1.initDeviceIdentity)(profile);
|
|
354
359
|
// The CLI deliberately does NOT enroll a device key with the server
|
|
355
360
|
// (OSK-12063). Enrollment pins the FIRST key presented on a machine's
|
|
356
361
|
// Daemon row, and every later refresh signed by a different key is
|
|
@@ -431,6 +436,7 @@ function __resetPrefetchCliAuthForTest() {
|
|
|
431
436
|
_prefetchPromise = null;
|
|
432
437
|
_credEnvelopeInitPromise = null;
|
|
433
438
|
_credEnvelopeInitStartedAt = 0;
|
|
439
|
+
_prefetchProfile = null;
|
|
434
440
|
}
|
|
435
441
|
// Eager-configure on first import. Cheap and avoids ordering pitfalls in
|
|
436
442
|
// any future command that calls auth-core helpers from outside a Command.
|
package/dist/lib/keychain.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Default profile when no profile flag or environment override is present. */
|
|
2
2
|
export declare const DEFAULT_PROFILE = "default";
|
|
3
3
|
export declare function setActiveProfile(profile: string): void;
|
|
4
4
|
/**
|
|
@@ -102,11 +102,13 @@ export declare function deleteRefreshToken(): void;
|
|
|
102
102
|
* Returns the services it actually removed something from, so the caller can
|
|
103
103
|
* say what happened instead of destroying credentials silently.
|
|
104
104
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
105
|
+
* Ordinary logout removes only retired CLI slots. Machine credentials and
|
|
106
|
+
* KEKs are shared with the daemon and are removed only by --all-devices.
|
|
107
|
+
* That broader mode also signs out an installed retired `oversky` daemon.
|
|
108
108
|
*/
|
|
109
|
-
export declare function deleteLegacyBrandEntries(
|
|
109
|
+
export declare function deleteLegacyBrandEntries(options?: {
|
|
110
|
+
includeMachineCredentials?: boolean;
|
|
111
|
+
}): string[];
|
|
110
112
|
/**
|
|
111
113
|
* Remove every CLI keychain slot for the active profile. Used by `skrr logout`.
|
|
112
114
|
* Safe to call on a clean system — missing items are not an error.
|
package/dist/lib/keychain.js
CHANGED
|
@@ -67,10 +67,8 @@ exports.resolveProfileFromArgv = resolveProfileFromArgv;
|
|
|
67
67
|
* Mirrors `daemon/src/keychain.ts` but simpler:
|
|
68
68
|
* - No prefetch: the CLI is a short-lived foreground process; parallelizing
|
|
69
69
|
* the keychain probe with other startup work isn't worth the code.
|
|
70
|
-
* - Namespaced `ai.skrr.cli.<profile>`
|
|
71
|
-
*
|
|
72
|
-
* the suffix in now makes the future migration a rename instead of a
|
|
73
|
-
* schema change.
|
|
70
|
+
* - Namespaced `ai.skrr.cli.<profile>` so each selected profile keeps
|
|
71
|
+
* its own CLI refresh family.
|
|
74
72
|
* - Distinct service from the daemon (`ai.skrr.cli.*` vs.
|
|
75
73
|
* `ai.skrr.daemon.*`). The CLI holds `scope: 'cli'` tokens; the
|
|
76
74
|
* daemon holds `scope: 'daemon'`. They must never read each other's
|
|
@@ -121,7 +119,7 @@ const cred_envelope_1 = require("./cred-envelope");
|
|
|
121
119
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
122
120
|
/** Service-name prefix. Always followed by `.<profile>`. */
|
|
123
121
|
const SERVICE_BASE = 'ai.skrr.cli';
|
|
124
|
-
/**
|
|
122
|
+
/** Default profile when no profile flag or environment override is present. */
|
|
125
123
|
exports.DEFAULT_PROFILE = 'default';
|
|
126
124
|
const ACCOUNT_TOKEN = 'cli-token';
|
|
127
125
|
const ACCOUNT_EXPIRES = 'cli-token-expires';
|
|
@@ -619,14 +617,16 @@ function deleteEntireService(service) {
|
|
|
619
617
|
* Returns the services it actually removed something from, so the caller can
|
|
620
618
|
* say what happened instead of destroying credentials silently.
|
|
621
619
|
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
620
|
+
* Ordinary logout removes only retired CLI slots. Machine credentials and
|
|
621
|
+
* KEKs are shared with the daemon and are removed only by --all-devices.
|
|
622
|
+
* That broader mode also signs out an installed retired `oversky` daemon.
|
|
625
623
|
*/
|
|
626
|
-
function deleteLegacyBrandEntries() {
|
|
624
|
+
function deleteLegacyBrandEntries(options = {}) {
|
|
627
625
|
if (!isAvailable())
|
|
628
626
|
return [];
|
|
629
|
-
return discoverLegacyBrandServices()
|
|
627
|
+
return discoverLegacyBrandServices()
|
|
628
|
+
.filter((service) => options.includeMachineCredentials === true || /^(?:ai|com)\.oversky\.cli\./.test(service))
|
|
629
|
+
.filter((service) => deleteEntireService(service));
|
|
630
630
|
}
|
|
631
631
|
/**
|
|
632
632
|
* Remove every CLI keychain slot for the active profile. Used by `skrr logout`.
|
package/dist/lib/refresh.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface EnsureFreshCliCredentialResult {
|
|
|
45
45
|
/** True when we adopted a fresher token another process already wrote. */
|
|
46
46
|
adoptedStoredToken?: boolean;
|
|
47
47
|
pair?: TokenPair;
|
|
48
|
+
/** Stable diagnostic code; never contains token or server response text. */
|
|
49
|
+
refreshFailureCode?: string;
|
|
48
50
|
skippedReason?: 'no_token' | 'ci_token' | 'external_source' | 'env_override' | 'bare_mode' | 'fresh' | 'no_refresh_token' | 'refresh_failed';
|
|
49
51
|
}
|
|
50
52
|
/**
|
package/dist/lib/refresh.js
CHANGED
|
@@ -58,6 +58,7 @@ class CliRefreshUnavailableError extends Error {
|
|
|
58
58
|
}
|
|
59
59
|
exports.CliRefreshUnavailableError = CliRefreshUnavailableError;
|
|
60
60
|
let ensureInFlight = null;
|
|
61
|
+
let reportedPermanentRefreshFailure = false;
|
|
61
62
|
function expiryFromToken(token) {
|
|
62
63
|
if (!token)
|
|
63
64
|
return undefined;
|
|
@@ -158,8 +159,25 @@ async function ensureFreshCliCredential(opts = {}) {
|
|
|
158
159
|
pair,
|
|
159
160
|
};
|
|
160
161
|
}
|
|
161
|
-
catch {
|
|
162
|
-
|
|
162
|
+
catch (err) {
|
|
163
|
+
const refreshFailureCode = err instanceof auth_core_1.PermanentAuthFailure
|
|
164
|
+
? err.code
|
|
165
|
+
: err instanceof auth_core_1.TransientAuthFailure
|
|
166
|
+
? 'TRANSIENT_REFRESH_FAILURE'
|
|
167
|
+
: err instanceof CliRefreshUnavailableError
|
|
168
|
+
? 'LOCAL_REFRESH_UNAVAILABLE'
|
|
169
|
+
: 'LOCAL_REFRESH_ERROR';
|
|
170
|
+
if (err instanceof auth_core_1.PermanentAuthFailure && !reportedPermanentRefreshFailure) {
|
|
171
|
+
reportedPermanentRefreshFailure = true;
|
|
172
|
+
console.error(`[skrr/auth] Stored CLI session refresh was rejected (${refreshFailureCode}). ` +
|
|
173
|
+
'The background service may still provide access; run `skrr login` if it cannot.');
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
credential,
|
|
177
|
+
refreshed: false,
|
|
178
|
+
skippedReason: 'refresh_failed',
|
|
179
|
+
refreshFailureCode,
|
|
180
|
+
};
|
|
163
181
|
}
|
|
164
182
|
finally {
|
|
165
183
|
ensureInFlight = null;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { MemoryMount as SharedMemoryMount, MemoryStore as SharedMemoryStore } from '@skrr-ai/data-schemas';
|
|
2
2
|
import { BaseCommand } from '../base-command';
|
|
3
3
|
import { ApiFetchError, type ApiFetchOptions } from './api-fetch';
|
|
4
4
|
export type MemoryMount = SharedMemoryMount;
|
|
5
5
|
export type MemoryStore = SharedMemoryStore & {
|
|
6
|
-
mount
|
|
6
|
+
mount: SharedMemoryMount;
|
|
7
7
|
};
|
|
8
|
+
export declare function parseMemoryMount(value: unknown): MemoryMount;
|
|
9
|
+
export declare function parseMountedMemoryStore(value: unknown): MemoryStore;
|
|
8
10
|
export declare const scopedMemoryFlags: {
|
|
9
11
|
agent: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
12
|
space: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -1,12 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ScopedMemoryCommand = exports.versionedMutationFlags = exports.mutationFlags = exports.scopedMemoryFlags = void 0;
|
|
4
|
+
exports.parseMemoryMount = parseMemoryMount;
|
|
5
|
+
exports.parseMountedMemoryStore = parseMountedMemoryStore;
|
|
4
6
|
exports.parseMemoryApiError = parseMemoryApiError;
|
|
5
7
|
const core_1 = require("@oclif/core");
|
|
6
|
-
const data_schemas_1 = require("@skrr-ai/data-schemas");
|
|
7
8
|
const node_crypto_1 = require("node:crypto");
|
|
8
9
|
const base_command_1 = require("../base-command");
|
|
9
10
|
const api_fetch_1 = require("./api-fetch");
|
|
11
|
+
// The public CLI must not load the private data-schemas package at runtime.
|
|
12
|
+
// The API validates the full wire contract; check the fields used by CLI commands
|
|
13
|
+
// before treating a response as a mounted Store or mount.
|
|
14
|
+
function parseMemoryMount(value) {
|
|
15
|
+
const mount = value;
|
|
16
|
+
if (!mount || typeof mount !== 'object' || typeof mount.storeId !== 'string' ||
|
|
17
|
+
typeof mount.uri !== 'string' || typeof mount.authorizationGeneration !== 'number' ||
|
|
18
|
+
!['read_only', 'read_write'].includes(String(mount.access)) ||
|
|
19
|
+
!['agent', 'current_space', 'ancestor_space'].includes(String(mount.source))) {
|
|
20
|
+
throw new Error('Invalid Memory mount response from server');
|
|
21
|
+
}
|
|
22
|
+
return mount;
|
|
23
|
+
}
|
|
24
|
+
function parseMountedMemoryStore(value) {
|
|
25
|
+
const store = value;
|
|
26
|
+
if (!store || typeof store !== 'object' || typeof store.id !== 'string' ||
|
|
27
|
+
typeof store.revision !== 'number' || typeof store.authorizationGeneration !== 'number' ||
|
|
28
|
+
!store.owner || !['agent', 'space'].includes(String(store.owner.kind)) ||
|
|
29
|
+
typeof store.owner.id !== 'string') {
|
|
30
|
+
throw new Error('Invalid Memory Store response from server');
|
|
31
|
+
}
|
|
32
|
+
return { ...store, mount: parseMemoryMount(store.mount) };
|
|
33
|
+
}
|
|
10
34
|
exports.scopedMemoryFlags = {
|
|
11
35
|
agent: core_1.Flags.string({ description: 'Act through this Agent', required: true }),
|
|
12
36
|
space: core_1.Flags.string({ description: 'Current Space context (required for Space scopes)' }),
|
|
@@ -126,7 +150,7 @@ class ScopedMemoryCommand extends base_command_1.BaseCommand {
|
|
|
126
150
|
async resolveStore(flags, { write = false } = {}) {
|
|
127
151
|
if (flags.store) {
|
|
128
152
|
const rawStore = await this.memoryApi(flags.agent, `/stores/${encodeURIComponent(flags.store)}${this.query(flags)}`);
|
|
129
|
-
const store =
|
|
153
|
+
const store = parseMountedMemoryStore(rawStore);
|
|
130
154
|
if (write && store.mount?.access !== 'read_write') {
|
|
131
155
|
this.error('MEMORY_STORE_READ_ONLY: selected Store is read-only', { exit: 5 });
|
|
132
156
|
}
|
|
@@ -143,7 +167,7 @@ class ScopedMemoryCommand extends base_command_1.BaseCommand {
|
|
|
143
167
|
this.error(`MEMORY_SPACE_CONTEXT_REQUIRED: --space ${id} is required when selecting ${flags.scope}.`, { exit: 2, code: 'MEMORY_SPACE_CONTEXT_REQUIRED' });
|
|
144
168
|
}
|
|
145
169
|
const response = await this.memoryApi(flags.agent, `/stores${this.query(flags)}`);
|
|
146
|
-
const stores = response.stores.map(
|
|
170
|
+
const stores = response.stores.map(parseMountedMemoryStore);
|
|
147
171
|
const store = stores.find((candidate) => candidate.owner.kind === kind && candidate.owner.id === id);
|
|
148
172
|
if (!store)
|
|
149
173
|
this.error(`MEMORY_NOT_MOUNTED: ${flags.scope}`, { exit: 5 });
|
|
@@ -10,7 +10,7 @@ type DeviceIdentityState = {
|
|
|
10
10
|
profile: string;
|
|
11
11
|
envelopeProtected: boolean;
|
|
12
12
|
};
|
|
13
|
-
type DisabledReason = 'init_failed' | 'load_failed' | 'persist_failed';
|
|
13
|
+
type DisabledReason = 'init_failed' | 'load_failed' | 'persist_failed' | 'unreadable_key';
|
|
14
14
|
/** @internal Test seam — force-set state. */
|
|
15
15
|
export declare function __setStateForTest(next: DeviceIdentityState): DeviceIdentityState;
|
|
16
16
|
/** @internal Test seam — read state. */
|
|
@@ -42,11 +42,13 @@ export declare function describeDeviceKeySplit(profile: string): {
|
|
|
42
42
|
* 2. Generate fresh keypair and persist. Failure to persist is a hard
|
|
43
43
|
* error landing in disabled+persist_failed.
|
|
44
44
|
*/
|
|
45
|
-
export declare function initDeviceIdentity(profile: string
|
|
45
|
+
export declare function initDeviceIdentity(profile: string, options?: {
|
|
46
|
+
recoverUnreadable?: boolean;
|
|
47
|
+
}): Promise<void>;
|
|
46
48
|
/**
|
|
47
|
-
* Reset state. With `clearOnDisk=true` deletes both keypair files
|
|
48
|
-
*
|
|
49
|
-
*
|
|
49
|
+
* Reset in-memory state. With `clearOnDisk=true` deletes both keypair files.
|
|
50
|
+
* Machine-wide sign-out may use that option; ordinary CLI sign-out preserves
|
|
51
|
+
* the daemon's pinned identity.
|
|
50
52
|
*/
|
|
51
53
|
export declare function resetDeviceIdentity(opts?: {
|
|
52
54
|
profile?: string;
|
|
@@ -100,6 +100,26 @@ function publicKeyPath(profile) {
|
|
|
100
100
|
function privateKeyPath(profile) {
|
|
101
101
|
return node_path_1.default.join(profileConfigDir(profile), PRIVATE_KEY_FILENAME);
|
|
102
102
|
}
|
|
103
|
+
function storedKeyMaterialExists(profile) {
|
|
104
|
+
let files;
|
|
105
|
+
try {
|
|
106
|
+
files = [publicKeyPath(profile), privateKeyPath(profile), enrollmentMarkerPath(profile)];
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
try {
|
|
113
|
+
node_fs_1.default.lstatSync(file);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err.code !== 'ENOENT')
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
103
123
|
function enrollmentMarkerPath(profile) {
|
|
104
124
|
return node_path_1.default.join(profileConfigDir(profile), ENROLLMENT_MARKER_FILENAME);
|
|
105
125
|
}
|
|
@@ -339,7 +359,7 @@ async function consolidateDeviceIdentity(profile) {
|
|
|
339
359
|
* 2. Generate fresh keypair and persist. Failure to persist is a hard
|
|
340
360
|
* error landing in disabled+persist_failed.
|
|
341
361
|
*/
|
|
342
|
-
async function initDeviceIdentity(profile) {
|
|
362
|
+
async function initDeviceIdentity(profile, options = {}) {
|
|
343
363
|
if (_state.kind !== 'uninit')
|
|
344
364
|
return;
|
|
345
365
|
if (typeof profile !== 'string' || profile.length === 0) {
|
|
@@ -374,6 +394,18 @@ async function initDeviceIdentity(profile) {
|
|
|
374
394
|
});
|
|
375
395
|
return;
|
|
376
396
|
}
|
|
397
|
+
// A first install has neither file. If either file already exists, a null
|
|
398
|
+
// load is a lost, corrupt or temporarily unreadable identity. Replacing it
|
|
399
|
+
// during ordinary boot would make the next refresh sign with a key the
|
|
400
|
+
// server has never pinned, turning a local read failure into an unexpected
|
|
401
|
+
// DEVICE_KEY_MISMATCH. Only an explicit interactive login may replace it;
|
|
402
|
+
// that login proves the new key and re-pins it before issuing credentials.
|
|
403
|
+
const hasStoredKey = storedKeyMaterialExists(profile);
|
|
404
|
+
if (hasStoredKey && !options.recoverUnreadable) {
|
|
405
|
+
_state = { kind: 'disabled', reason: 'unreadable_key' };
|
|
406
|
+
emit('recovery_required', false, { profile, reason: 'stored_key_unreadable' });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
377
409
|
let fresh;
|
|
378
410
|
try {
|
|
379
411
|
fresh = (0, deviceKey_js_1.generateDeviceKeyPair)();
|
|
@@ -413,9 +445,9 @@ async function initDeviceIdentity(profile) {
|
|
|
413
445
|
});
|
|
414
446
|
}
|
|
415
447
|
/**
|
|
416
|
-
* Reset state. With `clearOnDisk=true` deletes both keypair files
|
|
417
|
-
*
|
|
418
|
-
*
|
|
448
|
+
* Reset in-memory state. With `clearOnDisk=true` deletes both keypair files.
|
|
449
|
+
* Machine-wide sign-out may use that option; ordinary CLI sign-out preserves
|
|
450
|
+
* the daemon's pinned identity.
|
|
419
451
|
*/
|
|
420
452
|
async function resetDeviceIdentity(opts) {
|
|
421
453
|
if (opts?.clearOnDisk && opts.profile) {
|
|
@@ -10,7 +10,7 @@ type DeviceIdentityState = {
|
|
|
10
10
|
profile: string;
|
|
11
11
|
envelopeProtected: boolean;
|
|
12
12
|
};
|
|
13
|
-
type DisabledReason = 'init_failed' | 'load_failed' | 'persist_failed';
|
|
13
|
+
type DisabledReason = 'init_failed' | 'load_failed' | 'persist_failed' | 'unreadable_key';
|
|
14
14
|
/** @internal Test seam — force-set state. */
|
|
15
15
|
export declare function __setStateForTest(next: DeviceIdentityState): DeviceIdentityState;
|
|
16
16
|
/** @internal Test seam — read state. */
|
|
@@ -42,11 +42,13 @@ export declare function describeDeviceKeySplit(profile: string): {
|
|
|
42
42
|
* 2. Generate fresh keypair and persist. Failure to persist is a hard
|
|
43
43
|
* error landing in disabled+persist_failed.
|
|
44
44
|
*/
|
|
45
|
-
export declare function initDeviceIdentity(profile: string
|
|
45
|
+
export declare function initDeviceIdentity(profile: string, options?: {
|
|
46
|
+
recoverUnreadable?: boolean;
|
|
47
|
+
}): Promise<void>;
|
|
46
48
|
/**
|
|
47
|
-
* Reset state. With `clearOnDisk=true` deletes both keypair files
|
|
48
|
-
*
|
|
49
|
-
*
|
|
49
|
+
* Reset in-memory state. With `clearOnDisk=true` deletes both keypair files.
|
|
50
|
+
* Machine-wide sign-out may use that option; ordinary CLI sign-out preserves
|
|
51
|
+
* the daemon's pinned identity.
|
|
50
52
|
*/
|
|
51
53
|
export declare function resetDeviceIdentity(opts?: {
|
|
52
54
|
profile?: string;
|
|
@@ -80,6 +80,26 @@ function publicKeyPath(profile) {
|
|
|
80
80
|
function privateKeyPath(profile) {
|
|
81
81
|
return path.join(profileConfigDir(profile), PRIVATE_KEY_FILENAME);
|
|
82
82
|
}
|
|
83
|
+
function storedKeyMaterialExists(profile) {
|
|
84
|
+
let files;
|
|
85
|
+
try {
|
|
86
|
+
files = [publicKeyPath(profile), privateKeyPath(profile), enrollmentMarkerPath(profile)];
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
for (const file of files) {
|
|
92
|
+
try {
|
|
93
|
+
fs.lstatSync(file);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
if (err.code !== 'ENOENT')
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
83
103
|
function enrollmentMarkerPath(profile) {
|
|
84
104
|
return path.join(profileConfigDir(profile), ENROLLMENT_MARKER_FILENAME);
|
|
85
105
|
}
|
|
@@ -319,7 +339,7 @@ async function consolidateDeviceIdentity(profile) {
|
|
|
319
339
|
* 2. Generate fresh keypair and persist. Failure to persist is a hard
|
|
320
340
|
* error landing in disabled+persist_failed.
|
|
321
341
|
*/
|
|
322
|
-
export async function initDeviceIdentity(profile) {
|
|
342
|
+
export async function initDeviceIdentity(profile, options = {}) {
|
|
323
343
|
if (_state.kind !== 'uninit')
|
|
324
344
|
return;
|
|
325
345
|
if (typeof profile !== 'string' || profile.length === 0) {
|
|
@@ -354,6 +374,18 @@ export async function initDeviceIdentity(profile) {
|
|
|
354
374
|
});
|
|
355
375
|
return;
|
|
356
376
|
}
|
|
377
|
+
// A first install has neither file. If either file already exists, a null
|
|
378
|
+
// load is a lost, corrupt or temporarily unreadable identity. Replacing it
|
|
379
|
+
// during ordinary boot would make the next refresh sign with a key the
|
|
380
|
+
// server has never pinned, turning a local read failure into an unexpected
|
|
381
|
+
// DEVICE_KEY_MISMATCH. Only an explicit interactive login may replace it;
|
|
382
|
+
// that login proves the new key and re-pins it before issuing credentials.
|
|
383
|
+
const hasStoredKey = storedKeyMaterialExists(profile);
|
|
384
|
+
if (hasStoredKey && !options.recoverUnreadable) {
|
|
385
|
+
_state = { kind: 'disabled', reason: 'unreadable_key' };
|
|
386
|
+
emit('recovery_required', false, { profile, reason: 'stored_key_unreadable' });
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
357
389
|
let fresh;
|
|
358
390
|
try {
|
|
359
391
|
fresh = generateDeviceKeyPair();
|
|
@@ -393,9 +425,9 @@ export async function initDeviceIdentity(profile) {
|
|
|
393
425
|
});
|
|
394
426
|
}
|
|
395
427
|
/**
|
|
396
|
-
* Reset state. With `clearOnDisk=true` deletes both keypair files
|
|
397
|
-
*
|
|
398
|
-
*
|
|
428
|
+
* Reset in-memory state. With `clearOnDisk=true` deletes both keypair files.
|
|
429
|
+
* Machine-wide sign-out may use that option; ordinary CLI sign-out preserves
|
|
430
|
+
* the daemon's pinned identity.
|
|
399
431
|
*/
|
|
400
432
|
export async function resetDeviceIdentity(opts) {
|
|
401
433
|
if (opts?.clearOnDisk && opts.profile) {
|
|
@@ -44752,6 +44752,10 @@ var gR = Object.freeze({ __proto__: null, acceptAgentIntuition: function(e2, t2,
|
|
|
44752
44752
|
return oR.get(e2 ? "".concat(vr(), "?workspaceId=").concat(encodeURIComponent(e2)) : vr());
|
|
44753
44753
|
}, getStorePublisherFollowOverlay: function(e2) {
|
|
44754
44754
|
return oR.get(xr(e2));
|
|
44755
|
+
}, getStorePublisherFollows: function(e2) {
|
|
44756
|
+
return oR.get((function(e3) {
|
|
44757
|
+
return "".concat(er(), "/publisher-follows").concat(e3 ? "?cursor=".concat(encodeURIComponent(e3)) : "");
|
|
44758
|
+
})(e2));
|
|
44755
44759
|
}, getStorePublisherNamespaces: function(e2) {
|
|
44756
44760
|
return oR.get(hr(e2));
|
|
44757
44761
|
}, getStorePublisherProfileEditor: function(e2) {
|
|
@@ -44767,7 +44771,7 @@ var gR = Object.freeze({ __proto__: null, acceptAgentIntuition: function(e2, t2,
|
|
|
44767
44771
|
}, getStoreReleaseAdmissions: function(e2) {
|
|
44768
44772
|
return oR.get((function(e3) {
|
|
44769
44773
|
var t2 = new URLSearchParams();
|
|
44770
|
-
(null == e3 ? void 0 : e3.state) && t2.set("state", e3.state), (null == e3 ? void 0 : e3.limit) && t2.set("limit", String(e3.limit));
|
|
44774
|
+
(null == e3 ? void 0 : e3.state) && t2.set("state", e3.state), (null == e3 ? void 0 : e3.limit) && t2.set("limit", String(e3.limit)), (null == e3 ? void 0 : e3.artifactAfter) && t2.set("artifactAfter", e3.artifactAfter);
|
|
44771
44775
|
var n2 = t2.toString();
|
|
44772
44776
|
return "".concat(er(), "/admissions").concat(n2 ? "?".concat(n2) : "");
|
|
44773
44777
|
})(e2));
|
|
@@ -46542,10 +46546,10 @@ var gR = Object.freeze({ __proto__: null, acceptAgentIntuition: function(e2, t2,
|
|
|
46542
46546
|
}, setAgentUsageCap: function(e2, t2) {
|
|
46543
46547
|
return oR.put(Nn(e2), t2);
|
|
46544
46548
|
}, setAgentVisibility: function(e2) {
|
|
46545
|
-
var t2 = e2.agentId, n2 = e2.isPublic, o2 = e2.price, r2 = e2.shareMode, a2 = e2.changelog, i2 = e2.versionMajor, s2 = e2.versionMinor, c2 = e2.capabilityRationales, l2 = e2.supportedLanguages, u2 = e2.
|
|
46546
|
-
return void 0 !== o2 && (
|
|
46549
|
+
var t2 = e2.agentId, n2 = e2.isPublic, o2 = e2.price, r2 = e2.shareMode, a2 = e2.changelog, i2 = e2.versionMajor, s2 = e2.versionMinor, c2 = e2.capabilityRationales, l2 = e2.supportedLanguages, u2 = e2.dependencies, d2 = e2.license, p2 = e2.sourceVisibility, m2 = { isPublic: n2 };
|
|
46550
|
+
return void 0 !== o2 && (m2.price = o2), void 0 !== r2 && (m2.shareMode = r2), void 0 !== a2 && (m2.changelog = a2), void 0 !== i2 && (m2.versionMajor = i2), void 0 !== s2 && (m2.versionMinor = s2), void 0 !== c2 && (m2.capabilityRationales = c2), void 0 !== l2 && (m2.supportedLanguages = l2), void 0 !== u2 && (m2.dependencies = u2), void 0 !== d2 && (m2.license = d2), void 0 !== p2 && (m2.sourceVisibility = p2), oR.post((function(e3) {
|
|
46547
46551
|
return "".concat(en, "/api/agents/").concat(e3, "/visibility");
|
|
46548
|
-
})(t2),
|
|
46552
|
+
})(t2), m2);
|
|
46549
46553
|
}, setAlwaysOnDesired: function(e2, t2) {
|
|
46550
46554
|
return oR.put((n2 = e2, "".concat(en, "/api/agents/").concat(encodeURIComponent(n2), "/always-on")), t2);
|
|
46551
46555
|
var n2;
|
|
@@ -46890,6 +46894,10 @@ var gR = Object.freeze({ __proto__: null, acceptAgentIntuition: function(e2, t2,
|
|
|
46890
46894
|
return oR.patch(Aa(e2), { unarchive: true });
|
|
46891
46895
|
}, unfollowStorePublisherByHandle: function(e2) {
|
|
46892
46896
|
return oR.delete(xr(e2));
|
|
46897
|
+
}, unfollowStorePublisherById: function(e2) {
|
|
46898
|
+
return oR.delete((function(e3) {
|
|
46899
|
+
return "".concat(er(), "/publisher-follows/").concat(encodeURIComponent(e3));
|
|
46900
|
+
})(e2));
|
|
46893
46901
|
}, uninstallStoreAgent: function(e2) {
|
|
46894
46902
|
var t2 = e2.agentId, n2 = e2.erase;
|
|
46895
46903
|
return oR.post((function(e3) {
|