opensteer 0.6.0 → 0.6.2

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.
@@ -0,0 +1,1320 @@
1
+ // src/error-normalization.ts
2
+ function extractErrorMessage(error, fallback = "Unknown error.") {
3
+ if (error instanceof Error) {
4
+ const message = error.message.trim();
5
+ if (message) return message;
6
+ const name = error.name.trim();
7
+ if (name) return name;
8
+ }
9
+ if (typeof error === "string" && error.trim()) {
10
+ return error.trim();
11
+ }
12
+ const record = asRecord(error);
13
+ const recordMessage = toNonEmptyString(record?.message) || toNonEmptyString(record?.error);
14
+ if (recordMessage) {
15
+ return recordMessage;
16
+ }
17
+ return fallback;
18
+ }
19
+ function normalizeError(error, fallback = "Unknown error.", maxCauseDepth = 2) {
20
+ const seen = /* @__PURE__ */ new WeakSet();
21
+ return normalizeErrorInternal(error, fallback, maxCauseDepth, seen);
22
+ }
23
+ function normalizeErrorInternal(error, fallback, depthRemaining, seen) {
24
+ const record = asRecord(error);
25
+ if (record) {
26
+ if (seen.has(record)) {
27
+ return {
28
+ message: extractErrorMessage(error, fallback)
29
+ };
30
+ }
31
+ seen.add(record);
32
+ }
33
+ const message = extractErrorMessage(error, fallback);
34
+ const code = extractCode(error);
35
+ const name = extractName(error);
36
+ const details = extractDetails(error);
37
+ if (depthRemaining <= 0) {
38
+ return compactErrorInfo({
39
+ message,
40
+ ...code ? { code } : {},
41
+ ...name ? { name } : {},
42
+ ...details ? { details } : {}
43
+ });
44
+ }
45
+ const cause = extractCause(error);
46
+ if (!cause) {
47
+ return compactErrorInfo({
48
+ message,
49
+ ...code ? { code } : {},
50
+ ...name ? { name } : {},
51
+ ...details ? { details } : {}
52
+ });
53
+ }
54
+ const normalizedCause = normalizeErrorInternal(
55
+ cause,
56
+ "Caused by an unknown error.",
57
+ depthRemaining - 1,
58
+ seen
59
+ );
60
+ return compactErrorInfo({
61
+ message,
62
+ ...code ? { code } : {},
63
+ ...name ? { name } : {},
64
+ ...details ? { details } : {},
65
+ cause: normalizedCause
66
+ });
67
+ }
68
+ function compactErrorInfo(info) {
69
+ const safeDetails = toJsonSafeRecord(info.details);
70
+ return {
71
+ message: info.message,
72
+ ...info.code ? { code: info.code } : {},
73
+ ...info.name ? { name: info.name } : {},
74
+ ...safeDetails ? { details: safeDetails } : {},
75
+ ...info.cause ? { cause: info.cause } : {}
76
+ };
77
+ }
78
+ function extractCode(error) {
79
+ const record = asRecord(error);
80
+ const raw = record?.code;
81
+ if (typeof raw === "string" && raw.trim()) {
82
+ return raw.trim();
83
+ }
84
+ if (typeof raw === "number" && Number.isFinite(raw)) {
85
+ return String(raw);
86
+ }
87
+ return void 0;
88
+ }
89
+ function extractName(error) {
90
+ if (error instanceof Error && error.name.trim()) {
91
+ return error.name.trim();
92
+ }
93
+ const record = asRecord(error);
94
+ return toNonEmptyString(record?.name);
95
+ }
96
+ function extractDetails(error) {
97
+ const record = asRecord(error);
98
+ if (!record) return void 0;
99
+ const details = {};
100
+ const rawDetails = asRecord(record.details);
101
+ if (rawDetails) {
102
+ Object.assign(details, rawDetails);
103
+ }
104
+ const action = toNonEmptyString(record.action);
105
+ if (action) {
106
+ details.action = action;
107
+ }
108
+ const selectorUsed = toNonEmptyString(record.selectorUsed);
109
+ if (selectorUsed) {
110
+ details.selectorUsed = selectorUsed;
111
+ }
112
+ if (typeof record.status === "number" && Number.isFinite(record.status)) {
113
+ details.status = record.status;
114
+ }
115
+ const failure = asRecord(record.failure);
116
+ if (failure) {
117
+ const failureCode = toNonEmptyString(failure.code);
118
+ const classificationSource = toNonEmptyString(
119
+ failure.classificationSource
120
+ );
121
+ const failureDetails = asRecord(failure.details);
122
+ if (failureCode || classificationSource || failureDetails) {
123
+ details.actionFailure = {
124
+ ...failureCode ? { code: failureCode } : {},
125
+ ...classificationSource ? { classificationSource } : {},
126
+ ...failureDetails ? { details: failureDetails } : {}
127
+ };
128
+ }
129
+ }
130
+ return Object.keys(details).length ? details : void 0;
131
+ }
132
+ function extractCause(error) {
133
+ if (error instanceof Error) {
134
+ return error.cause;
135
+ }
136
+ const record = asRecord(error);
137
+ return record?.cause;
138
+ }
139
+ function asRecord(value) {
140
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
141
+ return null;
142
+ }
143
+ return value;
144
+ }
145
+ function toNonEmptyString(value) {
146
+ if (typeof value !== "string") return void 0;
147
+ const normalized = value.trim();
148
+ return normalized.length ? normalized : void 0;
149
+ }
150
+ function toJsonSafeRecord(value) {
151
+ if (!value) return void 0;
152
+ const sanitized = toJsonSafeValue(value, /* @__PURE__ */ new WeakSet());
153
+ if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) {
154
+ return void 0;
155
+ }
156
+ const record = sanitized;
157
+ return Object.keys(record).length > 0 ? record : void 0;
158
+ }
159
+ function toJsonSafeValue(value, seen) {
160
+ if (value === null) return null;
161
+ if (typeof value === "string" || typeof value === "boolean") {
162
+ return value;
163
+ }
164
+ if (typeof value === "number") {
165
+ return Number.isFinite(value) ? value : null;
166
+ }
167
+ if (typeof value === "bigint") {
168
+ return value.toString();
169
+ }
170
+ if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
171
+ return void 0;
172
+ }
173
+ if (value instanceof Date) {
174
+ return Number.isNaN(value.getTime()) ? null : value.toISOString();
175
+ }
176
+ if (Array.isArray(value)) {
177
+ if (seen.has(value)) return "[Circular]";
178
+ seen.add(value);
179
+ const output = value.map((item) => {
180
+ const next = toJsonSafeValue(item, seen);
181
+ return next === void 0 ? null : next;
182
+ });
183
+ seen.delete(value);
184
+ return output;
185
+ }
186
+ if (value instanceof Set) {
187
+ if (seen.has(value)) return "[Circular]";
188
+ seen.add(value);
189
+ const output = Array.from(value, (item) => {
190
+ const next = toJsonSafeValue(item, seen);
191
+ return next === void 0 ? null : next;
192
+ });
193
+ seen.delete(value);
194
+ return output;
195
+ }
196
+ if (value instanceof Map) {
197
+ if (seen.has(value)) return "[Circular]";
198
+ seen.add(value);
199
+ const output = {};
200
+ for (const [key, item] of value.entries()) {
201
+ const normalizedKey = String(key);
202
+ const next = toJsonSafeValue(item, seen);
203
+ if (next !== void 0) {
204
+ output[normalizedKey] = next;
205
+ }
206
+ }
207
+ seen.delete(value);
208
+ return output;
209
+ }
210
+ if (typeof value === "object") {
211
+ const objectValue = value;
212
+ if (seen.has(objectValue)) return "[Circular]";
213
+ seen.add(objectValue);
214
+ const output = {};
215
+ for (const [key, item] of Object.entries(objectValue)) {
216
+ const next = toJsonSafeValue(item, seen);
217
+ if (next !== void 0) {
218
+ output[key] = next;
219
+ }
220
+ }
221
+ seen.delete(objectValue);
222
+ return output;
223
+ }
224
+ return void 0;
225
+ }
226
+
227
+ // src/storage/namespace.ts
228
+ import path from "path";
229
+ var DEFAULT_NAMESPACE = "default";
230
+ function normalizeNamespace(input) {
231
+ const raw = String(input || "").trim().replace(/\\/g, "/");
232
+ if (!raw) return DEFAULT_NAMESPACE;
233
+ const segments = raw.split("/").map((segment) => sanitizeNamespaceSegment(segment)).filter((segment) => Boolean(segment));
234
+ if (!segments.length) return DEFAULT_NAMESPACE;
235
+ return segments.join("/");
236
+ }
237
+ function resolveNamespaceDir(rootDir, namespace) {
238
+ const selectorsRoot = path.resolve(rootDir, ".opensteer", "selectors");
239
+ const normalizedNamespace = normalizeNamespace(namespace);
240
+ const namespaceDir = path.resolve(selectorsRoot, normalizedNamespace);
241
+ const relative = path.relative(selectorsRoot, namespaceDir);
242
+ if (relative === "" || relative === ".") return namespaceDir;
243
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
244
+ throw new Error(
245
+ `Namespace "${namespace}" resolves outside selectors root.`
246
+ );
247
+ }
248
+ return namespaceDir;
249
+ }
250
+ function sanitizeNamespaceSegment(segment) {
251
+ const trimmed = String(segment || "").trim();
252
+ if (!trimmed || trimmed === "." || trimmed === "..") return "";
253
+ const replaced = trimmed.replace(/[^a-zA-Z0-9_-]+/g, "_");
254
+ const collapsed = replaced.replace(/_+/g, "_");
255
+ const bounded = collapsed.replace(/^_+|_+$/g, "");
256
+ return bounded || "";
257
+ }
258
+
259
+ // src/config.ts
260
+ import fs from "fs";
261
+ import path2 from "path";
262
+ import { fileURLToPath } from "url";
263
+ import { parse as parseDotenv } from "dotenv";
264
+ var DEFAULT_CONFIG = {
265
+ browser: {
266
+ headless: false,
267
+ executablePath: void 0,
268
+ slowMo: 0,
269
+ connectUrl: void 0,
270
+ channel: void 0,
271
+ profileDir: void 0
272
+ },
273
+ storage: {
274
+ rootDir: process.cwd()
275
+ },
276
+ cursor: {
277
+ enabled: false,
278
+ profile: "snappy"
279
+ },
280
+ model: "gpt-5.1",
281
+ debug: false
282
+ };
283
+ function dotenvFileOrder(nodeEnv) {
284
+ const normalized = nodeEnv?.trim() || "";
285
+ const files = [];
286
+ if (normalized) {
287
+ files.push(`.env.${normalized}.local`);
288
+ }
289
+ if (normalized !== "test") {
290
+ files.push(".env.local");
291
+ }
292
+ if (normalized) {
293
+ files.push(`.env.${normalized}`);
294
+ }
295
+ files.push(".env");
296
+ return files;
297
+ }
298
+ function loadDotenvValues(rootDir, baseEnv, options = {}) {
299
+ const values = {};
300
+ if (parseBool(baseEnv.OPENSTEER_DISABLE_DOTENV_AUTOLOAD) === true) {
301
+ return values;
302
+ }
303
+ const debug = options.debug ?? parseBool(baseEnv.OPENSTEER_DEBUG) === true;
304
+ const baseDir = path2.resolve(rootDir);
305
+ const nodeEnv = baseEnv.NODE_ENV?.trim() || "";
306
+ for (const filename of dotenvFileOrder(nodeEnv)) {
307
+ const filePath = path2.join(baseDir, filename);
308
+ if (!fs.existsSync(filePath)) continue;
309
+ try {
310
+ const raw = fs.readFileSync(filePath, "utf8");
311
+ const parsed = parseDotenv(raw);
312
+ for (const [key, value] of Object.entries(parsed)) {
313
+ if (values[key] === void 0) {
314
+ values[key] = value;
315
+ }
316
+ }
317
+ } catch (error) {
318
+ const message = extractErrorMessage(
319
+ error,
320
+ "Unable to read or parse dotenv file."
321
+ );
322
+ if (debug) {
323
+ console.warn(
324
+ `[opensteer] failed to load dotenv file "${filePath}": ${message}`
325
+ );
326
+ }
327
+ continue;
328
+ }
329
+ }
330
+ return values;
331
+ }
332
+ function resolveEnv(rootDir, options = {}) {
333
+ const baseEnv = options.baseEnv ?? process.env;
334
+ const dotenvValues = loadDotenvValues(rootDir, baseEnv, options);
335
+ return {
336
+ ...dotenvValues,
337
+ ...baseEnv
338
+ };
339
+ }
340
+ function hasOwn(config, key) {
341
+ if (!config || typeof config !== "object") return false;
342
+ return Object.prototype.hasOwnProperty.call(config, key);
343
+ }
344
+ function hasLegacyAiConfig(config) {
345
+ return hasOwn(config, "ai");
346
+ }
347
+ function assertNoLegacyAiConfig(source, config) {
348
+ if (hasLegacyAiConfig(config)) {
349
+ throw new Error(
350
+ `Legacy "ai" config is no longer supported in ${source}. Use top-level "model" instead.`
351
+ );
352
+ }
353
+ }
354
+ function assertNoLegacyRuntimeConfig(source, config) {
355
+ if (!config || typeof config !== "object") return;
356
+ const configRecord = config;
357
+ if (hasOwn(configRecord, "runtime")) {
358
+ throw new Error(
359
+ `Legacy "runtime" config is no longer supported in ${source}. Use top-level "cloud" instead.`
360
+ );
361
+ }
362
+ if (hasOwn(configRecord, "mode")) {
363
+ throw new Error(
364
+ `Top-level "mode" config is no longer supported in ${source}. Use "cloud: true" to enable cloud mode.`
365
+ );
366
+ }
367
+ if (hasOwn(configRecord, "remote")) {
368
+ throw new Error(
369
+ `Top-level "remote" config is no longer supported in ${source}. Use "cloud" options instead.`
370
+ );
371
+ }
372
+ if (hasOwn(configRecord, "apiKey")) {
373
+ throw new Error(
374
+ `Top-level "apiKey" config is not supported in ${source}. Use "cloud.apiKey" instead.`
375
+ );
376
+ }
377
+ }
378
+ function loadConfigFile(rootDir, options = {}) {
379
+ const configPath = path2.join(rootDir, ".opensteer", "config.json");
380
+ if (!fs.existsSync(configPath)) return {};
381
+ try {
382
+ const raw = fs.readFileSync(configPath, "utf8");
383
+ return JSON.parse(raw);
384
+ } catch (error) {
385
+ const message = extractErrorMessage(
386
+ error,
387
+ "Unable to read or parse config file."
388
+ );
389
+ if (options.debug) {
390
+ console.warn(
391
+ `[opensteer] failed to load config file "${configPath}": ${message}`
392
+ );
393
+ }
394
+ return {};
395
+ }
396
+ }
397
+ function mergeDeep(base, patch) {
398
+ const out = {
399
+ ...base
400
+ };
401
+ for (const [key, value] of Object.entries(patch || {})) {
402
+ const currentValue = out[key];
403
+ if (value && typeof value === "object" && !Array.isArray(value) && currentValue && typeof currentValue === "object" && !Array.isArray(currentValue)) {
404
+ out[key] = mergeDeep(
405
+ currentValue,
406
+ value
407
+ );
408
+ continue;
409
+ }
410
+ if (value !== void 0) {
411
+ out[key] = value;
412
+ }
413
+ }
414
+ return out;
415
+ }
416
+ function parseBool(value) {
417
+ if (value == null) return void 0;
418
+ const normalized = value.trim().toLowerCase();
419
+ if (normalized === "true" || normalized === "1") return true;
420
+ if (normalized === "false" || normalized === "0") return false;
421
+ return void 0;
422
+ }
423
+ function parseNumber(value) {
424
+ if (value == null || value.trim() === "") return void 0;
425
+ const parsed = Number(value);
426
+ if (!Number.isFinite(parsed)) return void 0;
427
+ return parsed;
428
+ }
429
+ function parseRuntimeMode(value, source) {
430
+ if (value == null) return void 0;
431
+ if (typeof value !== "string") {
432
+ throw new Error(
433
+ `Invalid ${source} value "${String(value)}". Use "local" or "cloud".`
434
+ );
435
+ }
436
+ const normalized = value.trim().toLowerCase();
437
+ if (!normalized) return void 0;
438
+ if (normalized === "local" || normalized === "cloud") {
439
+ return normalized;
440
+ }
441
+ throw new Error(
442
+ `Invalid ${source} value "${value}". Use "local" or "cloud".`
443
+ );
444
+ }
445
+ function parseAuthScheme(value, source) {
446
+ if (value == null) return void 0;
447
+ if (typeof value !== "string") {
448
+ throw new Error(
449
+ `Invalid ${source} value "${String(value)}". Use "api-key" or "bearer".`
450
+ );
451
+ }
452
+ const normalized = value.trim().toLowerCase();
453
+ if (!normalized) return void 0;
454
+ if (normalized === "api-key" || normalized === "bearer") {
455
+ return normalized;
456
+ }
457
+ throw new Error(
458
+ `Invalid ${source} value "${value}". Use "api-key" or "bearer".`
459
+ );
460
+ }
461
+ function parseCloudAnnounce(value, source) {
462
+ if (value == null) return void 0;
463
+ if (typeof value !== "string") {
464
+ throw new Error(
465
+ `Invalid ${source} value "${String(value)}". Use "always", "off", or "tty".`
466
+ );
467
+ }
468
+ const normalized = value.trim().toLowerCase();
469
+ if (!normalized) return void 0;
470
+ if (normalized === "always" || normalized === "off" || normalized === "tty") {
471
+ return normalized;
472
+ }
473
+ throw new Error(
474
+ `Invalid ${source} value "${value}". Use "always", "off", or "tty".`
475
+ );
476
+ }
477
+ function resolveOpensteerApiKey(env) {
478
+ const value = env.OPENSTEER_API_KEY?.trim();
479
+ if (!value) return void 0;
480
+ return value;
481
+ }
482
+ function resolveOpensteerAccessToken(env) {
483
+ const value = env.OPENSTEER_ACCESS_TOKEN?.trim();
484
+ if (!value) return void 0;
485
+ return value;
486
+ }
487
+ function resolveOpensteerBaseUrl(env) {
488
+ const value = env.OPENSTEER_BASE_URL?.trim();
489
+ if (!value) return void 0;
490
+ return value;
491
+ }
492
+ function resolveOpensteerAuthScheme(env) {
493
+ return parseAuthScheme(env.OPENSTEER_AUTH_SCHEME, "OPENSTEER_AUTH_SCHEME");
494
+ }
495
+ function resolveOpensteerCloudProfileId(env) {
496
+ const value = env.OPENSTEER_CLOUD_PROFILE_ID?.trim();
497
+ if (!value) return void 0;
498
+ return value;
499
+ }
500
+ function resolveOpensteerCloudProfileReuseIfActive(env) {
501
+ return parseBool(env.OPENSTEER_CLOUD_PROFILE_REUSE_IF_ACTIVE);
502
+ }
503
+ function parseCloudBrowserProfileReuseIfActive(value) {
504
+ if (value == null) return void 0;
505
+ if (typeof value !== "boolean") {
506
+ throw new Error(
507
+ `Invalid cloud.browserProfile.reuseIfActive value "${String(
508
+ value
509
+ )}". Use true or false.`
510
+ );
511
+ }
512
+ return value;
513
+ }
514
+ function normalizeCloudBrowserProfileOptions(value, source) {
515
+ if (value == null) {
516
+ return void 0;
517
+ }
518
+ if (typeof value !== "object" || Array.isArray(value)) {
519
+ throw new Error(
520
+ `Invalid ${source} value "${String(value)}". Use an object with profileId and optional reuseIfActive.`
521
+ );
522
+ }
523
+ const record = value;
524
+ const rawProfileId = record.profileId;
525
+ if (typeof rawProfileId !== "string" || !rawProfileId.trim()) {
526
+ throw new Error(
527
+ `${source}.profileId must be a non-empty string when browserProfile is provided.`
528
+ );
529
+ }
530
+ return {
531
+ profileId: rawProfileId.trim(),
532
+ reuseIfActive: parseCloudBrowserProfileReuseIfActive(record.reuseIfActive)
533
+ };
534
+ }
535
+ function resolveEnvCloudBrowserProfile(profileId, reuseIfActive) {
536
+ if (reuseIfActive !== void 0 && !profileId) {
537
+ throw new Error(
538
+ "OPENSTEER_CLOUD_PROFILE_REUSE_IF_ACTIVE requires OPENSTEER_CLOUD_PROFILE_ID."
539
+ );
540
+ }
541
+ if (!profileId) {
542
+ return void 0;
543
+ }
544
+ return {
545
+ profileId,
546
+ reuseIfActive
547
+ };
548
+ }
549
+ function normalizeCloudOptions(value) {
550
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
551
+ return void 0;
552
+ }
553
+ return value;
554
+ }
555
+ function normalizeNonEmptyString(value) {
556
+ if (typeof value !== "string") return void 0;
557
+ const normalized = value.trim();
558
+ return normalized.length ? normalized : void 0;
559
+ }
560
+ function parseCloudEnabled(value, source) {
561
+ if (value == null) return void 0;
562
+ if (typeof value === "boolean") return value;
563
+ if (typeof value === "object" && !Array.isArray(value)) return true;
564
+ throw new Error(
565
+ `Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
566
+ );
567
+ }
568
+ function resolveCloudSelection(config, env = process.env) {
569
+ const configCloud = parseCloudEnabled(config.cloud, "cloud");
570
+ if (configCloud !== void 0) {
571
+ return {
572
+ cloud: configCloud,
573
+ source: "config.cloud"
574
+ };
575
+ }
576
+ const envMode = parseRuntimeMode(env.OPENSTEER_MODE, "OPENSTEER_MODE");
577
+ if (envMode) {
578
+ return {
579
+ cloud: envMode === "cloud",
580
+ source: "env.OPENSTEER_MODE"
581
+ };
582
+ }
583
+ return {
584
+ cloud: false,
585
+ source: "default"
586
+ };
587
+ }
588
+ function resolveConfigWithEnv(input = {}, options = {}) {
589
+ const processEnv = options.env ?? process.env;
590
+ const debugHint = typeof input.debug === "boolean" ? input.debug : parseBool(processEnv.OPENSTEER_DEBUG) === true;
591
+ const initialRootDir = input.storage?.rootDir ?? process.cwd();
592
+ const runtimeDefaults = mergeDeep(DEFAULT_CONFIG, {
593
+ storage: {
594
+ rootDir: initialRootDir
595
+ }
596
+ });
597
+ assertNoLegacyAiConfig("Opensteer constructor config", input);
598
+ assertNoLegacyRuntimeConfig("Opensteer constructor config", input);
599
+ const fileConfig = loadConfigFile(initialRootDir, {
600
+ debug: debugHint
601
+ });
602
+ assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
603
+ assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
604
+ const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
605
+ const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
606
+ const env = resolveEnv(envRootDir, {
607
+ debug: debugHint,
608
+ baseEnv: processEnv
609
+ });
610
+ if (env.OPENSTEER_AI_MODEL) {
611
+ throw new Error(
612
+ "OPENSTEER_AI_MODEL is no longer supported. Use OPENSTEER_MODEL instead."
613
+ );
614
+ }
615
+ if (env.OPENSTEER_RUNTIME != null) {
616
+ throw new Error(
617
+ "OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
618
+ );
619
+ }
620
+ const envConfig = {
621
+ browser: {
622
+ headless: parseBool(env.OPENSTEER_HEADLESS),
623
+ executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
624
+ slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
625
+ connectUrl: env.OPENSTEER_CONNECT_URL || void 0,
626
+ channel: env.OPENSTEER_CHANNEL || void 0,
627
+ profileDir: env.OPENSTEER_PROFILE_DIR || void 0
628
+ },
629
+ cursor: {
630
+ enabled: parseBool(env.OPENSTEER_CURSOR)
631
+ },
632
+ model: env.OPENSTEER_MODEL || void 0,
633
+ debug: parseBool(env.OPENSTEER_DEBUG)
634
+ };
635
+ const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
636
+ const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
637
+ const resolved = mergeDeep(mergedWithEnv, input);
638
+ const envApiKey = resolveOpensteerApiKey(env);
639
+ const envAccessTokenRaw = resolveOpensteerAccessToken(env);
640
+ const envBaseUrl = resolveOpensteerBaseUrl(env);
641
+ const envAuthScheme = resolveOpensteerAuthScheme(env);
642
+ if (envApiKey && envAccessTokenRaw) {
643
+ throw new Error(
644
+ "OPENSTEER_API_KEY and OPENSTEER_ACCESS_TOKEN are mutually exclusive. Set only one."
645
+ );
646
+ }
647
+ const envAccessToken = envAccessTokenRaw || (envAuthScheme === "bearer" ? envApiKey : void 0);
648
+ const envApiCredential = envAuthScheme === "bearer" && !envAccessTokenRaw ? void 0 : envApiKey;
649
+ const envCloudProfileId = resolveOpensteerCloudProfileId(env);
650
+ const envCloudProfileReuseIfActive = resolveOpensteerCloudProfileReuseIfActive(env);
651
+ const envCloudAnnounce = parseCloudAnnounce(
652
+ env.OPENSTEER_REMOTE_ANNOUNCE,
653
+ "OPENSTEER_REMOTE_ANNOUNCE"
654
+ );
655
+ const inputCloudOptions = normalizeCloudOptions(input.cloud);
656
+ const inputCloudBrowserProfile = normalizeCloudBrowserProfileOptions(
657
+ inputCloudOptions?.browserProfile,
658
+ "cloud.browserProfile"
659
+ );
660
+ const inputAuthScheme = parseAuthScheme(
661
+ inputCloudOptions?.authScheme,
662
+ "cloud.authScheme"
663
+ );
664
+ const inputCloudAnnounce = parseCloudAnnounce(
665
+ inputCloudOptions?.announce,
666
+ "cloud.announce"
667
+ );
668
+ const inputHasCloudApiKey = Boolean(
669
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "apiKey")
670
+ );
671
+ const inputHasCloudAccessToken = Boolean(
672
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "accessToken")
673
+ );
674
+ const inputHasCloudBaseUrl = Boolean(
675
+ inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
676
+ );
677
+ if (normalizeNonEmptyString(inputCloudOptions?.apiKey) && normalizeNonEmptyString(inputCloudOptions?.accessToken)) {
678
+ throw new Error(
679
+ "cloud.apiKey and cloud.accessToken are mutually exclusive. Set only one."
680
+ );
681
+ }
682
+ const cloudSelection = resolveCloudSelection({
683
+ cloud: resolved.cloud
684
+ }, env);
685
+ if (cloudSelection.cloud) {
686
+ const resolvedCloud = normalizeCloudOptions(resolved.cloud) ?? {};
687
+ const {
688
+ apiKey: resolvedCloudApiKeyRaw,
689
+ accessToken: resolvedCloudAccessTokenRaw,
690
+ ...resolvedCloudRest
691
+ } = resolvedCloud;
692
+ if (normalizeNonEmptyString(resolvedCloudApiKeyRaw) && normalizeNonEmptyString(resolvedCloudAccessTokenRaw)) {
693
+ throw new Error(
694
+ "Cloud config cannot include both apiKey and accessToken at the same time."
695
+ );
696
+ }
697
+ const resolvedCloudBrowserProfile = normalizeCloudBrowserProfileOptions(
698
+ resolvedCloud.browserProfile,
699
+ "resolved.cloud.browserProfile"
700
+ );
701
+ const envCloudBrowserProfile = resolveEnvCloudBrowserProfile(
702
+ envCloudProfileId,
703
+ envCloudProfileReuseIfActive
704
+ );
705
+ const browserProfile = inputCloudBrowserProfile ?? envCloudBrowserProfile ?? resolvedCloudBrowserProfile;
706
+ let authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
707
+ const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
708
+ const credentialOverriddenByInput = inputHasCloudApiKey || inputHasCloudAccessToken;
709
+ let apiKey = normalizeNonEmptyString(resolvedCloudApiKeyRaw);
710
+ let accessToken = normalizeNonEmptyString(resolvedCloudAccessTokenRaw);
711
+ if (!credentialOverriddenByInput) {
712
+ if (envAccessToken) {
713
+ accessToken = envAccessToken;
714
+ apiKey = void 0;
715
+ } else if (envApiCredential) {
716
+ apiKey = envApiCredential;
717
+ accessToken = void 0;
718
+ }
719
+ }
720
+ if (accessToken) {
721
+ authScheme = "bearer";
722
+ }
723
+ resolved.cloud = {
724
+ ...resolvedCloudRest,
725
+ ...inputHasCloudApiKey ? { apiKey: resolvedCloudApiKeyRaw } : apiKey ? { apiKey } : {},
726
+ ...inputHasCloudAccessToken ? { accessToken: resolvedCloudAccessTokenRaw } : accessToken ? { accessToken } : {},
727
+ authScheme,
728
+ announce,
729
+ ...browserProfile ? { browserProfile } : {}
730
+ };
731
+ }
732
+ if (envBaseUrl && cloudSelection.cloud && !inputHasCloudBaseUrl) {
733
+ resolved.cloud = {
734
+ ...normalizeCloudOptions(resolved.cloud) ?? {},
735
+ baseUrl: envBaseUrl
736
+ };
737
+ }
738
+ return {
739
+ config: resolved,
740
+ env
741
+ };
742
+ }
743
+ function resolveNamespace(config, rootDir) {
744
+ if (config.name && config.name.trim()) {
745
+ return normalizeNamespace(config.name);
746
+ }
747
+ const caller = getCallerFilePath();
748
+ if (!caller) return normalizeNamespace("default");
749
+ const relative = path2.relative(rootDir, caller);
750
+ const cleaned = relative.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
751
+ return normalizeNamespace(cleaned || "default");
752
+ }
753
+ function getCallerFilePath() {
754
+ const stack = new Error().stack;
755
+ if (!stack) return null;
756
+ const lines = stack.split("\n").slice(2);
757
+ for (const line of lines) {
758
+ const match = line.match(/\((.*):(\d+):(\d+)\)/) || line.match(/at\s+(.*):(\d+):(\d+)/);
759
+ if (!match) continue;
760
+ const rawPath = match[1];
761
+ if (!rawPath) continue;
762
+ if (rawPath.includes("node:internal")) continue;
763
+ if (rawPath.includes("node_modules")) continue;
764
+ if (rawPath.includes("/opensteer-oss/src/")) continue;
765
+ try {
766
+ if (rawPath.startsWith("file://")) {
767
+ return fileURLToPath(rawPath);
768
+ }
769
+ return rawPath;
770
+ } catch {
771
+ continue;
772
+ }
773
+ }
774
+ return null;
775
+ }
776
+
777
+ // src/cloud/errors.ts
778
+ var OpensteerCloudError = class extends Error {
779
+ code;
780
+ status;
781
+ details;
782
+ constructor(code, message, status, details) {
783
+ super(message);
784
+ this.name = "OpensteerCloudError";
785
+ this.code = code;
786
+ this.status = status;
787
+ this.details = details;
788
+ }
789
+ };
790
+ function cloudUnsupportedMethodError(method, message) {
791
+ return new OpensteerCloudError(
792
+ "CLOUD_UNSUPPORTED_METHOD",
793
+ message || `${method} is not supported in cloud mode.`
794
+ );
795
+ }
796
+ function cloudNotLaunchedError() {
797
+ return new OpensteerCloudError(
798
+ "CLOUD_SESSION_NOT_FOUND",
799
+ "Cloud session is not connected. Call launch() first."
800
+ );
801
+ }
802
+
803
+ // src/cloud/cdp-client.ts
804
+ import {
805
+ chromium
806
+ } from "playwright";
807
+ var CloudCdpClient = class {
808
+ async connect(args) {
809
+ const endpoint = withTokenQuery(args.wsUrl, args.token);
810
+ let browser;
811
+ try {
812
+ browser = await chromium.connectOverCDP(endpoint);
813
+ } catch (error) {
814
+ const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
815
+ throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
816
+ }
817
+ const contexts = browser.contexts();
818
+ const context = contexts[0];
819
+ if (!context) {
820
+ await browser.close();
821
+ throw new OpensteerCloudError(
822
+ "CLOUD_INTERNAL",
823
+ "Cloud browser returned no context."
824
+ );
825
+ }
826
+ const preferred = selectPreferredContextPage(browser, contexts);
827
+ if (preferred) {
828
+ return preferred;
829
+ }
830
+ const page = context.pages()[0] || await context.newPage();
831
+ return { browser, context, page };
832
+ }
833
+ };
834
+ function selectPreferredContextPage(browser, contexts) {
835
+ let aboutBlankCandidate = null;
836
+ for (const context of contexts) {
837
+ for (const page of context.pages()) {
838
+ const url = safePageUrl(page);
839
+ if (!isInternalOrEmptyUrl(url)) {
840
+ return { browser, context, page };
841
+ }
842
+ if (!aboutBlankCandidate && url === "about:blank") {
843
+ aboutBlankCandidate = { browser, context, page };
844
+ }
845
+ }
846
+ }
847
+ return aboutBlankCandidate;
848
+ }
849
+ function safePageUrl(page) {
850
+ try {
851
+ return page.url();
852
+ } catch {
853
+ return "";
854
+ }
855
+ }
856
+ function isInternalOrEmptyUrl(url) {
857
+ if (!url) return true;
858
+ if (url === "about:blank") return true;
859
+ return url.startsWith("chrome://") || url.startsWith("devtools://") || url.startsWith("edge://");
860
+ }
861
+ function withTokenQuery(wsUrl, token) {
862
+ const url = new URL(wsUrl);
863
+ url.searchParams.set("token", token);
864
+ return url.toString();
865
+ }
866
+
867
+ // src/utils/strip-trailing-slashes.ts
868
+ function stripTrailingSlashes(value) {
869
+ let end = value.length;
870
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
871
+ end -= 1;
872
+ }
873
+ return end === value.length ? value : value.slice(0, end);
874
+ }
875
+
876
+ // src/cloud/http-client.ts
877
+ function normalizeCloudBaseUrl(baseUrl) {
878
+ return stripTrailingSlashes(baseUrl);
879
+ }
880
+ function cloudAuthHeaders(key, authScheme) {
881
+ if (authScheme === "bearer") {
882
+ return {
883
+ authorization: `Bearer ${key}`
884
+ };
885
+ }
886
+ return {
887
+ "x-api-key": key
888
+ };
889
+ }
890
+ async function parseCloudHttpError(response) {
891
+ let body = null;
892
+ try {
893
+ body = await response.json();
894
+ } catch {
895
+ body = null;
896
+ }
897
+ const code = typeof body?.code === "string" ? toCloudErrorCode(body.code) : "CLOUD_TRANSPORT_ERROR";
898
+ const message = typeof body?.error === "string" ? body.error : `Cloud request failed with status ${response.status}.`;
899
+ return new OpensteerCloudError(code, message, response.status, body?.details);
900
+ }
901
+ function toCloudErrorCode(code) {
902
+ if (code === "CLOUD_AUTH_FAILED" || code === "CLOUD_SESSION_NOT_FOUND" || code === "CLOUD_SESSION_CLOSED" || code === "CLOUD_UNSUPPORTED_METHOD" || code === "CLOUD_INVALID_REQUEST" || code === "CLOUD_MODEL_NOT_ALLOWED" || code === "CLOUD_ACTION_FAILED" || code === "CLOUD_INTERNAL" || code === "CLOUD_CAPACITY_EXHAUSTED" || code === "CLOUD_RUNTIME_UNAVAILABLE" || code === "CLOUD_RUNTIME_MISMATCH" || code === "CLOUD_SESSION_STALE" || code === "CLOUD_CONTRACT_MISMATCH" || code === "CLOUD_CONTROL_PLANE_ERROR" || code === "CLOUD_PROXY_UNAVAILABLE" || code === "CLOUD_PROXY_REQUIRED" || code === "CLOUD_BILLING_LIMIT_REACHED" || code === "CLOUD_RATE_LIMITED" || code === "CLOUD_BROWSER_PROFILE_NOT_FOUND" || code === "CLOUD_BROWSER_PROFILE_BUSY" || code === "CLOUD_BROWSER_PROFILE_DISABLED" || code === "CLOUD_BROWSER_PROFILE_PROXY_UNAVAILABLE" || code === "CLOUD_BROWSER_PROFILE_SYNC_FAILED") {
903
+ return code;
904
+ }
905
+ return "CLOUD_TRANSPORT_ERROR";
906
+ }
907
+
908
+ // src/cloud/session-client.ts
909
+ var CACHE_IMPORT_BATCH_SIZE = 200;
910
+ var CloudSessionClient = class {
911
+ baseUrl;
912
+ key;
913
+ authScheme;
914
+ constructor(baseUrl, key, authScheme = "api-key") {
915
+ this.baseUrl = normalizeCloudBaseUrl(baseUrl);
916
+ this.key = key;
917
+ this.authScheme = authScheme;
918
+ }
919
+ async create(request) {
920
+ const response = await fetch(`${this.baseUrl}/sessions`, {
921
+ method: "POST",
922
+ headers: {
923
+ "content-type": "application/json",
924
+ ...cloudAuthHeaders(this.key, this.authScheme)
925
+ },
926
+ body: JSON.stringify(request)
927
+ });
928
+ if (!response.ok) {
929
+ throw await parseCloudHttpError(response);
930
+ }
931
+ let body;
932
+ try {
933
+ body = await response.json();
934
+ } catch {
935
+ throw new OpensteerCloudError(
936
+ "CLOUD_CONTRACT_MISMATCH",
937
+ "Invalid cloud session create response: expected a JSON object.",
938
+ response.status
939
+ );
940
+ }
941
+ return parseCreateResponse(body, response.status);
942
+ }
943
+ async close(sessionId) {
944
+ const response = await fetch(`${this.baseUrl}/sessions/${sessionId}`, {
945
+ method: "DELETE",
946
+ headers: {
947
+ ...cloudAuthHeaders(this.key, this.authScheme)
948
+ }
949
+ });
950
+ if (response.status === 204) {
951
+ return;
952
+ }
953
+ if (!response.ok) {
954
+ throw await parseCloudHttpError(response);
955
+ }
956
+ }
957
+ async importSelectorCache(request) {
958
+ if (!request.entries.length) {
959
+ return zeroImportResponse();
960
+ }
961
+ let totals = zeroImportResponse();
962
+ for (let offset = 0; offset < request.entries.length; offset += CACHE_IMPORT_BATCH_SIZE) {
963
+ const batch = request.entries.slice(
964
+ offset,
965
+ offset + CACHE_IMPORT_BATCH_SIZE
966
+ );
967
+ const response = await this.importSelectorCacheBatch(batch);
968
+ totals = mergeImportResponse(totals, response);
969
+ }
970
+ return totals;
971
+ }
972
+ async importSelectorCacheBatch(entries) {
973
+ const response = await fetch(`${this.baseUrl}/selector-cache/import`, {
974
+ method: "POST",
975
+ headers: {
976
+ "content-type": "application/json",
977
+ ...cloudAuthHeaders(this.key, this.authScheme)
978
+ },
979
+ body: JSON.stringify({ entries })
980
+ });
981
+ if (!response.ok) {
982
+ throw await parseCloudHttpError(response);
983
+ }
984
+ return await response.json();
985
+ }
986
+ };
987
+ function parseCreateResponse(body, status) {
988
+ const root = requireObject(
989
+ body,
990
+ "Invalid cloud session create response: expected a JSON object.",
991
+ status
992
+ );
993
+ const sessionId = requireString(root, "sessionId", status);
994
+ const actionWsUrl = requireString(root, "actionWsUrl", status);
995
+ const cdpWsUrl = requireString(root, "cdpWsUrl", status);
996
+ const actionToken = requireString(root, "actionToken", status);
997
+ const cdpToken = requireString(root, "cdpToken", status);
998
+ const cloudSessionUrl = requireString(root, "cloudSessionUrl", status);
999
+ const cloudSessionRoot = requireObject(
1000
+ root.cloudSession,
1001
+ "Invalid cloud session create response: cloudSession must be an object.",
1002
+ status
1003
+ );
1004
+ const cloudSession = {
1005
+ sessionId: requireString(cloudSessionRoot, "sessionId", status, "cloudSession"),
1006
+ workspaceId: requireString(
1007
+ cloudSessionRoot,
1008
+ "workspaceId",
1009
+ status,
1010
+ "cloudSession"
1011
+ ),
1012
+ state: requireString(cloudSessionRoot, "state", status, "cloudSession"),
1013
+ createdAt: requireNumber(cloudSessionRoot, "createdAt", status, "cloudSession"),
1014
+ sourceType: requireSourceType(cloudSessionRoot, "sourceType", status, "cloudSession"),
1015
+ sourceRef: optionalString(cloudSessionRoot, "sourceRef", status, "cloudSession"),
1016
+ label: optionalString(cloudSessionRoot, "label", status, "cloudSession")
1017
+ };
1018
+ const expiresAt = optionalNumber(root, "expiresAt", status);
1019
+ return {
1020
+ sessionId,
1021
+ actionWsUrl,
1022
+ cdpWsUrl,
1023
+ actionToken,
1024
+ cdpToken,
1025
+ expiresAt,
1026
+ cloudSessionUrl,
1027
+ cloudSession
1028
+ };
1029
+ }
1030
+ function requireObject(value, message, status) {
1031
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1032
+ throw new OpensteerCloudError("CLOUD_CONTRACT_MISMATCH", message, status);
1033
+ }
1034
+ return value;
1035
+ }
1036
+ function requireString(source, field, status, parent) {
1037
+ const value = source[field];
1038
+ if (typeof value !== "string" || !value.trim()) {
1039
+ throw new OpensteerCloudError(
1040
+ "CLOUD_CONTRACT_MISMATCH",
1041
+ `Invalid cloud session create response: ${formatFieldPath(
1042
+ field,
1043
+ parent
1044
+ )} must be a non-empty string.`,
1045
+ status
1046
+ );
1047
+ }
1048
+ return value;
1049
+ }
1050
+ function requireNumber(source, field, status, parent) {
1051
+ const value = source[field];
1052
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1053
+ throw new OpensteerCloudError(
1054
+ "CLOUD_CONTRACT_MISMATCH",
1055
+ `Invalid cloud session create response: ${formatFieldPath(
1056
+ field,
1057
+ parent
1058
+ )} must be a finite number.`,
1059
+ status
1060
+ );
1061
+ }
1062
+ return value;
1063
+ }
1064
+ function optionalString(source, field, status, parent) {
1065
+ const value = source[field];
1066
+ if (value == null) {
1067
+ return void 0;
1068
+ }
1069
+ if (typeof value !== "string") {
1070
+ throw new OpensteerCloudError(
1071
+ "CLOUD_CONTRACT_MISMATCH",
1072
+ `Invalid cloud session create response: ${formatFieldPath(
1073
+ field,
1074
+ parent
1075
+ )} must be a string when present.`,
1076
+ status
1077
+ );
1078
+ }
1079
+ return value;
1080
+ }
1081
+ function optionalNumber(source, field, status, parent) {
1082
+ const value = source[field];
1083
+ if (value == null) {
1084
+ return void 0;
1085
+ }
1086
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1087
+ throw new OpensteerCloudError(
1088
+ "CLOUD_CONTRACT_MISMATCH",
1089
+ `Invalid cloud session create response: ${formatFieldPath(
1090
+ field,
1091
+ parent
1092
+ )} must be a finite number when present.`,
1093
+ status
1094
+ );
1095
+ }
1096
+ return value;
1097
+ }
1098
+ function requireSourceType(source, field, status, parent) {
1099
+ const value = source[field];
1100
+ if (value === "agent-thread" || value === "agent-run" || value === "local-cloud" || value === "manual") {
1101
+ return value;
1102
+ }
1103
+ throw new OpensteerCloudError(
1104
+ "CLOUD_CONTRACT_MISMATCH",
1105
+ `Invalid cloud session create response: ${formatFieldPath(
1106
+ field,
1107
+ parent
1108
+ )} must be one of "agent-thread", "agent-run", "local-cloud", or "manual".`,
1109
+ status
1110
+ );
1111
+ }
1112
+ function formatFieldPath(field, parent) {
1113
+ return parent ? `"${parent}.${field}"` : `"${field}"`;
1114
+ }
1115
+ function zeroImportResponse() {
1116
+ return {
1117
+ imported: 0,
1118
+ inserted: 0,
1119
+ updated: 0,
1120
+ skipped: 0
1121
+ };
1122
+ }
1123
+ function mergeImportResponse(first, second) {
1124
+ return {
1125
+ imported: first.imported + second.imported,
1126
+ inserted: first.inserted + second.inserted,
1127
+ updated: first.updated + second.updated,
1128
+ skipped: first.skipped + second.skipped
1129
+ };
1130
+ }
1131
+
1132
+ // src/cloud/runtime.ts
1133
+ var DEFAULT_CLOUD_BASE_URL = "https://api.opensteer.com";
1134
+ function createCloudRuntimeState(key, baseUrl = resolveCloudBaseUrl(), authScheme = "api-key") {
1135
+ const normalizedBaseUrl = normalizeCloudBaseUrl(baseUrl);
1136
+ return {
1137
+ sessionClient: new CloudSessionClient(
1138
+ normalizedBaseUrl,
1139
+ key,
1140
+ authScheme
1141
+ ),
1142
+ cdpClient: new CloudCdpClient(),
1143
+ baseUrl: normalizedBaseUrl,
1144
+ actionClient: null,
1145
+ sessionId: null,
1146
+ localRunId: null,
1147
+ cloudSessionUrl: null
1148
+ };
1149
+ }
1150
+ function resolveCloudBaseUrl() {
1151
+ const value = process.env.OPENSTEER_BASE_URL?.trim();
1152
+ if (!value) return DEFAULT_CLOUD_BASE_URL;
1153
+ return normalizeCloudBaseUrl(value);
1154
+ }
1155
+ function readCloudActionDescription(payload) {
1156
+ const description = payload.description;
1157
+ if (typeof description !== "string") return void 0;
1158
+ const normalized = description.trim();
1159
+ return normalized.length ? normalized : void 0;
1160
+ }
1161
+
1162
+ // src/auth/keychain-store.ts
1163
+ import { spawnSync } from "child_process";
1164
+ function commandExists(command) {
1165
+ const result = spawnSync(command, ["--help"], {
1166
+ encoding: "utf8",
1167
+ stdio: "ignore"
1168
+ });
1169
+ return result.error == null;
1170
+ }
1171
+ function commandFailed(result) {
1172
+ return typeof result.status === "number" && result.status !== 0;
1173
+ }
1174
+ function sanitizeCommandArgs(command, args) {
1175
+ if (command !== "security") {
1176
+ return args;
1177
+ }
1178
+ const sanitized = [];
1179
+ for (let index = 0; index < args.length; index += 1) {
1180
+ const value = args[index];
1181
+ sanitized.push(value);
1182
+ if (value === "-w" && index + 1 < args.length) {
1183
+ sanitized.push("[REDACTED]");
1184
+ index += 1;
1185
+ }
1186
+ }
1187
+ return sanitized;
1188
+ }
1189
+ function buildCommandError(command, args, result) {
1190
+ const stderr = typeof result.stderr === "string" && result.stderr.trim() ? result.stderr.trim() : `Command "${command}" failed with status ${String(result.status)}.`;
1191
+ const sanitizedArgs = sanitizeCommandArgs(command, args);
1192
+ return new Error(
1193
+ [
1194
+ `Unable to persist credential via ${command}.`,
1195
+ `${command} ${sanitizedArgs.join(" ")}`,
1196
+ stderr
1197
+ ].join(" ")
1198
+ );
1199
+ }
1200
+ function createMacosSecurityStore() {
1201
+ return {
1202
+ backend: "macos-security",
1203
+ get(service, account) {
1204
+ const result = spawnSync(
1205
+ "security",
1206
+ ["find-generic-password", "-s", service, "-a", account, "-w"],
1207
+ { encoding: "utf8" }
1208
+ );
1209
+ if (commandFailed(result)) {
1210
+ return null;
1211
+ }
1212
+ const secret = result.stdout.trim();
1213
+ return secret.length ? secret : null;
1214
+ },
1215
+ set(service, account, secret) {
1216
+ const args = [
1217
+ "add-generic-password",
1218
+ "-U",
1219
+ "-s",
1220
+ service,
1221
+ "-a",
1222
+ account,
1223
+ "-w",
1224
+ secret
1225
+ ];
1226
+ const result = spawnSync("security", args, { encoding: "utf8" });
1227
+ if (commandFailed(result)) {
1228
+ throw buildCommandError("security", args, result);
1229
+ }
1230
+ },
1231
+ delete(service, account) {
1232
+ const args = ["delete-generic-password", "-s", service, "-a", account];
1233
+ const result = spawnSync("security", args, { encoding: "utf8" });
1234
+ if (commandFailed(result)) {
1235
+ return;
1236
+ }
1237
+ }
1238
+ };
1239
+ }
1240
+ function createLinuxSecretToolStore() {
1241
+ return {
1242
+ backend: "linux-secret-tool",
1243
+ get(service, account) {
1244
+ const result = spawnSync(
1245
+ "secret-tool",
1246
+ ["lookup", "service", service, "account", account],
1247
+ {
1248
+ encoding: "utf8"
1249
+ }
1250
+ );
1251
+ if (commandFailed(result)) {
1252
+ return null;
1253
+ }
1254
+ const secret = result.stdout.trim();
1255
+ return secret.length ? secret : null;
1256
+ },
1257
+ set(service, account, secret) {
1258
+ const args = [
1259
+ "store",
1260
+ "--label",
1261
+ "Opensteer CLI",
1262
+ "service",
1263
+ service,
1264
+ "account",
1265
+ account
1266
+ ];
1267
+ const result = spawnSync("secret-tool", args, {
1268
+ encoding: "utf8",
1269
+ input: secret
1270
+ });
1271
+ if (commandFailed(result)) {
1272
+ throw buildCommandError("secret-tool", args, result);
1273
+ }
1274
+ },
1275
+ delete(service, account) {
1276
+ const args = ["clear", "service", service, "account", account];
1277
+ spawnSync("secret-tool", args, {
1278
+ encoding: "utf8"
1279
+ });
1280
+ }
1281
+ };
1282
+ }
1283
+ function createKeychainStore() {
1284
+ if (process.platform === "darwin") {
1285
+ if (!commandExists("security")) {
1286
+ return null;
1287
+ }
1288
+ return createMacosSecurityStore();
1289
+ }
1290
+ if (process.platform === "linux") {
1291
+ if (!commandExists("secret-tool")) {
1292
+ return null;
1293
+ }
1294
+ return createLinuxSecretToolStore();
1295
+ }
1296
+ return null;
1297
+ }
1298
+
1299
+ export {
1300
+ createKeychainStore,
1301
+ extractErrorMessage,
1302
+ normalizeError,
1303
+ normalizeNamespace,
1304
+ resolveNamespaceDir,
1305
+ resolveCloudSelection,
1306
+ resolveConfigWithEnv,
1307
+ resolveNamespace,
1308
+ OpensteerCloudError,
1309
+ cloudUnsupportedMethodError,
1310
+ cloudNotLaunchedError,
1311
+ CloudCdpClient,
1312
+ stripTrailingSlashes,
1313
+ normalizeCloudBaseUrl,
1314
+ cloudAuthHeaders,
1315
+ parseCloudHttpError,
1316
+ CloudSessionClient,
1317
+ DEFAULT_CLOUD_BASE_URL,
1318
+ createCloudRuntimeState,
1319
+ readCloudActionDescription
1320
+ };