opensteer 0.6.13 → 0.7.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 (62) hide show
  1. package/README.md +256 -184
  2. package/dist/chunk-PQYA6IX2.js +32571 -0
  3. package/dist/chunk-PQYA6IX2.js.map +1 -0
  4. package/dist/cli/bin.cjs +38201 -0
  5. package/dist/cli/bin.cjs.map +1 -0
  6. package/dist/cli/bin.d.cts +1 -0
  7. package/dist/cli/bin.d.ts +1 -0
  8. package/dist/cli/bin.js +5612 -0
  9. package/dist/cli/bin.js.map +1 -0
  10. package/dist/index.cjs +31309 -16009
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +4440 -670
  13. package/dist/index.d.ts +4440 -670
  14. package/dist/index.js +438 -378
  15. package/dist/index.js.map +1 -0
  16. package/package.json +56 -62
  17. package/skills/README.md +21 -20
  18. package/skills/opensteer/SKILL.md +60 -194
  19. package/skills/opensteer/references/cli-reference.md +69 -113
  20. package/skills/opensteer/references/request-workflow.md +81 -0
  21. package/skills/opensteer/references/sdk-reference.md +101 -154
  22. package/CHANGELOG.md +0 -75
  23. package/bin/opensteer.mjs +0 -1423
  24. package/dist/browser-profile-client-CGXc0-P9.d.cts +0 -228
  25. package/dist/browser-profile-client-DHLzMf-K.d.ts +0 -228
  26. package/dist/chunk-2ES46WCO.js +0 -1437
  27. package/dist/chunk-3H5RRIMZ.js +0 -69
  28. package/dist/chunk-AVXUMEDG.js +0 -62
  29. package/dist/chunk-DN3GI5CH.js +0 -57
  30. package/dist/chunk-FAHE5DB2.js +0 -190
  31. package/dist/chunk-HBTSQ2V4.js +0 -15259
  32. package/dist/chunk-K5CL76MG.js +0 -81
  33. package/dist/chunk-U724TBY6.js +0 -1262
  34. package/dist/chunk-ZRCFF546.js +0 -77
  35. package/dist/cli/auth.cjs +0 -2022
  36. package/dist/cli/auth.d.cts +0 -114
  37. package/dist/cli/auth.d.ts +0 -114
  38. package/dist/cli/auth.js +0 -15
  39. package/dist/cli/local-profile.cjs +0 -197
  40. package/dist/cli/local-profile.d.cts +0 -18
  41. package/dist/cli/local-profile.d.ts +0 -18
  42. package/dist/cli/local-profile.js +0 -97
  43. package/dist/cli/profile.cjs +0 -18548
  44. package/dist/cli/profile.d.cts +0 -79
  45. package/dist/cli/profile.d.ts +0 -79
  46. package/dist/cli/profile.js +0 -1328
  47. package/dist/cli/server.cjs +0 -17232
  48. package/dist/cli/server.d.cts +0 -2
  49. package/dist/cli/server.d.ts +0 -2
  50. package/dist/cli/server.js +0 -977
  51. package/dist/cli/skills-installer.cjs +0 -230
  52. package/dist/cli/skills-installer.d.cts +0 -28
  53. package/dist/cli/skills-installer.d.ts +0 -28
  54. package/dist/cli/skills-installer.js +0 -201
  55. package/dist/extractor-4Q3TFZJB.js +0 -8
  56. package/dist/resolver-MGN64KCP.js +0 -7
  57. package/dist/types-Cr10igF3.d.cts +0 -345
  58. package/dist/types-Cr10igF3.d.ts +0 -345
  59. package/skills/electron/SKILL.md +0 -87
  60. package/skills/electron/references/opensteer-electron-recipes.md +0 -88
  61. package/skills/electron/references/opensteer-electron-workflow.md +0 -85
  62. package/skills/opensteer/references/examples.md +0 -118
@@ -1,1328 +0,0 @@
1
- import {
2
- BrowserProfileClient
3
- } from "../chunk-ZRCFF546.js";
4
- import {
5
- createKeychainStore,
6
- ensureCloudCredentialsForCommand
7
- } from "../chunk-U724TBY6.js";
8
- import {
9
- Opensteer
10
- } from "../chunk-HBTSQ2V4.js";
11
- import {
12
- resolveConfigWithEnv
13
- } from "../chunk-2ES46WCO.js";
14
- import {
15
- expandHome
16
- } from "../chunk-K5CL76MG.js";
17
- import "../chunk-3H5RRIMZ.js";
18
-
19
- // src/cli/profile.ts
20
- import path from "path";
21
- import { createInterface } from "readline/promises";
22
-
23
- // src/browser/chromium-profile.ts
24
- import { promisify } from "util";
25
- import { execFile } from "child_process";
26
- import { createDecipheriv, createHash, pbkdf2Sync } from "crypto";
27
- import {
28
- cp,
29
- copyFile,
30
- mkdtemp,
31
- readdir,
32
- readFile,
33
- rm
34
- } from "fs/promises";
35
- import { existsSync, statSync } from "fs";
36
- import { basename, dirname, join } from "path";
37
- import { tmpdir } from "os";
38
- import { chromium } from "playwright";
39
- var execFileAsync = promisify(execFile);
40
- var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
41
- var AES_BLOCK_BYTES = 16;
42
- var MAC_KEY_ITERATIONS = 1003;
43
- var LINUX_KEY_ITERATIONS = 1;
44
- var KEY_LENGTH = 16;
45
- var KEY_SALT = "saltysalt";
46
- var DEFAULT_CHROMIUM_BRAND = {
47
- macService: "Chrome Safe Storage",
48
- macAccount: "Chrome",
49
- linuxApplications: ["chrome", "google-chrome"]
50
- };
51
- var CHROMIUM_BRANDS = [
52
- {
53
- match: ["bravesoftware", "brave-browser"],
54
- brand: {
55
- macService: "Brave Safe Storage",
56
- macAccount: "Brave",
57
- linuxApplications: ["brave-browser", "brave"]
58
- }
59
- },
60
- {
61
- match: ["microsoft", "edge"],
62
- brand: {
63
- macService: "Microsoft Edge Safe Storage",
64
- macAccount: "Microsoft Edge",
65
- linuxApplications: ["microsoft-edge"],
66
- playwrightChannel: "msedge"
67
- }
68
- },
69
- {
70
- match: ["google", "chrome beta"],
71
- brand: {
72
- macService: "Chrome Beta Safe Storage",
73
- macAccount: "Chrome Beta",
74
- linuxApplications: ["chrome-beta"],
75
- playwrightChannel: "chrome-beta"
76
- }
77
- },
78
- {
79
- match: ["google", "chrome"],
80
- brand: {
81
- macService: "Chrome Safe Storage",
82
- macAccount: "Chrome",
83
- linuxApplications: ["chrome", "google-chrome"],
84
- playwrightChannel: "chrome"
85
- }
86
- },
87
- {
88
- match: ["chromium"],
89
- brand: {
90
- macService: "Chromium Safe Storage",
91
- macAccount: "Chromium",
92
- linuxApplications: ["chromium"]
93
- }
94
- }
95
- ];
96
- function directoryExists(filePath) {
97
- try {
98
- return statSync(filePath).isDirectory();
99
- } catch {
100
- return false;
101
- }
102
- }
103
- function fileExists(filePath) {
104
- try {
105
- return statSync(filePath).isFile();
106
- } catch {
107
- return false;
108
- }
109
- }
110
- function resolveCookieDbPath(profileDir) {
111
- const candidates = [join(profileDir, "Network", "Cookies"), join(profileDir, "Cookies")];
112
- for (const candidate of candidates) {
113
- if (fileExists(candidate)) {
114
- return candidate;
115
- }
116
- }
117
- return null;
118
- }
119
- async function selectProfileDirFromUserDataDir(userDataDir) {
120
- const entries = await readdir(userDataDir, {
121
- withFileTypes: true
122
- }).catch(() => []);
123
- const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => join(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
124
- return candidates;
125
- }
126
- async function resolveChromiumProfileLocation(inputPath) {
127
- const expandedPath = expandHome(inputPath.trim());
128
- if (!expandedPath) {
129
- throw new Error("Profile path cannot be empty.");
130
- }
131
- if (fileExists(expandedPath) && basename(expandedPath) === "Cookies") {
132
- const directParent = dirname(expandedPath);
133
- const profileDir = basename(directParent) === "Network" ? dirname(directParent) : directParent;
134
- const userDataDir = dirname(profileDir);
135
- return {
136
- userDataDir,
137
- profileDir,
138
- profileDirectory: basename(profileDir),
139
- cookieDbPath: expandedPath,
140
- localStatePath: fileExists(join(userDataDir, "Local State")) ? join(userDataDir, "Local State") : null
141
- };
142
- }
143
- if (fileExists(expandedPath)) {
144
- throw new Error(
145
- `Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
146
- );
147
- }
148
- if (!directoryExists(expandedPath)) {
149
- throw new Error(
150
- `Could not find a Chromium profile at "${inputPath}".`
151
- );
152
- }
153
- const directCookieDb = resolveCookieDbPath(expandedPath);
154
- if (directCookieDb) {
155
- const userDataDir = dirname(expandedPath);
156
- return {
157
- userDataDir,
158
- profileDir: expandedPath,
159
- profileDirectory: basename(expandedPath),
160
- cookieDbPath: directCookieDb,
161
- localStatePath: fileExists(join(userDataDir, "Local State")) ? join(userDataDir, "Local State") : null
162
- };
163
- }
164
- const localStatePath = join(expandedPath, "Local State");
165
- if (!fileExists(localStatePath)) {
166
- throw new Error(
167
- `Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
168
- );
169
- }
170
- const profileDirs = await selectProfileDirFromUserDataDir(expandedPath);
171
- if (profileDirs.length === 0) {
172
- throw new Error(
173
- `No Chromium profile with a Cookies database was found under "${inputPath}".`
174
- );
175
- }
176
- if (profileDirs.length > 1) {
177
- const candidates = profileDirs.map((entry) => basename(entry)).join(", ");
178
- throw new Error(
179
- `"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
180
- );
181
- }
182
- const selectedProfileDir = profileDirs[0];
183
- const cookieDbPath = resolveCookieDbPath(selectedProfileDir);
184
- if (!cookieDbPath) {
185
- throw new Error(
186
- `No Chromium Cookies database was found for "${inputPath}".`
187
- );
188
- }
189
- return {
190
- userDataDir: expandedPath,
191
- profileDir: selectedProfileDir,
192
- profileDirectory: basename(selectedProfileDir),
193
- cookieDbPath,
194
- localStatePath
195
- };
196
- }
197
- function detectChromiumBrand(location) {
198
- const normalizedPath = location.userDataDir.toLowerCase();
199
- for (const candidate of CHROMIUM_BRANDS) {
200
- if (candidate.match.every((fragment) => normalizedPath.includes(fragment))) {
201
- return candidate.brand;
202
- }
203
- }
204
- return DEFAULT_CHROMIUM_BRAND;
205
- }
206
- async function createSqliteSnapshot(dbPath) {
207
- const snapshotDir = await mkdtemp(join(tmpdir(), "opensteer-cookie-db-"));
208
- const snapshotPath = join(snapshotDir, "Cookies");
209
- await copyFile(dbPath, snapshotPath);
210
- for (const suffix of ["-wal", "-shm", "-journal"]) {
211
- const source = `${dbPath}${suffix}`;
212
- if (!existsSync(source)) {
213
- continue;
214
- }
215
- await copyFile(source, `${snapshotPath}${suffix}`);
216
- }
217
- return {
218
- snapshotPath,
219
- cleanup: async () => {
220
- await rm(snapshotDir, { recursive: true, force: true });
221
- }
222
- };
223
- }
224
- async function querySqliteJson(dbPath, query) {
225
- const result = await execFileAsync("sqlite3", ["-json", dbPath, query], {
226
- encoding: "utf8",
227
- maxBuffer: 64 * 1024 * 1024
228
- });
229
- const stdout = result.stdout;
230
- const trimmed = stdout.trim();
231
- if (!trimmed) {
232
- return [];
233
- }
234
- return JSON.parse(trimmed);
235
- }
236
- function convertChromiumTimestampToUnixSeconds(value) {
237
- if (!value || value === "0") {
238
- return -1;
239
- }
240
- const micros = BigInt(value);
241
- if (micros <= CHROMIUM_EPOCH_MICROS) {
242
- return -1;
243
- }
244
- return Number((micros - CHROMIUM_EPOCH_MICROS) / 1000000n);
245
- }
246
- function mapChromiumSameSite(value) {
247
- if (value === 2) {
248
- return "Strict";
249
- }
250
- if (value === 0) {
251
- return "None";
252
- }
253
- return "Lax";
254
- }
255
- function stripChromiumPadding(buffer) {
256
- const paddingLength = buffer[buffer.length - 1];
257
- if (paddingLength <= 0 || paddingLength > AES_BLOCK_BYTES) {
258
- return buffer;
259
- }
260
- return buffer.subarray(0, buffer.length - paddingLength);
261
- }
262
- function stripDomainHashPrefix(buffer, hostKey) {
263
- if (buffer.length < 32) {
264
- return buffer;
265
- }
266
- const domainHash = createHash("sha256").update(hostKey, "utf8").digest();
267
- if (buffer.subarray(0, 32).equals(domainHash)) {
268
- return buffer.subarray(32);
269
- }
270
- return buffer;
271
- }
272
- function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
273
- const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
274
- const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
275
- const decipher = createDecipheriv("aes-128-cbc", key, iv);
276
- const plaintext = Buffer.concat([
277
- decipher.update(ciphertext),
278
- decipher.final()
279
- ]);
280
- return stripDomainHashPrefix(stripChromiumPadding(plaintext), hostKey).toString(
281
- "utf8"
282
- );
283
- }
284
- function decryptChromiumAes256GcmValue(encryptedValue, key) {
285
- const nonce = encryptedValue.subarray(3, 15);
286
- const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
287
- const authTag = encryptedValue.subarray(encryptedValue.length - 16);
288
- const decipher = createDecipheriv("aes-256-gcm", key, nonce);
289
- decipher.setAuthTag(authTag);
290
- return Buffer.concat([
291
- decipher.update(ciphertext),
292
- decipher.final()
293
- ]).toString("utf8");
294
- }
295
- async function dpapiUnprotect(buffer) {
296
- const script = [
297
- `$inputBytes = [Convert]::FromBase64String('${buffer.toString("base64")}')`,
298
- "$plainBytes = [System.Security.Cryptography.ProtectedData]::Unprotect(",
299
- " $inputBytes,",
300
- " $null,",
301
- " [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
302
- ")",
303
- "[Convert]::ToBase64String($plainBytes)"
304
- ].join("\n");
305
- const { stdout } = await execFileAsync(
306
- "powershell.exe",
307
- ["-NoProfile", "-NonInteractive", "-Command", script],
308
- {
309
- encoding: "utf8",
310
- maxBuffer: 8 * 1024 * 1024
311
- }
312
- );
313
- return Buffer.from(stdout.trim(), "base64");
314
- }
315
- async function buildChromiumDecryptor(location) {
316
- if (process.platform === "darwin") {
317
- const brand = detectChromiumBrand(location);
318
- const keychainStore = createKeychainStore();
319
- const password = keychainStore?.get(brand.macService, brand.macAccount) ?? null;
320
- if (!password) {
321
- throw new Error(
322
- `Unable to read ${brand.macService} from macOS Keychain.`
323
- );
324
- }
325
- const key = pbkdf2Sync(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
326
- return async (row) => decryptChromiumAes128CbcValue(
327
- Buffer.from(row.encrypted_value || "", "hex"),
328
- key,
329
- row.host_key
330
- );
331
- }
332
- if (process.platform === "linux") {
333
- const brand = detectChromiumBrand(location);
334
- const keychainStore = createKeychainStore();
335
- const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
336
- const key = pbkdf2Sync(
337
- password || "peanuts",
338
- KEY_SALT,
339
- LINUX_KEY_ITERATIONS,
340
- KEY_LENGTH,
341
- "sha1"
342
- );
343
- return async (row) => decryptChromiumAes128CbcValue(
344
- Buffer.from(row.encrypted_value || "", "hex"),
345
- key,
346
- row.host_key
347
- );
348
- }
349
- if (process.platform === "win32") {
350
- if (!location.localStatePath) {
351
- throw new Error(
352
- `Unable to locate Chromium Local State for profile: ${location.profileDir}`
353
- );
354
- }
355
- const localState = JSON.parse(
356
- await readFile(location.localStatePath, "utf8")
357
- );
358
- const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
359
- if (!encryptedKeyBase64) {
360
- throw new Error(
361
- `Local State did not include os_crypt.encrypted_key for ${location.userDataDir}`
362
- );
363
- }
364
- const encryptedKey = Buffer.from(encryptedKeyBase64, "base64");
365
- const masterKey = await dpapiUnprotect(encryptedKey.subarray(5));
366
- return async (row) => {
367
- const encryptedValue = Buffer.from(row.encrypted_value || "", "hex");
368
- if (encryptedValue.length > 4 && encryptedValue[0] === 1 && encryptedValue[1] === 0 && encryptedValue[2] === 0 && encryptedValue[3] === 0) {
369
- const decrypted = await dpapiUnprotect(encryptedValue);
370
- return decrypted.toString("utf8");
371
- }
372
- return decryptChromiumAes256GcmValue(encryptedValue, masterKey);
373
- };
374
- }
375
- throw new Error(
376
- `Local Chromium cookie sync is not supported on ${process.platform}.`
377
- );
378
- }
379
- function buildPlaywrightCookie(row, value) {
380
- if (!row.name.trim()) {
381
- return null;
382
- }
383
- if (!row.host_key.trim()) {
384
- return null;
385
- }
386
- const expires = row.has_expires === 1 ? convertChromiumTimestampToUnixSeconds(row.expires_utc) : -1;
387
- if (expires !== -1 && expires <= Math.floor(Date.now() / 1e3)) {
388
- return null;
389
- }
390
- return {
391
- name: row.name,
392
- value,
393
- domain: row.host_key,
394
- path: row.path || "/",
395
- expires,
396
- httpOnly: row.is_httponly === 1,
397
- secure: row.is_secure === 1,
398
- sameSite: mapChromiumSameSite(row.samesite)
399
- };
400
- }
401
- async function loadCookiesFromLocalProfileDir(inputPath, options = {}) {
402
- const location = await resolveChromiumProfileLocation(inputPath);
403
- try {
404
- return await loadCookiesFromSqlite(location);
405
- } catch (error) {
406
- if (!isMissingSqliteBinary(error)) {
407
- throw error;
408
- }
409
- }
410
- return await loadCookiesFromBrowserSnapshot(location, options);
411
- }
412
- async function loadCookiesFromSqlite(location) {
413
- const snapshot = await createSqliteSnapshot(location.cookieDbPath);
414
- try {
415
- const rows = await querySqliteJson(
416
- snapshot.snapshotPath,
417
- [
418
- "SELECT",
419
- " host_key,",
420
- " name,",
421
- " value,",
422
- " hex(encrypted_value) AS encrypted_value,",
423
- " path,",
424
- " CAST(expires_utc AS TEXT) AS expires_utc,",
425
- " is_secure,",
426
- " is_httponly,",
427
- " has_expires,",
428
- " samesite",
429
- "FROM cookies"
430
- ].join(" ")
431
- );
432
- const decryptValue = await buildChromiumDecryptor(location);
433
- const cookies = [];
434
- for (const row of rows) {
435
- let value = row.value || "";
436
- if (!value && row.encrypted_value) {
437
- value = await decryptValue(row);
438
- }
439
- const cookie = buildPlaywrightCookie(row, value);
440
- if (cookie) {
441
- cookies.push(cookie);
442
- }
443
- }
444
- return cookies;
445
- } finally {
446
- await snapshot.cleanup();
447
- }
448
- }
449
- async function loadCookiesFromBrowserSnapshot(location, options) {
450
- const snapshotRootDir = await mkdtemp(join(tmpdir(), "opensteer-profile-"));
451
- const snapshotProfileDir = join(
452
- snapshotRootDir,
453
- basename(location.profileDir)
454
- );
455
- let context = null;
456
- try {
457
- await cp(location.profileDir, snapshotProfileDir, {
458
- recursive: true
459
- });
460
- if (location.localStatePath) {
461
- await copyFile(location.localStatePath, join(snapshotRootDir, "Local State"));
462
- }
463
- const brand = detectChromiumBrand(location);
464
- const args = [`--profile-directory=${basename(snapshotProfileDir)}`];
465
- context = await chromium.launchPersistentContext(snapshotRootDir, {
466
- channel: brand.playwrightChannel,
467
- headless: options.headless ?? true,
468
- timeout: options.timeout ?? 12e4,
469
- args
470
- });
471
- return await context.cookies();
472
- } finally {
473
- await context?.close().catch(() => void 0);
474
- await rm(snapshotRootDir, { recursive: true, force: true });
475
- }
476
- }
477
- function isMissingSqliteBinary(error) {
478
- return Boolean(
479
- error && typeof error === "object" && "code" in error && error.code === "ENOENT"
480
- );
481
- }
482
-
483
- // src/cli/profile-sync.ts
484
- function normalizeCookieDomain(value) {
485
- const trimmed = value.trim().toLowerCase();
486
- return trimmed.replace(/^\.+/, "");
487
- }
488
- function extractCookieDomain(cookie) {
489
- if (typeof cookie.domain === "string" && cookie.domain.trim()) {
490
- return normalizeCookieDomain(cookie.domain);
491
- }
492
- return null;
493
- }
494
- function cookieMatchesDomainFilters(cookie, domainFilters) {
495
- if (!domainFilters.length) return true;
496
- const cookieDomain = extractCookieDomain(cookie);
497
- if (!cookieDomain) return false;
498
- return domainFilters.some((domain) => {
499
- if (cookieDomain === domain) return true;
500
- return cookieDomain.endsWith(`.${domain}`);
501
- });
502
- }
503
- function toCookieParam(cookie) {
504
- const name = typeof cookie.name === "string" ? cookie.name.trim() : "";
505
- if (!name) return null;
506
- if (typeof cookie.value !== "string") return null;
507
- const output = {
508
- name,
509
- value: cookie.value
510
- };
511
- if (typeof cookie.domain === "string" && cookie.domain.trim()) {
512
- output.domain = cookie.domain.trim();
513
- }
514
- if (typeof cookie.path === "string" && cookie.path.trim()) {
515
- output.path = cookie.path;
516
- }
517
- if (!output.domain) {
518
- return null;
519
- }
520
- if (!output.path) {
521
- output.path = "/";
522
- }
523
- if (typeof cookie.expires === "number" && Number.isFinite(cookie.expires)) {
524
- if (cookie.expires > 0) {
525
- output.expires = cookie.expires;
526
- }
527
- }
528
- if (typeof cookie.httpOnly === "boolean") {
529
- output.httpOnly = cookie.httpOnly;
530
- }
531
- if (typeof cookie.secure === "boolean") {
532
- output.secure = cookie.secure;
533
- }
534
- if (cookie.sameSite === "Strict" || cookie.sameSite === "Lax" || cookie.sameSite === "None") {
535
- output.sameSite = cookie.sameSite;
536
- }
537
- return output;
538
- }
539
- function prepareCookiesForSync(cookies, options = {}) {
540
- const filteredDomains = Array.from(
541
- new Set(
542
- (options.domains || []).map((domain) => normalizeCookieDomain(domain)).filter(Boolean)
543
- )
544
- );
545
- const domainCounts = {};
546
- let matchedCookies = 0;
547
- let droppedInvalid = 0;
548
- const dedupeMap = /* @__PURE__ */ new Map();
549
- for (const cookie of cookies) {
550
- if (!cookieMatchesDomainFilters(cookie, filteredDomains)) {
551
- continue;
552
- }
553
- matchedCookies += 1;
554
- const normalizedCookie = toCookieParam(cookie);
555
- if (!normalizedCookie) {
556
- droppedInvalid += 1;
557
- continue;
558
- }
559
- const domainKey = extractCookieDomain(cookie) || "(unknown)";
560
- domainCounts[domainKey] = (domainCounts[domainKey] || 0) + 1;
561
- const identityDomain = normalizedCookie.domain ? normalizeCookieDomain(normalizedCookie.domain) : "";
562
- const dedupeKey = [
563
- normalizedCookie.name,
564
- identityDomain,
565
- normalizedCookie.path || "/"
566
- ].join("");
567
- dedupeMap.set(dedupeKey, normalizedCookie);
568
- }
569
- const dedupedCookies = dedupeMap.size;
570
- return {
571
- cookies: Array.from(dedupeMap.values()),
572
- totalCookies: cookies.length,
573
- matchedCookies,
574
- dedupedCookies,
575
- droppedInvalid,
576
- filteredDomains,
577
- domainCounts
578
- };
579
- }
580
-
581
- // src/cli/profile.ts
582
- var HELP_TEXT = `Usage: opensteer profile <command> [options]
583
-
584
- Manage cloud browser profiles and sync local cookie state into cloud profiles.
585
-
586
- Commands:
587
- list List cloud browser profiles
588
- create --name <name> Create a cloud browser profile
589
- sync Sync local profile cookies to cloud
590
-
591
- Cloud auth options (all commands):
592
- --api-key <key> Cloud API key (defaults to OPENSTEER_API_KEY)
593
- --access-token <token> Cloud bearer access token (defaults to OPENSTEER_ACCESS_TOKEN)
594
- --base-url <url> Cloud API base URL (defaults to env or the last selected host)
595
- --auth-scheme <scheme> api-key (default) or bearer
596
- --json JSON output (progress logs go to stderr)
597
-
598
- Sync options:
599
- --from-profile-dir <dir> Local browser profile directory or Chromium user-data dir to read cookies from (required)
600
- --to-profile-id <id> Destination cloud profile id
601
- --name <name> Create destination cloud profile with this name if --to-profile-id is omitted
602
- --domain <domain> Restrict sync to one domain (repeatable)
603
- --all-domains Explicitly sync all domains
604
- --dry-run Analyze cookies and scope without uploading to cloud
605
- --yes Required for non-interactive execution
606
- --headless <true|false> Browser headless mode for local/cloud sync sessions (default: true)
607
- -h, --help Show this help
608
-
609
- Examples:
610
- opensteer profile list
611
- opensteer profile create --name "My Session Profile"
612
- opensteer profile sync --from-profile-dir ~/Library/Application\\ Support/Google/Chrome/Default --to-profile-id bp_123 --domain github.com
613
- opensteer profile sync --from-profile-dir ./my-profile --name "Imported Profile" --all-domains --yes
614
- `;
615
- function parseBooleanValue(raw, flag) {
616
- const normalized = raw.trim().toLowerCase();
617
- if (normalized === "true" || normalized === "1") {
618
- return { ok: true, value: true };
619
- }
620
- if (normalized === "false" || normalized === "0") {
621
- return { ok: true, value: false };
622
- }
623
- return {
624
- ok: false,
625
- error: `${flag} must be "true" or "false".`
626
- };
627
- }
628
- function parseAuthScheme(raw) {
629
- const normalized = raw.trim().toLowerCase();
630
- if (normalized === "api-key" || normalized === "bearer") {
631
- return normalized;
632
- }
633
- return null;
634
- }
635
- function isBrowserProfileStatus(value) {
636
- return value === "active" || value === "archived" || value === "error";
637
- }
638
- function readFlagValue(args, index, flag) {
639
- const value = args[index + 1];
640
- if (value === void 0 || value.startsWith("-")) {
641
- return {
642
- ok: false,
643
- error: `${flag} requires a value.`
644
- };
645
- }
646
- return {
647
- ok: true,
648
- value,
649
- nextIndex: index + 1
650
- };
651
- }
652
- function parseListArgs(rawArgs) {
653
- const args = {};
654
- for (let i = 0; i < rawArgs.length; i++) {
655
- const arg = rawArgs[i];
656
- if (arg === "--json") {
657
- args.json = true;
658
- continue;
659
- }
660
- if (arg === "--api-key") {
661
- const value = readFlagValue(rawArgs, i, "--api-key");
662
- if (!value.ok) return { mode: "error", error: value.error };
663
- args.apiKey = value.value;
664
- i = value.nextIndex;
665
- continue;
666
- }
667
- if (arg === "--access-token") {
668
- const value = readFlagValue(rawArgs, i, "--access-token");
669
- if (!value.ok) return { mode: "error", error: value.error };
670
- args.accessToken = value.value;
671
- i = value.nextIndex;
672
- continue;
673
- }
674
- if (arg === "--base-url") {
675
- const value = readFlagValue(rawArgs, i, "--base-url");
676
- if (!value.ok) return { mode: "error", error: value.error };
677
- args.baseUrl = value.value;
678
- i = value.nextIndex;
679
- continue;
680
- }
681
- if (arg === "--auth-scheme") {
682
- const value = readFlagValue(rawArgs, i, "--auth-scheme");
683
- if (!value.ok) return { mode: "error", error: value.error };
684
- const parsed = parseAuthScheme(value.value);
685
- if (!parsed) {
686
- return {
687
- mode: "error",
688
- error: '--auth-scheme must be "api-key" or "bearer".'
689
- };
690
- }
691
- args.authScheme = parsed;
692
- i = value.nextIndex;
693
- continue;
694
- }
695
- if (arg === "--cursor") {
696
- const value = readFlagValue(rawArgs, i, "--cursor");
697
- if (!value.ok) return { mode: "error", error: value.error };
698
- args.cursor = value.value;
699
- i = value.nextIndex;
700
- continue;
701
- }
702
- if (arg === "--limit") {
703
- const value = readFlagValue(rawArgs, i, "--limit");
704
- if (!value.ok) return { mode: "error", error: value.error };
705
- const parsed = Number.parseInt(value.value, 10);
706
- if (!Number.isInteger(parsed) || parsed <= 0) {
707
- return {
708
- mode: "error",
709
- error: "--limit must be a positive integer."
710
- };
711
- }
712
- args.limit = parsed;
713
- i = value.nextIndex;
714
- continue;
715
- }
716
- if (arg === "--status") {
717
- const value = readFlagValue(rawArgs, i, "--status");
718
- if (!value.ok) return { mode: "error", error: value.error };
719
- const status = value.value.trim();
720
- if (!isBrowserProfileStatus(status)) {
721
- return {
722
- mode: "error",
723
- error: "--status must be one of: active, archived, error."
724
- };
725
- }
726
- args.status = status;
727
- i = value.nextIndex;
728
- continue;
729
- }
730
- return {
731
- mode: "error",
732
- error: `Unsupported option "${arg}" for "opensteer profile list".`
733
- };
734
- }
735
- return { mode: "list", args };
736
- }
737
- function parseCreateArgs(rawArgs) {
738
- const args = {};
739
- for (let i = 0; i < rawArgs.length; i++) {
740
- const arg = rawArgs[i];
741
- if (arg === "--json") {
742
- args.json = true;
743
- continue;
744
- }
745
- if (arg === "--name") {
746
- const value = readFlagValue(rawArgs, i, "--name");
747
- if (!value.ok) return { mode: "error", error: value.error };
748
- args.name = value.value.trim();
749
- i = value.nextIndex;
750
- continue;
751
- }
752
- if (arg === "--api-key") {
753
- const value = readFlagValue(rawArgs, i, "--api-key");
754
- if (!value.ok) return { mode: "error", error: value.error };
755
- args.apiKey = value.value;
756
- i = value.nextIndex;
757
- continue;
758
- }
759
- if (arg === "--access-token") {
760
- const value = readFlagValue(rawArgs, i, "--access-token");
761
- if (!value.ok) return { mode: "error", error: value.error };
762
- args.accessToken = value.value;
763
- i = value.nextIndex;
764
- continue;
765
- }
766
- if (arg === "--base-url") {
767
- const value = readFlagValue(rawArgs, i, "--base-url");
768
- if (!value.ok) return { mode: "error", error: value.error };
769
- args.baseUrl = value.value;
770
- i = value.nextIndex;
771
- continue;
772
- }
773
- if (arg === "--auth-scheme") {
774
- const value = readFlagValue(rawArgs, i, "--auth-scheme");
775
- if (!value.ok) return { mode: "error", error: value.error };
776
- const parsed = parseAuthScheme(value.value);
777
- if (!parsed) {
778
- return {
779
- mode: "error",
780
- error: '--auth-scheme must be "api-key" or "bearer".'
781
- };
782
- }
783
- args.authScheme = parsed;
784
- i = value.nextIndex;
785
- continue;
786
- }
787
- return {
788
- mode: "error",
789
- error: `Unsupported option "${arg}" for "opensteer profile create".`
790
- };
791
- }
792
- if (!args.name) {
793
- return {
794
- mode: "error",
795
- error: '--name is required for "opensteer profile create".'
796
- };
797
- }
798
- return {
799
- mode: "create",
800
- args: {
801
- ...args,
802
- name: args.name
803
- }
804
- };
805
- }
806
- function parseSyncArgs(rawArgs) {
807
- const args = {
808
- fromProfileDir: "",
809
- domains: [],
810
- allDomains: false,
811
- dryRun: false,
812
- yes: false,
813
- headless: true
814
- };
815
- for (let i = 0; i < rawArgs.length; i++) {
816
- const arg = rawArgs[i];
817
- if (arg === "--json") {
818
- args.json = true;
819
- continue;
820
- }
821
- if (arg === "--all-domains") {
822
- args.allDomains = true;
823
- continue;
824
- }
825
- if (arg === "--dry-run") {
826
- args.dryRun = true;
827
- continue;
828
- }
829
- if (arg === "--yes") {
830
- args.yes = true;
831
- continue;
832
- }
833
- if (arg === "--from-profile-dir") {
834
- const value = readFlagValue(rawArgs, i, "--from-profile-dir");
835
- if (!value.ok) return { mode: "error", error: value.error };
836
- args.fromProfileDir = value.value;
837
- i = value.nextIndex;
838
- continue;
839
- }
840
- if (arg === "--to-profile-id") {
841
- const value = readFlagValue(rawArgs, i, "--to-profile-id");
842
- if (!value.ok) return { mode: "error", error: value.error };
843
- args.toProfileId = value.value;
844
- i = value.nextIndex;
845
- continue;
846
- }
847
- if (arg === "--name") {
848
- const value = readFlagValue(rawArgs, i, "--name");
849
- if (!value.ok) return { mode: "error", error: value.error };
850
- args.name = value.value;
851
- i = value.nextIndex;
852
- continue;
853
- }
854
- if (arg === "--domain") {
855
- const value = readFlagValue(rawArgs, i, "--domain");
856
- if (!value.ok) return { mode: "error", error: value.error };
857
- args.domains.push(value.value);
858
- i = value.nextIndex;
859
- continue;
860
- }
861
- if (arg === "--headless") {
862
- const value = readFlagValue(rawArgs, i, "--headless");
863
- if (!value.ok) return { mode: "error", error: value.error };
864
- const parsed = parseBooleanValue(value.value, "--headless");
865
- if (!parsed.ok) return { mode: "error", error: parsed.error };
866
- args.headless = parsed.value;
867
- i = value.nextIndex;
868
- continue;
869
- }
870
- if (arg === "--api-key") {
871
- const value = readFlagValue(rawArgs, i, "--api-key");
872
- if (!value.ok) return { mode: "error", error: value.error };
873
- args.apiKey = value.value;
874
- i = value.nextIndex;
875
- continue;
876
- }
877
- if (arg === "--access-token") {
878
- const value = readFlagValue(rawArgs, i, "--access-token");
879
- if (!value.ok) return { mode: "error", error: value.error };
880
- args.accessToken = value.value;
881
- i = value.nextIndex;
882
- continue;
883
- }
884
- if (arg === "--base-url") {
885
- const value = readFlagValue(rawArgs, i, "--base-url");
886
- if (!value.ok) return { mode: "error", error: value.error };
887
- args.baseUrl = value.value;
888
- i = value.nextIndex;
889
- continue;
890
- }
891
- if (arg === "--auth-scheme") {
892
- const value = readFlagValue(rawArgs, i, "--auth-scheme");
893
- if (!value.ok) return { mode: "error", error: value.error };
894
- const parsed = parseAuthScheme(value.value);
895
- if (!parsed) {
896
- return {
897
- mode: "error",
898
- error: '--auth-scheme must be "api-key" or "bearer".'
899
- };
900
- }
901
- args.authScheme = parsed;
902
- i = value.nextIndex;
903
- continue;
904
- }
905
- return {
906
- mode: "error",
907
- error: `Unsupported option "${arg}" for "opensteer profile sync".`
908
- };
909
- }
910
- if (!args.fromProfileDir.trim()) {
911
- return {
912
- mode: "error",
913
- error: '--from-profile-dir is required for "opensteer profile sync".'
914
- };
915
- }
916
- if (args.allDomains && args.domains.length > 0) {
917
- return {
918
- mode: "error",
919
- error: "Use either --all-domains or --domain, not both."
920
- };
921
- }
922
- return { mode: "sync", args };
923
- }
924
- function parseOpensteerProfileArgs(rawArgs) {
925
- if (!rawArgs.length) {
926
- return { mode: "help" };
927
- }
928
- const [subcommand, ...rest] = rawArgs;
929
- if (subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
930
- return { mode: "help" };
931
- }
932
- if (subcommand === "list") {
933
- return parseListArgs(rest);
934
- }
935
- if (subcommand === "create") {
936
- return parseCreateArgs(rest);
937
- }
938
- if (subcommand === "sync") {
939
- return parseSyncArgs(rest);
940
- }
941
- return {
942
- mode: "error",
943
- error: `Unsupported profile subcommand "${subcommand}".`
944
- };
945
- }
946
- function createDefaultDeps() {
947
- return {
948
- env: process.env,
949
- createBrowserProfileClient: (context) => new BrowserProfileClient(
950
- context.baseUrl,
951
- context.token,
952
- context.authScheme
953
- ),
954
- createOpensteer: (config) => new Opensteer(config),
955
- loadLocalProfileCookies: (profileDir, options) => loadCookiesFromLocalProfileDir(profileDir, options),
956
- isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
957
- confirm: async (message) => {
958
- const rl = createInterface({
959
- input: process.stdin,
960
- output: process.stderr
961
- });
962
- try {
963
- const answer = await rl.question(`${message} [y/N] `);
964
- const normalized = answer.trim().toLowerCase();
965
- return normalized === "y" || normalized === "yes";
966
- } finally {
967
- rl.close();
968
- }
969
- },
970
- writeStdout: (message) => process.stdout.write(message),
971
- writeStderr: (message) => process.stderr.write(message)
972
- };
973
- }
974
- async function buildCloudAuthContext(args, deps) {
975
- const ensured = await ensureCloudCredentialsForCommand({
976
- commandName: "opensteer profile",
977
- env: deps.env,
978
- apiKeyFlag: args.apiKey,
979
- accessTokenFlag: args.accessToken,
980
- baseUrl: args.baseUrl,
981
- interactive: deps.isInteractive(),
982
- autoLoginIfNeeded: true,
983
- writeProgress: args.json ? deps.writeStderr : deps.writeStdout,
984
- writeStderr: deps.writeStderr
985
- });
986
- if (args.authScheme) {
987
- if (ensured.kind === "access-token" && args.authScheme !== "bearer") {
988
- throw new Error(
989
- "--auth-scheme=api-key is incompatible with --access-token or saved login credentials."
990
- );
991
- }
992
- if (ensured.kind === "api-key" && args.authScheme === "bearer") {
993
- return {
994
- ...ensured,
995
- authScheme: "bearer"
996
- };
997
- }
998
- }
999
- return {
1000
- token: ensured.token,
1001
- baseUrl: ensured.baseUrl,
1002
- authScheme: ensured.authScheme,
1003
- kind: ensured.kind,
1004
- source: ensured.source
1005
- };
1006
- }
1007
- function printProfileHelp(deps) {
1008
- deps.writeStdout(`${HELP_TEXT}
1009
- `);
1010
- }
1011
- function writeJson(deps, payload) {
1012
- deps.writeStdout(`${JSON.stringify(payload)}
1013
- `);
1014
- }
1015
- function writeHumanLine(deps, message) {
1016
- deps.writeStdout(`${message}
1017
- `);
1018
- }
1019
- function writeProgressLine(deps, jsonOutput, message) {
1020
- if (jsonOutput) {
1021
- deps.writeStderr(`${message}
1022
- `);
1023
- return;
1024
- }
1025
- deps.writeStdout(`${message}
1026
- `);
1027
- }
1028
- async function runList(args, deps) {
1029
- const auth = await buildCloudAuthContext(args, deps);
1030
- const client = deps.createBrowserProfileClient(auth);
1031
- const response = await client.list({
1032
- cursor: args.cursor,
1033
- limit: args.limit,
1034
- status: args.status
1035
- });
1036
- if (args.json) {
1037
- writeJson(deps, response);
1038
- return 0;
1039
- }
1040
- if (!response.profiles.length) {
1041
- writeHumanLine(deps, "No cloud browser profiles found.");
1042
- return 0;
1043
- }
1044
- writeHumanLine(deps, `Cloud browser profiles (${response.profiles.length}):`);
1045
- for (const profile of response.profiles) {
1046
- writeHumanLine(
1047
- deps,
1048
- ` ${profile.profileId} ${profile.name} [${profile.status}]`
1049
- );
1050
- }
1051
- if (response.nextCursor) {
1052
- writeHumanLine(
1053
- deps,
1054
- `Next cursor: ${response.nextCursor}`
1055
- );
1056
- }
1057
- return 0;
1058
- }
1059
- async function runCreate(args, deps) {
1060
- const auth = await buildCloudAuthContext(args, deps);
1061
- const client = deps.createBrowserProfileClient(auth);
1062
- const profile = await client.create({
1063
- name: args.name
1064
- });
1065
- if (args.json) {
1066
- writeJson(deps, profile);
1067
- return 0;
1068
- }
1069
- writeHumanLine(
1070
- deps,
1071
- `Created cloud browser profile "${profile.name}" (${profile.profileId}).`
1072
- );
1073
- return 0;
1074
- }
1075
- async function importCookiesInBatches(context, cookies, batchSize = 100) {
1076
- let imported = 0;
1077
- let skipped = 0;
1078
- for (let offset = 0; offset < cookies.length; offset += batchSize) {
1079
- const batch = cookies.slice(offset, offset + batchSize);
1080
- try {
1081
- await context.addCookies(batch);
1082
- imported += batch.length;
1083
- } catch {
1084
- for (const cookie of batch) {
1085
- try {
1086
- await context.addCookies([cookie]);
1087
- imported += 1;
1088
- } catch {
1089
- skipped += 1;
1090
- }
1091
- }
1092
- }
1093
- }
1094
- return {
1095
- imported,
1096
- skipped
1097
- };
1098
- }
1099
- async function resolveTargetProfileId(args, deps, client) {
1100
- const explicitProfileId = args.toProfileId?.trim();
1101
- if (explicitProfileId) {
1102
- return {
1103
- profileId: explicitProfileId,
1104
- created: false
1105
- };
1106
- }
1107
- const requestedName = args.name?.trim();
1108
- if (requestedName) {
1109
- const profile = await client.create({
1110
- name: requestedName
1111
- });
1112
- return {
1113
- profileId: profile.profileId,
1114
- created: true
1115
- };
1116
- }
1117
- if (!deps.isInteractive()) {
1118
- throw new Error(
1119
- "Sync target is required in non-interactive mode. Use --to-profile-id <id> or --name <name>."
1120
- );
1121
- }
1122
- const defaultName = `Synced ${path.basename(args.fromProfileDir)}`;
1123
- const shouldCreate = await deps.confirm(
1124
- `No destination profile provided. Create a new cloud profile named "${defaultName}"?`
1125
- );
1126
- if (!shouldCreate) {
1127
- throw new Error(
1128
- "Profile sync cancelled. Provide --to-profile-id or --name to choose a destination profile."
1129
- );
1130
- }
1131
- const created = await client.create({
1132
- name: defaultName
1133
- });
1134
- return {
1135
- profileId: created.profileId,
1136
- created: true
1137
- };
1138
- }
1139
- function resolveSyncBrowserProfilePreference(profileId, env) {
1140
- const resolved = resolveConfigWithEnv({
1141
- cloud: true
1142
- }, {
1143
- env
1144
- }).config;
1145
- const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
1146
- const configured = cloudConfig?.browserProfile;
1147
- if (configured && configured.profileId.trim() === profileId && configured.reuseIfActive !== void 0) {
1148
- return {
1149
- profileId,
1150
- reuseIfActive: configured.reuseIfActive
1151
- };
1152
- }
1153
- return { profileId };
1154
- }
1155
- async function runSync(args, deps) {
1156
- const sourceProfileDir = expandHome(args.fromProfileDir.trim());
1157
- const nonInteractive = !deps.isInteractive();
1158
- const hasExplicitScope = args.allDomains || args.domains.length > 0;
1159
- if (nonInteractive && !args.yes) {
1160
- throw new Error(
1161
- "Non-interactive profile sync requires --yes."
1162
- );
1163
- }
1164
- if (nonInteractive && !hasExplicitScope) {
1165
- throw new Error(
1166
- "Non-interactive profile sync requires explicit scope: --domain <domain> (repeatable) or --all-domains."
1167
- );
1168
- }
1169
- if (!hasExplicitScope && !nonInteractive) {
1170
- const confirmed = await deps.confirm(
1171
- "No domain filter provided. Sync cookies for all domains?"
1172
- );
1173
- if (!confirmed) {
1174
- throw new Error(
1175
- "Profile sync cancelled. Use --domain <domain> or --all-domains."
1176
- );
1177
- }
1178
- }
1179
- writeProgressLine(
1180
- deps,
1181
- Boolean(args.json),
1182
- `Reading cookies from local profile: ${sourceProfileDir}`
1183
- );
1184
- let sourceCookies = [];
1185
- sourceCookies = await deps.loadLocalProfileCookies(sourceProfileDir, {
1186
- headless: args.headless,
1187
- timeout: 12e4
1188
- });
1189
- const prepared = prepareCookiesForSync(sourceCookies, {
1190
- domains: args.allDomains ? [] : args.domains
1191
- });
1192
- if (!prepared.cookies.length) {
1193
- throw new Error(
1194
- "No syncable cookies found for the selected profile and scope."
1195
- );
1196
- }
1197
- if (args.dryRun) {
1198
- const payload2 = {
1199
- success: true,
1200
- dryRun: true,
1201
- profileId: args.toProfileId?.trim() || null,
1202
- createdProfile: false,
1203
- totalCookies: prepared.totalCookies,
1204
- matchedCookies: prepared.matchedCookies,
1205
- dedupedCookies: prepared.dedupedCookies,
1206
- droppedInvalid: prepared.droppedInvalid,
1207
- filteredDomains: prepared.filteredDomains,
1208
- domainCounts: prepared.domainCounts
1209
- };
1210
- if (args.json) {
1211
- writeJson(deps, payload2);
1212
- } else {
1213
- writeHumanLine(deps, "Dry run complete.");
1214
- writeHumanLine(deps, ` Total cookies: ${prepared.totalCookies}`);
1215
- writeHumanLine(deps, ` Scope-matched cookies: ${prepared.matchedCookies}`);
1216
- writeHumanLine(deps, ` Deduped cookies: ${prepared.dedupedCookies}`);
1217
- writeHumanLine(deps, ` Dropped invalid: ${prepared.droppedInvalid}`);
1218
- if (prepared.filteredDomains.length) {
1219
- writeHumanLine(
1220
- deps,
1221
- ` Domain filters: ${prepared.filteredDomains.join(", ")}`
1222
- );
1223
- } else {
1224
- writeHumanLine(deps, " Domain scope: all domains");
1225
- }
1226
- }
1227
- return 0;
1228
- }
1229
- const auth = await buildCloudAuthContext(args, deps);
1230
- const client = deps.createBrowserProfileClient(auth);
1231
- const target = await resolveTargetProfileId(args, deps, client);
1232
- const targetBrowserProfile = resolveSyncBrowserProfilePreference(
1233
- target.profileId,
1234
- deps.env
1235
- );
1236
- writeProgressLine(
1237
- deps,
1238
- Boolean(args.json),
1239
- `Importing ${prepared.cookies.length} cookies into cloud profile ${target.profileId}`
1240
- );
1241
- const cloud = deps.createOpensteer({
1242
- cloud: {
1243
- ...auth.kind === "api-key" ? { apiKey: auth.token } : { accessToken: auth.token },
1244
- baseUrl: auth.baseUrl,
1245
- authScheme: auth.authScheme,
1246
- browserProfile: targetBrowserProfile
1247
- },
1248
- cursor: { enabled: false }
1249
- });
1250
- let imported = 0;
1251
- let skipped = 0;
1252
- try {
1253
- await cloud.launch({
1254
- headless: args.headless,
1255
- timeout: 12e4
1256
- });
1257
- const result = await importCookiesInBatches(cloud.context, prepared.cookies);
1258
- imported = result.imported;
1259
- skipped = result.skipped;
1260
- } finally {
1261
- await cloud.close().catch(() => void 0);
1262
- }
1263
- const payload = {
1264
- success: true,
1265
- profileId: target.profileId,
1266
- createdProfile: target.created,
1267
- totalCookies: prepared.totalCookies,
1268
- matchedCookies: prepared.matchedCookies,
1269
- dedupedCookies: prepared.dedupedCookies,
1270
- droppedInvalid: prepared.droppedInvalid,
1271
- importedCookies: imported,
1272
- skippedCookies: skipped,
1273
- filteredDomains: prepared.filteredDomains,
1274
- domainCounts: prepared.domainCounts
1275
- };
1276
- if (args.json) {
1277
- writeJson(deps, payload);
1278
- return 0;
1279
- }
1280
- writeHumanLine(deps, "Profile cookie sync complete.");
1281
- writeHumanLine(deps, ` Cloud profile: ${target.profileId}`);
1282
- writeHumanLine(deps, ` Imported cookies: ${imported}`);
1283
- writeHumanLine(deps, ` Skipped cookies: ${skipped}`);
1284
- if (prepared.filteredDomains.length) {
1285
- writeHumanLine(
1286
- deps,
1287
- ` Domain filters: ${prepared.filteredDomains.join(", ")}`
1288
- );
1289
- } else {
1290
- writeHumanLine(deps, " Domain scope: all domains");
1291
- }
1292
- return 0;
1293
- }
1294
- async function runOpensteerProfileCli(rawArgs, overrideDeps = {}) {
1295
- const deps = {
1296
- ...createDefaultDeps(),
1297
- ...overrideDeps
1298
- };
1299
- const parsed = parseOpensteerProfileArgs(rawArgs);
1300
- if (parsed.mode === "help") {
1301
- printProfileHelp(deps);
1302
- return 0;
1303
- }
1304
- if (parsed.mode === "error") {
1305
- deps.writeStderr(`${parsed.error}
1306
- `);
1307
- deps.writeStderr('Run "opensteer profile --help" for usage.\n');
1308
- return 1;
1309
- }
1310
- try {
1311
- if (parsed.mode === "list") {
1312
- return await runList(parsed.args, deps);
1313
- }
1314
- if (parsed.mode === "create") {
1315
- return await runCreate(parsed.args, deps);
1316
- }
1317
- return await runSync(parsed.args, deps);
1318
- } catch (error) {
1319
- const message = error instanceof Error ? error.message : "Profile command failed.";
1320
- deps.writeStderr(`${message}
1321
- `);
1322
- return 1;
1323
- }
1324
- }
1325
- export {
1326
- parseOpensteerProfileArgs,
1327
- runOpensteerProfileCli
1328
- };