@skrr-ai/cli 0.1.120 → 0.1.122

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.
Files changed (80) hide show
  1. package/dist/base-command.js +11 -2
  2. package/dist/commands/actions/publish.js +4 -0
  3. package/dist/commands/agent/message/context.d.ts +1 -0
  4. package/dist/commands/agent/message/context.js +3 -2
  5. package/dist/commands/agent/message/send.js +6 -1
  6. package/dist/commands/search/agents.js +5 -1
  7. package/dist/commands/search/all.js +74 -22
  8. package/dist/commands/search/skills.d.ts +1 -0
  9. package/dist/commands/search/skills.js +12 -1
  10. package/dist/commands/search/spaces.d.ts +8 -1
  11. package/dist/commands/search/spaces.js +50 -12
  12. package/dist/commands/search/tasks.d.ts +13 -2
  13. package/dist/commands/search/tasks.js +78 -14
  14. package/dist/commands/search/teams.js +5 -1
  15. package/dist/commands/search/workspace.d.ts +19 -0
  16. package/dist/commands/search/workspace.js +126 -0
  17. package/dist/commands/skills/browse.d.ts +1 -0
  18. package/dist/commands/skills/browse.js +8 -1
  19. package/dist/commands/skills/search.d.ts +1 -0
  20. package/dist/commands/skills/search.js +12 -1
  21. package/dist/commands/spaces/list.d.ts +1 -0
  22. package/dist/commands/spaces/list.js +12 -3
  23. package/dist/commands/spaces/search.d.ts +1 -0
  24. package/dist/commands/spaces/search.js +9 -0
  25. package/dist/commands/store/gallery-admissions/approve.d.ts +16 -0
  26. package/dist/commands/store/gallery-admissions/approve.js +36 -0
  27. package/dist/commands/store/gallery-admissions/list.d.ts +9 -0
  28. package/dist/commands/store/gallery-admissions/list.js +49 -0
  29. package/dist/commands/store/gallery-admissions/preview.d.ts +15 -0
  30. package/dist/commands/store/gallery-admissions/preview.js +48 -0
  31. package/dist/commands/store/gallery-admissions/refuse.d.ts +15 -0
  32. package/dist/commands/store/gallery-admissions/refuse.js +35 -0
  33. package/dist/commands/store/gallery-admissions/show.d.ts +12 -0
  34. package/dist/commands/store/gallery-admissions/show.js +55 -0
  35. package/dist/commands/tasks/list.js +8 -0
  36. package/dist/commands/tasks/search.d.ts +1 -0
  37. package/dist/commands/tasks/search.js +9 -0
  38. package/dist/generated/agent-guides.json +11 -11
  39. package/dist/lib/api-error-reason.d.ts +2 -0
  40. package/dist/lib/api-error-reason.js +14 -0
  41. package/dist/lib/api-fetch.js +3 -0
  42. package/dist/lib/command-miss.js +1 -0
  43. package/dist/lib/search-display.d.ts +7 -0
  44. package/dist/lib/search-display.js +31 -0
  45. package/dist/lib/skill-store-pagination.d.ts +8 -0
  46. package/dist/lib/skill-store-pagination.js +35 -0
  47. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +10 -0
  48. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +147 -0
  49. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.d.ts +20 -0
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.js +72 -0
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.d.ts +21 -0
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.js +96 -0
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +2 -3
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +102 -20
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.d.ts +27 -0
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +161 -0
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.d.ts +26 -0
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.js +41 -0
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +15 -0
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +20 -4
  62. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +10 -0
  63. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +172 -1
  64. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.d.ts +20 -0
  65. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.js +43 -0
  66. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.d.ts +21 -0
  67. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.js +62 -0
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +2 -3
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +133 -25
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.d.ts +27 -0
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +132 -0
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.d.ts +26 -0
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.js +15 -0
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -1
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +28 -15
  77. package/dist/node_modules/@skrr-ai/auth-core/package.json +31 -1
  78. package/dist/node_modules/@skrr-ai/data-provider/index.js +7728 -7666
  79. package/oclif.manifest.json +1180 -101
  80. package/package.json +1 -1
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var independentMachineReservation_exports = {};
30
+ __export(independentMachineReservation_exports, {
31
+ INDEPENDENT_MACHINE_PROFILE_RE: () => INDEPENDENT_MACHINE_PROFILE_RE,
32
+ INDEPENDENT_MACHINE_RESERVATION_FILE: () => INDEPENDENT_MACHINE_RESERVATION_FILE,
33
+ readIndependentMachineReservation: () => readIndependentMachineReservation,
34
+ reserveIndependentMachineProfile: () => reserveIndependentMachineProfile,
35
+ verifyIndependentMachineReservation: () => verifyIndependentMachineReservation
36
+ });
37
+ module.exports = __toCommonJS(independentMachineReservation_exports);
38
+ var import_node_crypto = __toESM(require("node:crypto"), 1);
39
+ var import_node_fs = __toESM(require("node:fs"), 1);
40
+ var import_node_path = __toESM(require("node:path"), 1);
41
+ const INDEPENDENT_MACHINE_PROFILE_RE = /^desktop-bg-[a-f0-9]{32}$/;
42
+ const INDEPENDENT_MACHINE_RESERVATION_FILE = "independent-machine-attempt.json";
43
+ function canonicalOrigin(input) {
44
+ try {
45
+ const url = new URL(input);
46
+ const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
47
+ return !url.username && !url.password && url.origin === input && (url.protocol === "https:" || url.protocol === "http:" && local);
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+ function validSecret(secret) {
53
+ return typeof secret === "string" && /^[A-Za-z0-9_-]{43}$/.test(secret) && Buffer.from(secret, "base64url").length === 32 && Buffer.from(secret, "base64url").toString("base64url") === secret;
54
+ }
55
+ function hashSecret(profile, ownerId, origin, secret) {
56
+ return import_node_crypto.default.createHash("sha256").update("independent-machine-reservation:v2\0").update(profile).update("\0").update(ownerId).update("\0").update(origin).update("\0").update(secret).digest();
57
+ }
58
+ function reservationPath(root, profile) {
59
+ if (!import_node_path.default.isAbsolute(root) || !INDEPENDENT_MACHINE_PROFILE_RE.test(profile)) {
60
+ throw new Error("Invalid independent machine profile");
61
+ }
62
+ return import_node_path.default.join(root, "profiles", profile, INDEPENDENT_MACHINE_RESERVATION_FILE);
63
+ }
64
+ function reserveIndependentMachineProfile(options) {
65
+ if (!import_node_path.default.isAbsolute(options.root) || !/^[a-fA-F0-9]{24}$/.test(options.ownerId) || !canonicalOrigin(options.origin))
66
+ throw new Error("Invalid background authorization context");
67
+ const secret = import_node_crypto.default.randomBytes(32).toString("base64url");
68
+ const encrypted = options.encrypt(secret);
69
+ if (!Buffer.isBuffer(encrypted) || encrypted.length === 0 || encrypted.length > 4096) {
70
+ throw new Error("Background authorization secure storage is unavailable");
71
+ }
72
+ const profiles = import_node_path.default.join(options.root, "profiles");
73
+ const rootExisted = import_node_fs.default.existsSync(options.root);
74
+ const profilesExisted = import_node_fs.default.existsSync(profiles);
75
+ import_node_fs.default.mkdirSync(profiles, { recursive: true, mode: 448 });
76
+ for (let i = 0; i < 5; i += 1) {
77
+ const profile = `desktop-bg-${import_node_crypto.default.randomBytes(16).toString("hex")}`;
78
+ const dir = import_node_path.default.join(profiles, profile);
79
+ try {
80
+ import_node_fs.default.mkdirSync(dir, { mode: 448 });
81
+ } catch (error) {
82
+ if (error.code === "EEXIST")
83
+ continue;
84
+ throw error;
85
+ }
86
+ const reservation = {
87
+ version: 2,
88
+ profile,
89
+ ownerId: options.ownerId.toLowerCase(),
90
+ origin: options.origin,
91
+ secretHash: hashSecret(profile, options.ownerId.toLowerCase(), options.origin, secret).toString("hex"),
92
+ encryptedSecret: encrypted.toString("base64"),
93
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
94
+ };
95
+ const file = reservationPath(options.root, profile);
96
+ const fd = import_node_fs.default.openSync(file, import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL | (import_node_fs.default.constants.O_NOFOLLOW ?? 0), 384);
97
+ try {
98
+ import_node_fs.default.writeFileSync(fd, `${JSON.stringify(reservation)}
99
+ `, "utf8");
100
+ import_node_fs.default.fsyncSync(fd);
101
+ } finally {
102
+ import_node_fs.default.closeSync(fd);
103
+ }
104
+ const directories = [
105
+ dir,
106
+ profiles,
107
+ ...!profilesExisted ? [options.root] : [],
108
+ ...!rootExisted ? [import_node_path.default.dirname(options.root)] : []
109
+ ];
110
+ for (const directory of directories) {
111
+ const directoryFd = import_node_fs.default.openSync(directory, "r");
112
+ try {
113
+ import_node_fs.default.fsyncSync(directoryFd);
114
+ } finally {
115
+ import_node_fs.default.closeSync(directoryFd);
116
+ }
117
+ }
118
+ return { reservation, secret };
119
+ }
120
+ throw new Error("Could not reserve a new background machine profile");
121
+ }
122
+ function readIndependentMachineReservation(root, profile) {
123
+ try {
124
+ const file = reservationPath(root, profile);
125
+ const directory = import_node_fs.default.lstatSync(import_node_path.default.dirname(file));
126
+ if (!directory.isDirectory() || directory.isSymbolicLink())
127
+ return null;
128
+ const fd = import_node_fs.default.openSync(file, import_node_fs.default.constants.O_RDONLY | (import_node_fs.default.constants.O_NOFOLLOW ?? 0));
129
+ let raw;
130
+ try {
131
+ const stat = import_node_fs.default.fstatSync(fd);
132
+ if (!stat.isFile() || stat.nlink !== 1 || stat.size > 8192 || (stat.mode & 63) !== 0)
133
+ return null;
134
+ raw = import_node_fs.default.readFileSync(fd, "utf8");
135
+ } finally {
136
+ import_node_fs.default.closeSync(fd);
137
+ }
138
+ const value = JSON.parse(raw);
139
+ if (value.version !== 2 || value.profile !== profile || typeof value.ownerId !== "string" || !/^[a-f0-9]{24}$/.test(value.ownerId) || typeof value.origin !== "string" || !canonicalOrigin(value.origin) || typeof value.secretHash !== "string" || !/^[a-f0-9]{64}$/.test(value.secretHash) || typeof value.encryptedSecret !== "string" || !/^[A-Za-z0-9+/]+={0,2}$/.test(value.encryptedSecret) || typeof value.createdAt !== "string" || !Number.isFinite(Date.parse(value.createdAt)))
140
+ return null;
141
+ return value;
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+ function verifyIndependentMachineReservation(root, profile, secret) {
147
+ if (!validSecret(secret))
148
+ return false;
149
+ const saved = readIndependentMachineReservation(root, profile);
150
+ if (!saved)
151
+ return false;
152
+ return import_node_crypto.default.timingSafeEqual(Buffer.from(saved.secretHash, "hex"), hashSecret(profile, saved.ownerId, saved.origin, secret));
153
+ }
154
+ // Annotate the CommonJS export names for ESM import in node:
155
+ 0 && (module.exports = {
156
+ INDEPENDENT_MACHINE_PROFILE_RE,
157
+ INDEPENDENT_MACHINE_RESERVATION_FILE,
158
+ readIndependentMachineReservation,
159
+ reserveIndependentMachineProfile,
160
+ verifyIndependentMachineReservation
161
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The capabilities explained to a person before a separate background
3
+ * machine is authorized. This version is an immutable wire contract: adding
4
+ * access needs a new contract and a new authorization, not an edit to v1.
5
+ */
6
+ export declare const INDEPENDENT_MACHINE_SCOPE_CONTRACT_V1: "desktop-background-v1";
7
+ export declare const INDEPENDENT_MACHINE_PERMISSIONS_V1: readonly [Readonly<{
8
+ scope: "daemon:tools:bash";
9
+ description: "Run terminal commands and local agents";
10
+ }>, Readonly<{
11
+ scope: "daemon:tools:fs:read";
12
+ description: "Read files in allowed folders";
13
+ }>, Readonly<{
14
+ scope: "daemon:tools:fs:write";
15
+ description: "Create and edit files in allowed folders";
16
+ }>, Readonly<{
17
+ scope: "daemon:browser:cdp";
18
+ description: "Control connected browser tabs";
19
+ }>, Readonly<{
20
+ scope: "daemon:session:replay";
21
+ description: "Replay results recorded while offline";
22
+ }>, Readonly<{
23
+ scope: "daemon:offline-queue";
24
+ description: "Send queued results after reconnecting";
25
+ }>];
26
+ export declare const INDEPENDENT_MACHINE_SCOPES_V1: readonly string[];
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var independentMachineScopes_exports = {};
20
+ __export(independentMachineScopes_exports, {
21
+ INDEPENDENT_MACHINE_PERMISSIONS_V1: () => INDEPENDENT_MACHINE_PERMISSIONS_V1,
22
+ INDEPENDENT_MACHINE_SCOPES_V1: () => INDEPENDENT_MACHINE_SCOPES_V1,
23
+ INDEPENDENT_MACHINE_SCOPE_CONTRACT_V1: () => INDEPENDENT_MACHINE_SCOPE_CONTRACT_V1
24
+ });
25
+ module.exports = __toCommonJS(independentMachineScopes_exports);
26
+ const INDEPENDENT_MACHINE_SCOPE_CONTRACT_V1 = "desktop-background-v1";
27
+ const INDEPENDENT_MACHINE_PERMISSIONS_V1 = Object.freeze([
28
+ Object.freeze({ scope: "daemon:tools:bash", description: "Run terminal commands and local agents" }),
29
+ Object.freeze({ scope: "daemon:tools:fs:read", description: "Read files in allowed folders" }),
30
+ Object.freeze({ scope: "daemon:tools:fs:write", description: "Create and edit files in allowed folders" }),
31
+ Object.freeze({ scope: "daemon:browser:cdp", description: "Control connected browser tabs" }),
32
+ Object.freeze({ scope: "daemon:session:replay", description: "Replay results recorded while offline" }),
33
+ Object.freeze({ scope: "daemon:offline-queue", description: "Send queued results after reconnecting" })
34
+ ]);
35
+ const INDEPENDENT_MACHINE_SCOPES_V1 = Object.freeze(INDEPENDENT_MACHINE_PERMISSIONS_V1.map((permission) => permission.scope));
36
+ // Annotate the CommonJS export names for ESM import in node:
37
+ 0 && (module.exports = {
38
+ INDEPENDENT_MACHINE_PERMISSIONS_V1,
39
+ INDEPENDENT_MACHINE_SCOPES_V1,
40
+ INDEPENDENT_MACHINE_SCOPE_CONTRACT_V1
41
+ });
@@ -28,7 +28,7 @@ export { buildCredentialAad, decrypt as credEnvelopeDecrypt, deserialize as cred
28
28
  export { __setKekStrategyForTest, getKekStrategy, InMemoryKek, UnavailableKek, KekUnavailableError, LinuxKek, MacosKek, registerZeroizeHook, WindowsKek, zeroizeKekCaches, type KekStrategy, } from './kek/index.js';
29
29
  export { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, normalizeHtu, publicJwkFromX, tokenHash, verifyProof as verifyDeviceProof, type DeviceKeyPair, type DeviceProofClaims, type DeviceProofHeader, type Ed25519PrivateJwk, type Ed25519PublicJwk, type VerifyFailureReason as DeviceProofVerifyFailureReason, type VerifyFailure as DeviceProofVerifyFailure, type VerifyOptions as DeviceProofVerifyOptions, type VerifySuccess as DeviceProofVerifySuccess, } from './deviceKey.js';
30
30
  export { DPOP_HEADER, DPOP_MAX_AGE_SECONDS, DPOP_PROOF_INVALID, DPOP_PROOF_REQUIRED, DPOP_PROOF_TYP, DPOP_PROOF_VERIFY_UNAVAILABLE, accessTokenHash, createDpopKey, isAth, isJwkThumbprint, normalizeDpopHtu, verifyDpopProof, type DpopKey, type DpopProofClaims, type DpopProofRequest, type DpopVerifyFailureReason, type DpopVerifyOptions, type DpopVerifyResult, } from './dpop.js';
31
- export { initCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
31
+ export { initCredEnvelope, initExistingCredEnvelope, initNewIndependentCredEnvelope, resetCredEnvelope, shutdownCredEnvelope, isCredEnvelopeActive, hasCredEnvelopePathConflict, describeCredEnvelopeState, splitKeyConvergencePolicy, maybeEncryptForWrite, maybeDecryptOnRead, DEFAULT_UNREADABLE_CREDENTIAL_HINT, type DecryptOnReadOptions, __wrappedDekFilePathForTest, __setStateForTest as __setCredEnvelopeStateForTest, __getStateForTest as __getCredEnvelopeStateForTest, __resetShutdownGuardForTest, type ReadTransformResult, } from './credentialEnvelopeBridge.js';
32
32
  export { initDeviceIdentity, resetDeviceIdentity, signServerRequest, enrollWithServer, clearEnrollmentMarker, isDeviceIdentityActive, getDevicePublicKey, describeDeviceIdentityState, describeDeviceKeySplit, __publicKeyPathForTest, __privateKeyPathForTest, __enrollmentMarkerPathForTest, __enrollmentBackoffPathForTest, __setStateForTest as __setDeviceIdentityStateForTest, __getStateForTest as __getDeviceIdentityStateForTest, type EnrollmentResult, } from './deviceIdentityBridge.js';
33
33
  export { getMachineUuid, getMachineUuidForProfile, describeMachineIdentity, type MachineIdentityDescription, type MachineIdentityReason, getHardwareMachineUuid, setMachineUuidProfile, getMachineUuidProfile, qualifyMachineUuid, MACHINE_IDENTITY_FILENAME, hostedRuntimeIdOverride, HOSTED_RUNTIME_MACHINE_SOURCES, __resetMachineUuidCacheForTest, } from './machineUuid.js';
34
34
  export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest, } from './machineId.js';
@@ -50,3 +50,4 @@ export * from './harnessPermissionControls.js';
50
50
  export { compareReleaseVersions } from './releaseVersions.js';
51
51
  export * from './computerUse.js';
52
52
  export * from './desktopWire.js';
53
+ export { INDEPENDENT_MACHINE_PROFILE_RE, INDEPENDENT_MACHINE_RESERVATION_FILE, readIndependentMachineReservation, reserveIndependentMachineProfile, verifyIndependentMachineReservation, type IndependentMachineReservation, } from './independentMachineReservation.js';
@@ -47,6 +47,8 @@ __export(index_exports, {
47
47
  HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT: () => import_harnessSkillLayout.HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT,
48
48
  HARNESS_TIERS: () => import_harnessTrust.HARNESS_TIERS,
49
49
  HOSTED_RUNTIME_MACHINE_SOURCES: () => import_machineUuid.HOSTED_RUNTIME_MACHINE_SOURCES,
50
+ INDEPENDENT_MACHINE_PROFILE_RE: () => import_independentMachineReservation.INDEPENDENT_MACHINE_PROFILE_RE,
51
+ INDEPENDENT_MACHINE_RESERVATION_FILE: () => import_independentMachineReservation.INDEPENDENT_MACHINE_RESERVATION_FILE,
50
52
  InMemoryKek: () => import_kek.InMemoryKek,
51
53
  KekUnavailableError: () => import_kek.KekUnavailableError,
52
54
  LEGACY_CONFIG_DIR_NAMES: () => import_localIdentity.LEGACY_CONFIG_DIR_NAMES,
@@ -189,6 +191,8 @@ __export(index_exports, {
189
191
  hostedRuntimeIdOverride: () => import_machineUuid.hostedRuntimeIdOverride,
190
192
  initCredEnvelope: () => import_credentialEnvelopeBridge.initCredEnvelope,
191
193
  initDeviceIdentity: () => import_deviceIdentityBridge.initDeviceIdentity,
194
+ initExistingCredEnvelope: () => import_credentialEnvelopeBridge.initExistingCredEnvelope,
195
+ initNewIndependentCredEnvelope: () => import_credentialEnvelopeBridge.initNewIndependentCredEnvelope,
192
196
  installLockExitHandlers: () => import_refresh.installLockExitHandlers,
193
197
  invalidateAuthHelperCache: () => import_credentialResolver.invalidateAuthHelperCache,
194
198
  invalidateHeadersHelperCache: () => import_headers_helper.invalidateHeadersHelperCache,
@@ -238,9 +242,11 @@ __export(index_exports, {
238
242
  publicJwkFromX: () => import_deviceKey.publicJwkFromX,
239
243
  qualifyMachineUuid: () => import_machineUuid.qualifyMachineUuid,
240
244
  readAuthFromFd: () => import_fdToken.readAuthFromFd,
245
+ readIndependentMachineReservation: () => import_independentMachineReservation.readIndependentMachineReservation,
241
246
  reauthCommand: () => import_messages.reauthCommand,
242
247
  refreshDaemonToken: () => import_refresh.refreshDaemonToken,
243
248
  registerZeroizeHook: () => import_kek.registerZeroizeHook,
249
+ reserveIndependentMachineProfile: () => import_independentMachineReservation.reserveIndependentMachineProfile,
244
250
  resetCredEnvelope: () => import_credentialEnvelopeBridge.resetCredEnvelope,
245
251
  resetDeviceIdentity: () => import_deviceIdentityBridge.resetDeviceIdentity,
246
252
  resolveChannelManifestUrl: () => import_firstPartyHarnessChannels.resolveChannelManifestUrl,
@@ -263,6 +269,7 @@ __export(index_exports, {
263
269
  verifyChallenge: () => import_pkce.verifyChallenge,
264
270
  verifyDeviceProof: () => import_deviceKey.verifyProof,
265
271
  verifyDpopProof: () => import_dpop.verifyDpopProof,
272
+ verifyIndependentMachineReservation: () => import_independentMachineReservation.verifyIndependentMachineReservation,
266
273
  verifyManifest: () => import_releaseManifest.verifyManifest,
267
274
  withAuthLock: () => import_refresh.withAuthLock,
268
275
  withLoopbackNoProxy: () => import_spawnEnv.withLoopbackNoProxy,
@@ -313,6 +320,7 @@ __reExport(index_exports, require("./harnessPermissionControls.js"), module.expo
313
320
  var import_releaseVersions = require("./releaseVersions.js");
314
321
  __reExport(index_exports, require("./computerUse.js"), module.exports);
315
322
  __reExport(index_exports, require("./desktopWire.js"), module.exports);
323
+ var import_independentMachineReservation = require("./independentMachineReservation.js");
316
324
  // Annotate the CommonJS export names for ESM import in node:
317
325
  0 && (module.exports = {
318
326
  ALL_DAEMON_SCOPES,
@@ -343,6 +351,8 @@ __reExport(index_exports, require("./desktopWire.js"), module.exports);
343
351
  HARNESSES_LOADING_CLAUDE_SKILLS_LAYOUT,
344
352
  HARNESS_TIERS,
345
353
  HOSTED_RUNTIME_MACHINE_SOURCES,
354
+ INDEPENDENT_MACHINE_PROFILE_RE,
355
+ INDEPENDENT_MACHINE_RESERVATION_FILE,
346
356
  InMemoryKek,
347
357
  KekUnavailableError,
348
358
  LEGACY_CONFIG_DIR_NAMES,
@@ -485,6 +495,8 @@ __reExport(index_exports, require("./desktopWire.js"), module.exports);
485
495
  hostedRuntimeIdOverride,
486
496
  initCredEnvelope,
487
497
  initDeviceIdentity,
498
+ initExistingCredEnvelope,
499
+ initNewIndependentCredEnvelope,
488
500
  installLockExitHandlers,
489
501
  invalidateAuthHelperCache,
490
502
  invalidateHeadersHelperCache,
@@ -534,9 +546,11 @@ __reExport(index_exports, require("./desktopWire.js"), module.exports);
534
546
  publicJwkFromX,
535
547
  qualifyMachineUuid,
536
548
  readAuthFromFd,
549
+ readIndependentMachineReservation,
537
550
  reauthCommand,
538
551
  refreshDaemonToken,
539
552
  registerZeroizeHook,
553
+ reserveIndependentMachineProfile,
540
554
  resetCredEnvelope,
541
555
  resetDeviceIdentity,
542
556
  resolveChannelManifestUrl,
@@ -559,6 +573,7 @@ __reExport(index_exports, require("./desktopWire.js"), module.exports);
559
573
  verifyChallenge,
560
574
  verifyDeviceProof,
561
575
  verifyDpopProof,
576
+ verifyIndependentMachineReservation,
562
577
  verifyManifest,
563
578
  withAuthLock,
564
579
  withLoopbackNoProxy,
@@ -178,7 +178,6 @@ async function writeKekToKeychain(kek, service, account) {
178
178
  account,
179
179
  "-w",
180
180
  b64,
181
- "-U",
182
181
  keychainPath
183
182
  ]);
184
183
  } catch (err) {
@@ -219,9 +218,26 @@ async function resolveOrCreateKek(service, account) {
219
218
  return existing;
220
219
  }
221
220
  const fresh = import_node_crypto.default.randomBytes(import_credentialEnvelope.KEY_LEN);
222
- await writeKekToKeychain(fresh, service, account);
223
- _kekCacheBySvc.set(cacheKey, fresh);
224
- return fresh;
221
+ try {
222
+ try {
223
+ await writeKekToKeychain(fresh, service, account);
224
+ } catch (error) {
225
+ const winner = await readKekFromKeychain(service, account).catch(() => null);
226
+ if (!winner)
227
+ throw error;
228
+ _kekCacheBySvc.set(cacheKey, winner);
229
+ return winner;
230
+ }
231
+ const persisted = await readKekFromKeychain(service, account);
232
+ if (!persisted || !persisted.equals(fresh)) {
233
+ persisted?.fill(0);
234
+ throw new import_types.KekUnavailableError(KIND_SHELLOUT, "created master KEK could not be verified");
235
+ }
236
+ _kekCacheBySvc.set(cacheKey, persisted);
237
+ return persisted;
238
+ } finally {
239
+ fresh.fill(0);
240
+ }
225
241
  }
226
242
  async function resolveExistingKek(service, account) {
227
243
  const cacheKey = `${service}::${account}`;
@@ -54,6 +54,16 @@ export declare function splitKeyConvergencePolicy(): {
54
54
  converge: boolean;
55
55
  reason: 'daemon_key_pinned' | 'daemon_key_refused';
56
56
  };
57
+ /** Open only the existing wrapped DEK. Recovery must never enroll a new key,
58
+ * consolidate profile paths or replace an unreadable envelope. A caller gets
59
+ * an error if the existing file or the platform's read-only unwrap is unsafe
60
+ * or unavailable. This intentionally does not load retired keys; credentials
61
+ * that require one remain unavailable rather than triggering a migration. */
62
+ export declare function initExistingCredEnvelope(profile: string): Promise<void>;
63
+ /** First enrollment for a reserved desktop background profile. Existing or
64
+ * ambiguous state is opened read-only or refused; it is never re-enrolled.
65
+ * The caller must validate the reservation before calling this function. */
66
+ export declare function initNewIndependentCredEnvelope(profile: string): Promise<void>;
57
67
  /**
58
68
  * Initialize the envelope path. Idempotent — second call when state is
59
69
  * non-uninit returns immediately.
@@ -40,7 +40,7 @@
40
40
  * when not active. CLI leaves it unset; legacy `OVERSKY_KEK_REQUIRED=1`
41
41
  * env var is still honored as a back-compat trigger.
42
42
  */
43
- import { createHash } from 'node:crypto';
43
+ import { createHash, randomBytes } from 'node:crypto';
44
44
  import fs from 'node:fs';
45
45
  import path from 'node:path';
46
46
  import lockfile from 'proper-lockfile';
@@ -429,6 +429,172 @@ function retiredDekFiles(profile) {
429
429
  return [];
430
430
  }
431
431
  }
432
+ /** Open only the existing wrapped DEK. Recovery must never enroll a new key,
433
+ * consolidate profile paths or replace an unreadable envelope. A caller gets
434
+ * an error if the existing file or the platform's read-only unwrap is unsafe
435
+ * or unavailable. This intentionally does not load retired keys; credentials
436
+ * that require one remain unavailable rather than triggering a migration. */
437
+ export async function initExistingCredEnvelope(profile) {
438
+ if (_state.kind === 'active' && _state.profile === profile)
439
+ return;
440
+ if (_state.kind !== 'uninit' || typeof profile !== 'string' || !profile) {
441
+ throw new Error('Existing credential envelope is unavailable');
442
+ }
443
+ let fd;
444
+ let candidate;
445
+ try {
446
+ const target = wrappedDekFilePath(profile);
447
+ fd = fs.openSync(target, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
448
+ const before = fs.fstatSync(fd);
449
+ const safe = (stat) => stat.isFile() && stat.nlink === 1 && stat.size > 0 && stat.size <= 65_536 &&
450
+ (stat.mode & 0o077) === 0 && (typeof process.getuid !== 'function' || stat.uid === process.getuid());
451
+ if (!safe(before))
452
+ throw new Error('unsafe envelope');
453
+ const readBounded = () => {
454
+ const bytes = Buffer.alloc(before.size);
455
+ let offset = 0;
456
+ while (offset < bytes.length) {
457
+ const count = fs.readSync(fd, bytes, offset, bytes.length - offset, offset);
458
+ if (count <= 0)
459
+ throw new Error('changed envelope');
460
+ offset += count;
461
+ }
462
+ return bytes;
463
+ };
464
+ const wrapped = readBounded();
465
+ const kek = getKekStrategy();
466
+ if (!kek.unwrapExisting || !(await kek.available()))
467
+ throw new Error('read-only KEK unavailable');
468
+ candidate = await kek.unwrapExisting(wrapped, buildCredentialAad(profile));
469
+ if (!Buffer.isBuffer(candidate) || candidate.length !== 32)
470
+ throw new Error('invalid envelope');
471
+ // The unwrap can wait for Keychain. A different file, a replaced path or
472
+ // a second initializer must not make this process adopt a stale key.
473
+ if (!readBounded().equals(wrapped))
474
+ throw new Error('changed envelope');
475
+ const after = fs.fstatSync(fd);
476
+ const current = fs.lstatSync(target);
477
+ if (!safe(after) || !safe(current) || before.dev !== after.dev || before.ino !== after.ino ||
478
+ before.dev !== current.dev || before.ino !== current.ino || before.size !== after.size ||
479
+ before.size !== current.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs ||
480
+ before.mtimeMs !== current.mtimeMs || before.ctimeMs !== current.ctimeMs ||
481
+ wrappedDekFilePath(profile) !== target || _state.kind !== 'uninit') {
482
+ throw new Error('changed envelope');
483
+ }
484
+ for (const old of _retiredDeks)
485
+ old.fill(0);
486
+ _retiredDeks = [];
487
+ _state = { kind: 'active', dek: candidate, kekId: kek.id,
488
+ kekKind: typeof kek.kind === 'string' || kek.kind === null ? (kek.kind ?? null) : null, profile };
489
+ candidate = undefined;
490
+ }
491
+ catch {
492
+ if (Buffer.isBuffer(candidate))
493
+ candidate.fill(0);
494
+ throw new Error('Existing credential envelope is unavailable');
495
+ }
496
+ finally {
497
+ if (fd !== undefined)
498
+ fs.closeSync(fd);
499
+ }
500
+ }
501
+ /** First enrollment for a reserved desktop background profile. Existing or
502
+ * ambiguous state is opened read-only or refused; it is never re-enrolled.
503
+ * The caller must validate the reservation before calling this function. */
504
+ export async function initNewIndependentCredEnvelope(profile) {
505
+ if (!/^desktop-bg-[a-f0-9]{32}$/.test(profile))
506
+ throw new Error('Background credential enrollment is unavailable');
507
+ if (_state.kind === 'active' && _state.profile === profile)
508
+ return;
509
+ if (_state.kind !== 'uninit')
510
+ throw new Error('Background credential enrollment is unavailable');
511
+ const resolved = resolveProfileStateFile(profile, ACCOUNT_CRED_DEK_WRAPPED);
512
+ const paths = [resolved.canonicalPath, ...(resolved.legacyPath ? [resolved.legacyPath] : [])];
513
+ const present = (file) => {
514
+ try {
515
+ fs.lstatSync(file);
516
+ return true;
517
+ }
518
+ catch (err) {
519
+ if (err.code === 'ENOENT')
520
+ return false;
521
+ throw err;
522
+ }
523
+ };
524
+ if (paths.some(present))
525
+ return initExistingCredEnvelope(profile);
526
+ // A missing DEK alongside a previously written identity or credential is
527
+ // recovery, not first enrollment, even if a retried caller says otherwise.
528
+ const dir = resolved.canonicalDir;
529
+ const stat = fs.lstatSync(dir);
530
+ if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0 ||
531
+ (typeof process.getuid === 'function' && stat.uid !== process.getuid())) {
532
+ throw new Error('Background credential enrollment is unavailable');
533
+ }
534
+ const artifacts = ['device-private-key', 'device-public-key.jwk', 'auth-binding.json', 'auth.json'];
535
+ const artifactDirs = [dir, ...(resolved.legacyDir ? [resolved.legacyDir] : [])];
536
+ const assertEmpty = () => {
537
+ if (paths.some(present) || artifactDirs.some((root) => artifacts.some((name) => present(path.join(root, name)))) ||
538
+ wrappedDekFilePath(profile) !== resolved.canonicalPath) {
539
+ throw new Error('Background credential enrollment needs recovery');
540
+ }
541
+ };
542
+ assertEmpty();
543
+ const kek = getKekStrategy();
544
+ if (!kek.unwrapExisting || !(await kek.available()))
545
+ throw new Error('Background credential enrollment is unavailable');
546
+ assertEmpty();
547
+ const dek = generateDek();
548
+ let temp;
549
+ try {
550
+ const wrapped = await kek.wrap(dek, buildCredentialAad(profile));
551
+ // Another enrollment may have won while the platform store was busy.
552
+ // Its file is opened read-only; this process never overwrites it.
553
+ if (paths.some(present))
554
+ return await initExistingCredEnvelope(profile);
555
+ assertEmpty();
556
+ const temporaryPath = path.join(dir, `.cred-dek-enrollment.${randomBytes(12).toString('hex')}.tmp`);
557
+ const fd = fs.openSync(temporaryPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL |
558
+ (fs.constants.O_NOFOLLOW ?? 0), 0o600);
559
+ temp = temporaryPath;
560
+ try {
561
+ fs.writeFileSync(fd, wrapped);
562
+ fs.fsyncSync(fd);
563
+ }
564
+ finally {
565
+ fs.closeSync(fd);
566
+ }
567
+ assertEmpty();
568
+ try {
569
+ fs.linkSync(temp, resolved.canonicalPath);
570
+ }
571
+ catch (err) {
572
+ if (err.code !== 'EEXIST')
573
+ throw err;
574
+ }
575
+ const dirFd = fs.openSync(dir, 'r');
576
+ try {
577
+ fs.fsyncSync(dirFd);
578
+ }
579
+ finally {
580
+ fs.closeSync(dirFd);
581
+ }
582
+ }
583
+ finally {
584
+ dek.fill(0);
585
+ if (temp) {
586
+ fs.unlinkSync(temp);
587
+ const dirFd = fs.openSync(dir, 'r');
588
+ try {
589
+ fs.fsyncSync(dirFd);
590
+ }
591
+ finally {
592
+ fs.closeSync(dirFd);
593
+ }
594
+ }
595
+ }
596
+ await initExistingCredEnvelope(profile);
597
+ }
432
598
  /**
433
599
  * Initialize the envelope path. Idempotent — second call when state is
434
600
  * non-uninit returns immediately.
@@ -440,6 +606,11 @@ function retiredDekFiles(profile) {
440
606
  * 4. Any throw → disabled+init_failed; transforms stay passthrough.
441
607
  */
442
608
  export async function initCredEnvelope(profile) {
609
+ // A reserved background profile has an independent owner and device key.
610
+ // Generic status, startup and CLI paths may read it, but cannot replace a
611
+ // missing or unreadable key. Only validated first enrollment can create one.
612
+ if (/^desktop-bg-[a-f0-9]{32}$/i.test(profile))
613
+ return initExistingCredEnvelope(profile);
443
614
  if (_state.kind !== 'uninit')
444
615
  return;
445
616
  if (typeof profile !== 'string' || profile.length === 0) {
@@ -0,0 +1,20 @@
1
+ export interface DaemonConsentPayload {
2
+ consentVersion: number;
3
+ consentSource: string;
4
+ isLegacy: boolean;
5
+ isReConsent: boolean;
6
+ scopes: string[];
7
+ scopeDescriptions: Record<string, string>;
8
+ }
9
+ /** A single canonical, bounded representation on both sides of the log. */
10
+ export declare function normalizeDaemonConsent(input: unknown): DaemonConsentPayload | null;
11
+ export interface DaemonConsentIdentity {
12
+ pid: number;
13
+ daemonId: string;
14
+ userId: string;
15
+ origin: string;
16
+ }
17
+ /** The bootstrap secret is never logged. This MAC binds the exact consent,
18
+ * live process, daemon and cloud owner; an old log entry is not authority. */
19
+ export declare function daemonConsentProof(secret: string, identity: DaemonConsentIdentity, payload: DaemonConsentPayload): string;
20
+ export declare function verifyDaemonConsentProof(secret: string, identity: DaemonConsentIdentity, payload: DaemonConsentPayload, proof: string): boolean;
@@ -0,0 +1,43 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ /** A single canonical, bounded representation on both sides of the log. */
3
+ export function normalizeDaemonConsent(input) {
4
+ if (!input || typeof input !== 'object' || Array.isArray(input))
5
+ return null;
6
+ const value = input;
7
+ if (!Number.isSafeInteger(value.consentVersion) || value.consentVersion < 0 ||
8
+ typeof value.consentSource !== 'string' || value.consentSource.length > 64 ||
9
+ typeof value.isLegacy !== 'boolean' || typeof value.isReConsent !== 'boolean' ||
10
+ !Array.isArray(value.scopes) || value.scopes.length > 128 ||
11
+ !value.scopes.every((scope) => typeof scope === 'string' && scope.length <= 256) ||
12
+ !value.scopeDescriptions || typeof value.scopeDescriptions !== 'object' || Array.isArray(value.scopeDescriptions))
13
+ return null;
14
+ const entries = Object.entries(value.scopeDescriptions)
15
+ .sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
16
+ if (entries.length > 128 || entries.some(([key, text]) => key.length > 256 || typeof text !== 'string' || text.length > 1024))
17
+ return null;
18
+ return { consentVersion: value.consentVersion, consentSource: value.consentSource,
19
+ isLegacy: value.isLegacy, isReConsent: value.isReConsent, scopes: value.scopes,
20
+ scopeDescriptions: Object.fromEntries(entries) };
21
+ }
22
+ /** The bootstrap secret is never logged. This MAC binds the exact consent,
23
+ * live process, daemon and cloud owner; an old log entry is not authority. */
24
+ export function daemonConsentProof(secret, identity, payload) {
25
+ const key = Buffer.from(secret, 'base64');
26
+ if (key.length !== 32 || !Number.isSafeInteger(identity.pid) || identity.pid <= 0 ||
27
+ !identity.daemonId || !identity.userId)
28
+ throw new Error('Invalid daemon consent identity');
29
+ const origin = new URL(identity.origin).origin;
30
+ return createHmac('sha256', key).update('skrr-daemon-consent:v1\0').update(JSON.stringify([
31
+ identity.pid, identity.daemonId, identity.userId.toLowerCase(), origin, payload,
32
+ ])).digest('hex');
33
+ }
34
+ export function verifyDaemonConsentProof(secret, identity, payload, proof) {
35
+ if (!/^[a-f0-9]{64}$/.test(proof))
36
+ return false;
37
+ try {
38
+ return timingSafeEqual(Buffer.from(proof, 'hex'), Buffer.from(daemonConsentProof(secret, identity, payload), 'hex'));
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }