@botbotgo/common 1.0.0

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 (83) hide show
  1. package/README.md +54 -0
  2. package/dist/chunk-DIIWJNRE.js +51 -0
  3. package/dist/chunk-DIIWJNRE.js.map +1 -0
  4. package/dist/chunk-H5BG6SXW.js +33 -0
  5. package/dist/chunk-H5BG6SXW.js.map +1 -0
  6. package/dist/chunk-HBCZVEUG.js +92 -0
  7. package/dist/chunk-HBCZVEUG.js.map +1 -0
  8. package/dist/chunk-OTWARMTU.js +182 -0
  9. package/dist/chunk-OTWARMTU.js.map +1 -0
  10. package/dist/chunk-QG6CT2GZ.js +31 -0
  11. package/dist/chunk-QG6CT2GZ.js.map +1 -0
  12. package/dist/chunk-RUIAZ7GO.js +463 -0
  13. package/dist/chunk-RUIAZ7GO.js.map +1 -0
  14. package/dist/chunk-X4TDNR4V.js +206 -0
  15. package/dist/chunk-X4TDNR4V.js.map +1 -0
  16. package/dist/cli/index.d.ts +1 -0
  17. package/dist/cli/index.js +13 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/startup.d.ts +14 -0
  20. package/dist/config/hydrate.d.ts +15 -0
  21. package/dist/config/index.d.ts +3 -0
  22. package/dist/config/index.js +27 -0
  23. package/dist/config/index.js.map +1 -0
  24. package/dist/config/kind.d.ts +28 -0
  25. package/dist/config/path.d.ts +14 -0
  26. package/dist/config/yaml.d.ts +32 -0
  27. package/dist/connectivity/check.d.ts +19 -0
  28. package/dist/connectivity/index.d.ts +3 -0
  29. package/dist/connectivity/index.js +102 -0
  30. package/dist/connectivity/index.js.map +1 -0
  31. package/dist/connectivity/types.d.ts +12 -0
  32. package/dist/context/default-context.d.ts +10 -0
  33. package/dist/context/index.d.ts +2 -0
  34. package/dist/context/index.js +16 -0
  35. package/dist/context/index.js.map +1 -0
  36. package/dist/context/tokens.d.ts +29 -0
  37. package/dist/events/index.d.ts +17 -0
  38. package/dist/events/index.js +20 -0
  39. package/dist/events/index.js.map +1 -0
  40. package/dist/events/progress-listener.d.ts +8 -0
  41. package/dist/events/runtime2-tree-listener.d.ts +26 -0
  42. package/dist/index.d.ts +4 -0
  43. package/dist/index.js +49 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/npm/cache.d.ts +9 -0
  46. package/dist/npm/command.d.ts +36 -0
  47. package/dist/npm/index.d.ts +1 -0
  48. package/dist/npm/index.js +29 -0
  49. package/dist/npm/index.js.map +1 -0
  50. package/dist/npm/install.d.ts +8 -0
  51. package/dist/npm/provider.d.ts +14 -0
  52. package/dist/npm/version.d.ts +11 -0
  53. package/dist/security-store/backends/file/index.d.ts +19 -0
  54. package/dist/security-store/backends/index.d.ts +7 -0
  55. package/dist/security-store/backends/keychain/constants.d.ts +4 -0
  56. package/dist/security-store/backends/keychain/index.d.ts +17 -0
  57. package/dist/security-store/backends/keychain/keytar.d.ts +5 -0
  58. package/dist/security-store/backends/keychain/swift-bridge.d.ts +17 -0
  59. package/dist/security-store/backends/keychain/utils.d.ts +11 -0
  60. package/dist/security-store/backends/memory/index.d.ts +10 -0
  61. package/dist/security-store/backends/types.d.ts +29 -0
  62. package/dist/security-store/backends/utils.d.ts +2 -0
  63. package/dist/security-store/index.d.ts +2 -0
  64. package/dist/security-store/index.js +819 -0
  65. package/dist/security-store/index.js.map +1 -0
  66. package/dist/security-store/store.d.ts +49 -0
  67. package/dist/testing/index.d.ts +1 -0
  68. package/dist/testing/index.js +7 -0
  69. package/dist/testing/index.js.map +1 -0
  70. package/dist/utils/agent-result.d.ts +4 -0
  71. package/dist/utils/checksum.d.ts +16 -0
  72. package/dist/utils/deep-merge.d.ts +4 -0
  73. package/dist/utils/frontmatter.d.ts +12 -0
  74. package/dist/utils/index.d.ts +10 -0
  75. package/dist/utils/index.js +38 -0
  76. package/dist/utils/index.js.map +1 -0
  77. package/dist/utils/log.d.ts +1 -0
  78. package/dist/utils/object.d.ts +4 -0
  79. package/dist/utils/override-with-config.d.ts +12 -0
  80. package/dist/utils/parsing.d.ts +4 -0
  81. package/dist/utils/selection/tool-choice.d.ts +1 -0
  82. package/dist/utils/selection/tool-registry.d.ts +5 -0
  83. package/package.json +128 -0
@@ -0,0 +1,819 @@
1
+ // src/security-store/store.ts
2
+ import crypto from "crypto";
3
+
4
+ // src/security-store/backends/file/index.ts
5
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
6
+ import path2 from "path";
7
+
8
+ // src/security-store/backends/utils.ts
9
+ import { homedir } from "os";
10
+ import path from "path";
11
+ function buildEntryKey(service, account) {
12
+ return `${service}:${account}`;
13
+ }
14
+ function sanitizeServiceName(service) {
15
+ return service.replace(/[^A-Za-z0-9._-]+/g, "_");
16
+ }
17
+ function defaultFileStorePath(service = "default") {
18
+ return path.join(homedir(), ".botbotgo", "secret-store", `${sanitizeServiceName(service)}.json`);
19
+ }
20
+
21
+ // src/security-store/backends/file/index.ts
22
+ var FILE_STORE_VERSION = 1;
23
+ function emptyFileStoreData() {
24
+ return { version: FILE_STORE_VERSION, secrets: {}, masterKeys: {} };
25
+ }
26
+ var FileSecretStoreBackend = class {
27
+ filePath;
28
+ data = null;
29
+ loading = null;
30
+ writeQueue = Promise.resolve();
31
+ constructor(options = {}) {
32
+ this.filePath = options.filePath?.trim() || defaultFileStorePath(options.service);
33
+ }
34
+ async init() {
35
+ await this.ensureLoaded();
36
+ }
37
+ async getSecret(service, account) {
38
+ await this.ensureLoaded();
39
+ return this.data?.secrets[buildEntryKey(service, account)];
40
+ }
41
+ async setSecret(service, account, value) {
42
+ await this.mutate((data) => {
43
+ data.secrets[buildEntryKey(service, account)] = value;
44
+ });
45
+ }
46
+ async deleteSecret(service, account) {
47
+ let deleted = false;
48
+ await this.mutate((data) => {
49
+ const key = buildEntryKey(service, account);
50
+ deleted = Object.hasOwn(data.secrets, key);
51
+ delete data.secrets[key];
52
+ });
53
+ return deleted;
54
+ }
55
+ async getMasterKey(context) {
56
+ await this.ensureLoaded();
57
+ const encoded = this.data?.masterKeys[buildEntryKey(context.service, context.account)];
58
+ return encoded ? Buffer.from(encoded, "base64") : null;
59
+ }
60
+ async setMasterKey(context, key) {
61
+ await this.mutate((data) => {
62
+ data.masterKeys[buildEntryKey(context.service, context.account)] = key.toString("base64");
63
+ });
64
+ }
65
+ async ensureLoaded() {
66
+ if (this.data) return;
67
+ if (!this.loading) this.loading = this.load();
68
+ await this.loading;
69
+ }
70
+ async load() {
71
+ const directory = path2.dirname(this.filePath);
72
+ await mkdir(directory, { recursive: true });
73
+ await this.setPermissions(directory, 448);
74
+ try {
75
+ const raw = await readFile(this.filePath, "utf8");
76
+ const parsed = JSON.parse(raw);
77
+ this.data = {
78
+ version: FILE_STORE_VERSION,
79
+ secrets: parsed.secrets ?? {},
80
+ masterKeys: parsed.masterKeys ?? {}
81
+ };
82
+ await this.setPermissions(this.filePath, 384);
83
+ } catch (error) {
84
+ const code = error?.code;
85
+ if (code !== "ENOENT") throw error;
86
+ this.data = emptyFileStoreData();
87
+ } finally {
88
+ this.loading = null;
89
+ }
90
+ }
91
+ async mutate(update) {
92
+ await this.ensureLoaded();
93
+ this.writeQueue = this.writeQueue.then(async () => {
94
+ const current = this.data ?? emptyFileStoreData();
95
+ const next = {
96
+ version: current.version,
97
+ secrets: { ...current.secrets },
98
+ masterKeys: { ...current.masterKeys }
99
+ };
100
+ update(next);
101
+ await this.persist(next);
102
+ this.data = next;
103
+ });
104
+ await this.writeQueue;
105
+ }
106
+ async persist(data) {
107
+ const directory = path2.dirname(this.filePath);
108
+ const tempPath = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;
109
+ await mkdir(directory, { recursive: true });
110
+ await writeFile(tempPath, `${JSON.stringify(data, null, 2)}
111
+ `, {
112
+ encoding: "utf8",
113
+ mode: 384
114
+ });
115
+ await rename(tempPath, this.filePath);
116
+ await this.setPermissions(this.filePath, 384);
117
+ }
118
+ async setPermissions(targetPath, mode) {
119
+ try {
120
+ await chmod(targetPath, mode);
121
+ } catch {
122
+ }
123
+ }
124
+ };
125
+
126
+ // src/security-store/backends/keychain/index.ts
127
+ import { execFile as execFile2 } from "child_process";
128
+ import { promisify as promisify2 } from "util";
129
+
130
+ // src/security-store/backends/keychain/constants.ts
131
+ var SECURITY_BIN = "/usr/bin/security";
132
+ var CODESIGN_BIN = "/usr/bin/codesign";
133
+ var MASTER_KEY_LENGTH = 32;
134
+ var DEFAULT_BIOMETRIC_PROMPT = "Authenticate to access secure data";
135
+
136
+ // src/security-store/backends/keychain/keytar.ts
137
+ import { createRequire } from "module";
138
+
139
+ // src/security-store/backends/keychain/utils.ts
140
+ function assertMacOS() {
141
+ if (process.platform !== "darwin") {
142
+ throw new Error("This module is only supported on macOS.");
143
+ }
144
+ }
145
+ function assertNonEmpty(value, field) {
146
+ const normalized = value.trim();
147
+ if (normalized.length === 0) {
148
+ throw new Error(`${field} must be a non-empty string.`);
149
+ }
150
+ return normalized;
151
+ }
152
+ function normalizeAppleDeveloperTeamId(teamId) {
153
+ const value = teamId?.trim();
154
+ if (!value) return void 0;
155
+ if (!/^[A-Za-z0-9]{10}$/.test(value)) {
156
+ throw new Error("appleDeveloperTeamId must be a 10-character alphanumeric string.");
157
+ }
158
+ return value;
159
+ }
160
+ function normalizeBiometricPolicy(policy) {
161
+ if (!policy) return "none";
162
+ if (policy === "none" || policy === "auto" || policy === "user-presence" || policy === "biometry-current-set") {
163
+ return policy;
164
+ }
165
+ return "none";
166
+ }
167
+ function resolveSwiftBiometricPolicy(policy) {
168
+ return policy === "biometry-current-set" ? "biometry-current-set" : "user-presence";
169
+ }
170
+ function isSwiftUnavailableError(error) {
171
+ const text = error instanceof Error ? `${error.message}` : String(error ?? "");
172
+ return text.includes("spawn xcrun ENOENT") || text.includes("xcrun: error") || text.includes('unable to find utility "swift"') || text.includes("invalid active developer path") || text.includes("tool 'swift' requires Xcode");
173
+ }
174
+ function isSwiftEntitlementError(error) {
175
+ const text = error instanceof Error ? `${error.message}` : String(error ?? "");
176
+ return text.includes("A required entitlement isn't present.") || text.includes("errSecMissingEntitlement");
177
+ }
178
+ function isKeychainNotFoundError(error) {
179
+ const text = error instanceof Error ? `${error.message}` : String(error ?? "");
180
+ return text.includes("could not be found") || text.includes("The specified item could not be found");
181
+ }
182
+ function isPackagedApp() {
183
+ return process.execPath.includes(".app/Contents/MacOS/");
184
+ }
185
+ function resolveAppBinPath(appBinPath) {
186
+ if (appBinPath) return appBinPath;
187
+ if (isPackagedApp()) return process.execPath;
188
+ throw new Error(
189
+ "appBinPath is required when not running from a packaged .app (process.execPath does not contain .app/Contents/MacOS/)"
190
+ );
191
+ }
192
+
193
+ // src/security-store/backends/keychain/keytar.ts
194
+ var require2 = createRequire(import.meta.url);
195
+ var keytar = null;
196
+ function getKeytar() {
197
+ assertMacOS();
198
+ if (keytar === null) {
199
+ try {
200
+ const loaded = require2("keytar");
201
+ if (!loaded || typeof loaded.getPassword !== "function") {
202
+ throw new Error("keytar.getPassword() is unavailable.");
203
+ }
204
+ keytar = loaded;
205
+ } catch {
206
+ throw new Error(
207
+ "keytar is not available. Install keytar to use Keychain features on macOS."
208
+ );
209
+ }
210
+ }
211
+ return keytar;
212
+ }
213
+
214
+ // src/security-store/backends/keychain/swift-bridge.ts
215
+ import { execFile } from "child_process";
216
+ import { promisify } from "util";
217
+ var execFileAsync = promisify(execFile);
218
+ var SWIFT_KEYCHAIN_BRIDGE = `
219
+ import Foundation
220
+ import Security
221
+
222
+ func emit(_ obj: [String: Any]) {
223
+ let data = try! JSONSerialization.data(withJSONObject: obj, options: [])
224
+ print(String(data: data, encoding: .utf8)!)
225
+ }
226
+
227
+ func secMessage(_ status: OSStatus) -> String {
228
+ if let msg = SecCopyErrorMessageString(status, nil) { return msg as String }
229
+ return "OSStatus \\(status)"
230
+ }
231
+
232
+ let env = ProcessInfo.processInfo.environment
233
+ let op = env["EASYNET_KC_OP"] ?? ""
234
+ let service = env["EASYNET_KC_SERVICE"] ?? ""
235
+ let account = env["EASYNET_KC_ACCOUNT"] ?? ""
236
+ let secret = env["EASYNET_KC_SECRET"] ?? ""
237
+ let prompt = env["EASYNET_KC_PROMPT"] ?? ""
238
+ let biometric = env["EASYNET_KC_BIOMETRIC"] ?? "none"
239
+
240
+ if op.isEmpty || service.isEmpty || account.isEmpty {
241
+ emit(["ok": false, "error": "Missing required env values"])
242
+ exit(2)
243
+ }
244
+
245
+ func buildBaseQuery() -> [String: Any] {
246
+ return [
247
+ kSecClass as String: kSecClassGenericPassword,
248
+ kSecAttrService as String: service,
249
+ kSecAttrAccount as String: account
250
+ ]
251
+ }
252
+
253
+ func buildAccessControl(_ policy: String) -> SecAccessControl? {
254
+ if policy == "none" { return nil }
255
+ var flags: SecAccessControlCreateFlags = []
256
+ if policy == "user-presence" {
257
+ flags.insert(.userPresence)
258
+ } else if policy == "biometry-current-set" {
259
+ flags.insert(.biometryCurrentSet)
260
+ } else {
261
+ flags.insert(.userPresence)
262
+ }
263
+ var err: Unmanaged<CFError>?
264
+ let ac = SecAccessControlCreateWithFlags(
265
+ nil,
266
+ kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
267
+ flags,
268
+ &err
269
+ )
270
+ return ac
271
+ }
272
+
273
+ switch op {
274
+ case "set":
275
+ guard let data = secret.data(using: .utf8) else {
276
+ emit(["ok": false, "error": "Secret is not valid UTF-8"])
277
+ exit(2)
278
+ }
279
+ var add = buildBaseQuery()
280
+ add[kSecValueData as String] = data
281
+
282
+ if biometric != "none" {
283
+ guard let ac = buildAccessControl(biometric) else {
284
+ emit(["ok": false, "error": "Failed to build access control"])
285
+ exit(1)
286
+ }
287
+ add[kSecAttrAccessControl as String] = ac
288
+ _ = SecItemDelete(buildBaseQuery() as CFDictionary)
289
+ let status = SecItemAdd(add as CFDictionary, nil)
290
+ if status != errSecSuccess {
291
+ emit(["ok": false, "error": secMessage(status)])
292
+ exit(1)
293
+ }
294
+ emit(["ok": true])
295
+ exit(0)
296
+ }
297
+
298
+ let addStatus = SecItemAdd(add as CFDictionary, nil)
299
+ if addStatus == errSecSuccess {
300
+ emit(["ok": true])
301
+ exit(0)
302
+ }
303
+ if addStatus == errSecDuplicateItem {
304
+ let updateStatus = SecItemUpdate(buildBaseQuery() as CFDictionary, [kSecValueData as String: data] as CFDictionary)
305
+ if updateStatus == errSecSuccess {
306
+ emit(["ok": true])
307
+ exit(0)
308
+ }
309
+ emit(["ok": false, "error": secMessage(updateStatus)])
310
+ exit(1)
311
+ }
312
+ emit(["ok": false, "error": secMessage(addStatus)])
313
+ exit(1)
314
+
315
+ case "get":
316
+ var query = buildBaseQuery()
317
+ query[kSecReturnData as String] = true
318
+ query[kSecMatchLimit as String] = kSecMatchLimitOne
319
+ if !prompt.isEmpty {
320
+ query[kSecUseOperationPrompt as String] = prompt
321
+ }
322
+ var item: CFTypeRef?
323
+ let status = SecItemCopyMatching(query as CFDictionary, &item)
324
+ if status == errSecItemNotFound {
325
+ emit(["ok": true, "found": false])
326
+ exit(0)
327
+ }
328
+ if status != errSecSuccess {
329
+ emit(["ok": false, "error": secMessage(status)])
330
+ exit(1)
331
+ }
332
+ guard let data = item as? Data else {
333
+ emit(["ok": false, "error": "Unexpected keychain data"])
334
+ exit(1)
335
+ }
336
+ let value = String(data: data, encoding: .utf8) ?? ""
337
+ emit(["ok": true, "found": true, "value": value])
338
+ exit(0)
339
+
340
+ default:
341
+ emit(["ok": false, "error": "Unsupported operation"])
342
+ exit(2)
343
+ }
344
+ `;
345
+ async function runSwiftKeychain(op, input) {
346
+ const env = {
347
+ ...process.env,
348
+ EASYNET_KC_OP: op,
349
+ EASYNET_KC_SERVICE: input.service,
350
+ EASYNET_KC_ACCOUNT: input.account,
351
+ EASYNET_KC_SECRET: input.secret ?? "",
352
+ EASYNET_KC_PROMPT: input.prompt ?? "",
353
+ EASYNET_KC_BIOMETRIC: normalizeBiometricPolicy(input.biometric)
354
+ };
355
+ try {
356
+ const { stdout } = await execFileAsync("xcrun", ["swift", "-e", SWIFT_KEYCHAIN_BRIDGE], {
357
+ env,
358
+ encoding: "utf8",
359
+ maxBuffer: 1024 * 1024
360
+ });
361
+ return JSON.parse(stdout.trim());
362
+ } catch (error) {
363
+ const err = error;
364
+ const stderr = typeof err.stderr === "string" ? err.stderr.trim() : Buffer.isBuffer(err.stderr) ? err.stderr.toString("utf8").trim() : "";
365
+ const stdout = typeof err.stdout === "string" ? err.stdout.trim() : Buffer.isBuffer(err.stdout) ? err.stdout.toString("utf8").trim() : "";
366
+ const details = [
367
+ err.message ?? String(error),
368
+ stderr ? `stderr: ${stderr}` : "",
369
+ stdout ? `stdout: ${stdout}` : ""
370
+ ].filter(Boolean).join("\n");
371
+ throw new Error(`Swift keychain bridge failed for op=${op}.
372
+ ${details}`);
373
+ }
374
+ }
375
+ async function runSwiftKeychainWithFallback(op, input) {
376
+ const policy = resolveSwiftBiometricPolicy(input.biometric);
377
+ try {
378
+ return await runSwiftKeychain(op, { ...input, biometric: policy });
379
+ } catch (error) {
380
+ if (isSwiftUnavailableError(error)) return void 0;
381
+ if (input.biometric === "auto" && isSwiftEntitlementError(error)) return void 0;
382
+ throw error;
383
+ }
384
+ }
385
+ function resolvePrompt(prompt) {
386
+ return prompt?.trim() || DEFAULT_BIOMETRIC_PROMPT;
387
+ }
388
+
389
+ // src/security-store/backends/keychain/index.ts
390
+ var execFileAsync2 = promisify2(execFile2);
391
+ async function getSecretViaSecurityCli(service, account) {
392
+ try {
393
+ const { stdout } = await execFileAsync2(SECURITY_BIN, [
394
+ "find-generic-password",
395
+ "-s",
396
+ service,
397
+ "-a",
398
+ account,
399
+ "-w"
400
+ ]);
401
+ const value = (typeof stdout === "string" ? stdout : String(stdout)).replace(/\r?\n$/, "");
402
+ return value.length > 0 ? value : null;
403
+ } catch (error) {
404
+ if (isKeychainNotFoundError(error)) return null;
405
+ throw error;
406
+ }
407
+ }
408
+ function isKeytarUnavailable(error) {
409
+ const text = error instanceof Error ? error.message : String(error ?? "");
410
+ return text.includes("keytar is not available");
411
+ }
412
+ async function getPasswordViaKeytarOrCli(service, account) {
413
+ try {
414
+ return await getKeytar().getPassword(service, account);
415
+ } catch (error) {
416
+ if (!isKeytarUnavailable(error)) throw error;
417
+ return getSecretViaSecurityCli(service, account);
418
+ }
419
+ }
420
+ function buildAddGenericPasswordArgs(service, account, value, appBinPath) {
421
+ return [
422
+ "add-generic-password",
423
+ "-s",
424
+ service,
425
+ "-a",
426
+ account,
427
+ "-w",
428
+ value,
429
+ ...appBinPath ? ["-T", appBinPath] : [],
430
+ "-U"
431
+ ];
432
+ }
433
+ async function getKeychainPassword(service, account, biometric, prompt) {
434
+ if (biometric === "none") return getPasswordViaKeytarOrCli(service, account);
435
+ const result = await runSwiftKeychainWithFallback("get", {
436
+ service,
437
+ account,
438
+ prompt: resolvePrompt(prompt),
439
+ biometric
440
+ });
441
+ if (!result) return getPasswordViaKeytarOrCli(service, account);
442
+ if (!result.ok) throw new Error(result.error ?? "Failed to get key from keychain.");
443
+ return result.found ? result.value ?? "" : null;
444
+ }
445
+ async function getKeyFromKeychain(service, account, options = {}) {
446
+ const normalizedService = assertNonEmpty(service, "service");
447
+ const normalizedAccount = assertNonEmpty(account, "account");
448
+ const biometric = normalizeBiometricPolicy(options.biometric);
449
+ const password = await getKeychainPassword(
450
+ normalizedService,
451
+ normalizedAccount,
452
+ biometric,
453
+ options.prompt
454
+ );
455
+ if (!password) return null;
456
+ const buf = Buffer.from(password, "base64");
457
+ if (buf.length !== MASTER_KEY_LENGTH) return null;
458
+ return buf;
459
+ }
460
+ async function getSecretFromKeychain(service, account) {
461
+ const normalizedService = assertNonEmpty(service, "service");
462
+ const normalizedAccount = assertNonEmpty(account, "account");
463
+ return getPasswordViaKeytarOrCli(normalizedService, normalizedAccount);
464
+ }
465
+ async function getTeamIdFromBinary(appBinPath) {
466
+ assertMacOS();
467
+ const normalizedPath = assertNonEmpty(appBinPath, "appBinPath");
468
+ try {
469
+ const { stdout, stderr } = await execFileAsync2(CODESIGN_BIN, ["-dvvv", normalizedPath], {
470
+ encoding: "utf8",
471
+ maxBuffer: 64 * 1024
472
+ });
473
+ const combined = `${stdout ?? ""}
474
+ ${stderr ?? ""}`;
475
+ const match = /TeamIdentifier=(.+)/.exec(combined);
476
+ if (!match) return null;
477
+ const raw = match[1].trim();
478
+ if (!raw || /^not\s+set$/i.test(raw)) return null;
479
+ const normalized = raw.match(/^[A-Za-z0-9]+$/)?.[0] ?? "";
480
+ return normalized || null;
481
+ } catch {
482
+ return null;
483
+ }
484
+ }
485
+ async function setKeyInKeychainWithAcl(service, account, keyBuffer, appBinPath, appleDeveloperTeamId, options = {}) {
486
+ assertMacOS();
487
+ const normalizedService = assertNonEmpty(service, "service");
488
+ const normalizedAccount = assertNonEmpty(account, "account");
489
+ const normalizedAppBinPath = assertNonEmpty(appBinPath, "appBinPath");
490
+ const biometric = normalizeBiometricPolicy(options.biometric);
491
+ if (keyBuffer.length !== MASTER_KEY_LENGTH) {
492
+ throw new Error(`Master key must be ${MASTER_KEY_LENGTH} bytes, got ${keyBuffer.length}`);
493
+ }
494
+ const b64 = keyBuffer.toString("base64");
495
+ if (biometric === "none") {
496
+ await execFileAsync2(
497
+ SECURITY_BIN,
498
+ buildAddGenericPasswordArgs(
499
+ normalizedService,
500
+ normalizedAccount,
501
+ b64,
502
+ normalizedAppBinPath
503
+ )
504
+ );
505
+ } else {
506
+ const result = await runSwiftKeychainWithFallback("set", {
507
+ service: normalizedService,
508
+ account: normalizedAccount,
509
+ secret: b64,
510
+ biometric
511
+ });
512
+ if (!result) {
513
+ await execFileAsync2(
514
+ SECURITY_BIN,
515
+ buildAddGenericPasswordArgs(
516
+ normalizedService,
517
+ normalizedAccount,
518
+ b64,
519
+ normalizedAppBinPath
520
+ )
521
+ );
522
+ } else if (!result.ok) {
523
+ throw new Error(result.error ?? "Failed to store key in keychain.");
524
+ }
525
+ }
526
+ const normalizedTeamId = normalizeAppleDeveloperTeamId(appleDeveloperTeamId);
527
+ if (normalizedTeamId) {
528
+ try {
529
+ await execFileAsync2(SECURITY_BIN, [
530
+ "set-generic-password-partition-list",
531
+ "-s",
532
+ normalizedService,
533
+ "-a",
534
+ normalizedAccount,
535
+ "-S",
536
+ `teamid:${normalizedTeamId}`
537
+ ]);
538
+ } catch {
539
+ }
540
+ }
541
+ }
542
+ async function setSecretInKeychain(service, account, value) {
543
+ assertMacOS();
544
+ const normalizedService = assertNonEmpty(service, "service");
545
+ const normalizedAccount = assertNonEmpty(account, "account");
546
+ await execFileAsync2(
547
+ SECURITY_BIN,
548
+ buildAddGenericPasswordArgs(normalizedService, normalizedAccount, value)
549
+ );
550
+ }
551
+ async function deleteSecretFromKeychain(service, account) {
552
+ assertMacOS();
553
+ const normalizedService = assertNonEmpty(service, "service");
554
+ const normalizedAccount = assertNonEmpty(account, "account");
555
+ try {
556
+ await execFileAsync2(SECURITY_BIN, [
557
+ "delete-generic-password",
558
+ "-s",
559
+ normalizedService,
560
+ "-a",
561
+ normalizedAccount
562
+ ]);
563
+ return true;
564
+ } catch (error) {
565
+ if (isKeychainNotFoundError(error)) return false;
566
+ throw error;
567
+ }
568
+ }
569
+ var KeychainSecretStoreBackend = class {
570
+ async getSecret(service, account) {
571
+ return await getSecretFromKeychain(service, account) ?? void 0;
572
+ }
573
+ async setSecret(service, account, value) {
574
+ await setSecretInKeychain(service, account, value);
575
+ }
576
+ async deleteSecret(service, account) {
577
+ return deleteSecretFromKeychain(service, account);
578
+ }
579
+ async getMasterKey(context) {
580
+ return getKeyFromKeychain(context.service, context.account, {
581
+ biometric: context.biometric,
582
+ prompt: context.prompt
583
+ });
584
+ }
585
+ async setMasterKey(context, key) {
586
+ const appPath = resolveAppBinPath(context.appBinPath);
587
+ const teamId = context.appleDeveloperTeamId ?? await getTeamIdFromBinary(appPath);
588
+ await setKeyInKeychainWithAcl(context.service, context.account, key, appPath, teamId, {
589
+ biometric: context.biometric,
590
+ prompt: context.prompt
591
+ });
592
+ }
593
+ };
594
+
595
+ // src/security-store/backends/memory/index.ts
596
+ var MemorySecretStoreBackend = class {
597
+ secrets = /* @__PURE__ */ new Map();
598
+ masterKeys = /* @__PURE__ */ new Map();
599
+ async getSecret(service, account) {
600
+ return this.secrets.get(buildEntryKey(service, account));
601
+ }
602
+ async setSecret(service, account, value) {
603
+ this.secrets.set(buildEntryKey(service, account), value);
604
+ }
605
+ async deleteSecret(service, account) {
606
+ return this.secrets.delete(buildEntryKey(service, account));
607
+ }
608
+ async getMasterKey(context) {
609
+ const key = this.masterKeys.get(buildEntryKey(context.service, context.account));
610
+ return key ? Buffer.from(key) : null;
611
+ }
612
+ async setMasterKey(context, key) {
613
+ this.masterKeys.set(buildEntryKey(context.service, context.account), Buffer.from(key));
614
+ }
615
+ };
616
+
617
+ // src/security-store/backends/index.ts
618
+ function createSecretStoreBackend(backend = "keychain", options = {}) {
619
+ if (backend === "keychain") return new KeychainSecretStoreBackend();
620
+ if (backend === "memory") return new MemorySecretStoreBackend();
621
+ if (backend === "file") {
622
+ return new FileSecretStoreBackend({ service: options.service });
623
+ }
624
+ return backend;
625
+ }
626
+
627
+ // src/security-store/store.ts
628
+ var PREFIX = "enc:v1:";
629
+ var NONCE_LENGTH = 12;
630
+ var TAG_LENGTH = 16;
631
+ function decodeBase64Strict(input) {
632
+ const trimmed = input.trim();
633
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(trimmed) || trimmed.length % 4 !== 0) {
634
+ throw new Error("Invalid base64 in encrypted payload");
635
+ }
636
+ const decoded = Buffer.from(trimmed, "base64");
637
+ if (decoded.toString("base64") !== trimmed) {
638
+ throw new Error("Invalid base64 in encrypted payload");
639
+ }
640
+ return decoded;
641
+ }
642
+ function assertNonEmpty2(value, field) {
643
+ const normalized = value.trim();
644
+ if (normalized.length === 0) {
645
+ throw new Error(`${field} must be a non-empty string.`);
646
+ }
647
+ return normalized;
648
+ }
649
+ var CryptoManager = class {
650
+ allowPlaintextFallback;
651
+ masterKeyStore;
652
+ masterKey = null;
653
+ constructor(options) {
654
+ this.allowPlaintextFallback = options.allowPlaintextFallback !== false;
655
+ this.masterKeyStore = options.masterKeyStore ?? null;
656
+ if (options.masterKey != null) {
657
+ if (options.masterKey.length !== MASTER_KEY_LENGTH) {
658
+ throw new Error(
659
+ `masterKey must be ${MASTER_KEY_LENGTH} bytes, got ${options.masterKey.length}`
660
+ );
661
+ }
662
+ this.masterKey = Buffer.from(options.masterKey);
663
+ }
664
+ }
665
+ async init() {
666
+ if (this.masterKey != null && this.masterKey.length === MASTER_KEY_LENGTH) return;
667
+ if (this.masterKeyStore == null) {
668
+ throw new Error("CryptoManager requires either masterKey or masterKeyStore.");
669
+ }
670
+ if (this.masterKeyStore.init) await this.masterKeyStore.init();
671
+ let key = await this.masterKeyStore.get();
672
+ if (key != null) {
673
+ this.masterKey = key;
674
+ return;
675
+ }
676
+ const newKey = crypto.randomBytes(MASTER_KEY_LENGTH);
677
+ await this.masterKeyStore.set(newKey);
678
+ key = await this.masterKeyStore.get();
679
+ if (key == null || key.length !== MASTER_KEY_LENGTH) {
680
+ throw new Error("Master key write succeeded but readback failed.");
681
+ }
682
+ this.masterKey = key;
683
+ }
684
+ encrypt(plainText) {
685
+ if (this.masterKey == null) {
686
+ throw new Error("CryptoManager not initialized. Call init() first.");
687
+ }
688
+ const nonce = crypto.randomBytes(NONCE_LENGTH);
689
+ const cipher = crypto.createCipheriv("aes-256-gcm", this.masterKey, nonce);
690
+ const enc = Buffer.concat([cipher.update(plainText, "utf8"), cipher.final()]);
691
+ const tag = cipher.getAuthTag();
692
+ const payload = Buffer.concat([nonce, tag, enc]);
693
+ return PREFIX + payload.toString("base64");
694
+ }
695
+ decrypt(cipherText) {
696
+ if (this.masterKey == null) {
697
+ throw new Error("CryptoManager not initialized. Call init() first.");
698
+ }
699
+ if (!cipherText.startsWith(PREFIX)) {
700
+ if (this.allowPlaintextFallback) return cipherText;
701
+ throw new Error("Input is not an encrypted string (missing enc:v1: prefix)");
702
+ }
703
+ const payload = decodeBase64Strict(cipherText.slice(PREFIX.length));
704
+ if (payload.length < NONCE_LENGTH + TAG_LENGTH) {
705
+ throw new Error("Encrypted payload too short");
706
+ }
707
+ const nonce = payload.subarray(0, NONCE_LENGTH);
708
+ const tag = payload.subarray(NONCE_LENGTH, NONCE_LENGTH + TAG_LENGTH);
709
+ const ciphertext = payload.subarray(NONCE_LENGTH + TAG_LENGTH);
710
+ const decipher = crypto.createDecipheriv("aes-256-gcm", this.masterKey, nonce);
711
+ decipher.setAuthTag(tag);
712
+ return decipher.update(ciphertext, void 0, "utf8") + decipher.final("utf8");
713
+ }
714
+ static isEncrypted(value) {
715
+ return value.startsWith(PREFIX);
716
+ }
717
+ };
718
+ var SecretStore = class {
719
+ manager;
720
+ dataService;
721
+ backend;
722
+ initializeBackend;
723
+ initialized = false;
724
+ initializing = null;
725
+ constructor(options) {
726
+ const service = assertNonEmpty2(options.service, "service");
727
+ const masterAccount = options.masterAccount?.trim() || "master-key";
728
+ this.dataService = options.dataService?.trim() || `${service}.data`;
729
+ this.backend = createSecretStoreBackend(options.backend, { service });
730
+ const backend = this.backend;
731
+ let backendInitPromise = null;
732
+ this.initializeBackend = async () => {
733
+ if (!backend.init) return;
734
+ if (!backendInitPromise) backendInitPromise = backend.init();
735
+ await backendInitPromise;
736
+ };
737
+ const backendContext = {
738
+ service,
739
+ account: masterAccount,
740
+ appBinPath: options.appBinPath,
741
+ appleDeveloperTeamId: options.appleDeveloperTeamId,
742
+ biometric: options.biometric,
743
+ prompt: options.prompt
744
+ };
745
+ this.manager = new CryptoManager({
746
+ service,
747
+ account: masterAccount,
748
+ allowPlaintextFallback: true,
749
+ masterKey: options.masterKey,
750
+ masterKeyStore: options.masterKey ? void 0 : {
751
+ init: this.initializeBackend,
752
+ get: async () => {
753
+ if (!backend.getMasterKey) {
754
+ throw new Error(
755
+ "Secret store backend does not support master key storage. Pass masterKey or implement getMasterKey/setMasterKey."
756
+ );
757
+ }
758
+ return backend.getMasterKey(backendContext);
759
+ },
760
+ set: async (key) => {
761
+ if (!backend.setMasterKey) {
762
+ throw new Error(
763
+ "Secret store backend does not support master key storage. Pass masterKey or implement getMasterKey/setMasterKey."
764
+ );
765
+ }
766
+ await backend.setMasterKey(backendContext, key);
767
+ }
768
+ }
769
+ });
770
+ }
771
+ async init() {
772
+ if (this.initialized) return;
773
+ if (this.initializing) {
774
+ await this.initializing;
775
+ return;
776
+ }
777
+ this.initializing = (async () => {
778
+ await this.initializeBackend();
779
+ await this.manager.init();
780
+ this.initialized = true;
781
+ })();
782
+ try {
783
+ await this.initializing;
784
+ } finally {
785
+ this.initializing = null;
786
+ }
787
+ }
788
+ async set(key, value) {
789
+ await this.ensureInitialized();
790
+ const account = assertNonEmpty2(key, "key");
791
+ const ciphertext = this.manager.encrypt(value);
792
+ await this.backend.setSecret(this.dataService, account, ciphertext);
793
+ }
794
+ async get(key) {
795
+ await this.ensureInitialized();
796
+ const account = assertNonEmpty2(key, "key");
797
+ const ciphertext = await this.backend.getSecret(this.dataService, account);
798
+ if (!ciphertext) return void 0;
799
+ return this.manager.decrypt(ciphertext);
800
+ }
801
+ async delete(key) {
802
+ await this.ensureInitialized();
803
+ const account = assertNonEmpty2(key, "key");
804
+ return this.backend.deleteSecret(this.dataService, account);
805
+ }
806
+ async ensureInitialized() {
807
+ if (this.initialized) return;
808
+ await this.init();
809
+ }
810
+ };
811
+ async function createSecretStore(options) {
812
+ const store = new SecretStore(options);
813
+ await store.init();
814
+ return store;
815
+ }
816
+ export {
817
+ createSecretStore
818
+ };
819
+ //# sourceMappingURL=index.js.map