opensteer 0.6.2 → 0.6.4

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 (33) hide show
  1. package/bin/opensteer.mjs +94 -8
  2. package/dist/{browser-profile-client-DK9qa_Dj.d.cts → browser-profile-client-D6PuRefA.d.cts} +1 -1
  3. package/dist/{browser-profile-client-CaL-mwqs.d.ts → browser-profile-client-OUHaODro.d.ts} +1 -1
  4. package/dist/{chunk-7RMY26CM.js → chunk-54KNQTOL.js} +172 -55
  5. package/dist/{chunk-WJI7TGBQ.js → chunk-6B6LOYU3.js} +1 -1
  6. package/dist/{chunk-F2VDVOJO.js → chunk-G6V2DJRN.js} +451 -609
  7. package/dist/chunk-K5CL76MG.js +81 -0
  8. package/dist/{chunk-WDRMHPWL.js → chunk-KPPOTU3D.js} +159 -180
  9. package/dist/cli/auth.cjs +186 -95
  10. package/dist/cli/auth.d.cts +1 -1
  11. package/dist/cli/auth.d.ts +1 -1
  12. package/dist/cli/auth.js +2 -2
  13. package/dist/cli/local-profile.cjs +197 -0
  14. package/dist/cli/local-profile.d.cts +18 -0
  15. package/dist/cli/local-profile.d.ts +18 -0
  16. package/dist/cli/local-profile.js +97 -0
  17. package/dist/cli/profile.cjs +1747 -1279
  18. package/dist/cli/profile.d.cts +2 -2
  19. package/dist/cli/profile.d.ts +2 -2
  20. package/dist/cli/profile.js +469 -7
  21. package/dist/cli/server.cjs +759 -257
  22. package/dist/cli/server.js +69 -16
  23. package/dist/index.cjs +688 -238
  24. package/dist/index.d.cts +7 -5
  25. package/dist/index.d.ts +7 -5
  26. package/dist/index.js +4 -3
  27. package/dist/{types-BxiRblC7.d.cts → types-BWItZPl_.d.cts} +31 -13
  28. package/dist/{types-BxiRblC7.d.ts → types-BWItZPl_.d.ts} +31 -13
  29. package/package.json +2 -2
  30. package/skills/opensteer/SKILL.md +34 -14
  31. package/skills/opensteer/references/cli-reference.md +1 -1
  32. package/skills/opensteer/references/examples.md +5 -3
  33. package/skills/opensteer/references/sdk-reference.md +16 -14
@@ -1,6 +1,6 @@
1
1
  import { Cookie } from 'playwright';
2
- import { O as OpensteerAuthScheme, a as OpensteerConfig, C as CookieParam } from '../types-BxiRblC7.cjs';
3
- import { B as BrowserProfileStatus, a as BrowserProfileListRequest, b as BrowserProfileListResponse, c as BrowserProfileCreateRequest, d as BrowserProfileDescriptor } from '../browser-profile-client-DK9qa_Dj.cjs';
2
+ import { O as OpensteerAuthScheme, a as OpensteerConfig, C as CookieParam } from '../types-BWItZPl_.cjs';
3
+ import { B as BrowserProfileStatus, a as BrowserProfileListRequest, b as BrowserProfileListResponse, c as BrowserProfileCreateRequest, d as BrowserProfileDescriptor } from '../browser-profile-client-D6PuRefA.cjs';
4
4
 
5
5
  type ParsedProfileArgs = {
6
6
  mode: 'help';
@@ -1,6 +1,6 @@
1
1
  import { Cookie } from 'playwright';
2
- import { O as OpensteerAuthScheme, a as OpensteerConfig, C as CookieParam } from '../types-BxiRblC7.js';
3
- import { B as BrowserProfileStatus, a as BrowserProfileListRequest, b as BrowserProfileListResponse, c as BrowserProfileCreateRequest, d as BrowserProfileDescriptor } from '../browser-profile-client-CaL-mwqs.js';
2
+ import { O as OpensteerAuthScheme, a as OpensteerConfig, C as CookieParam } from '../types-BWItZPl_.js';
3
+ import { B as BrowserProfileStatus, a as BrowserProfileListRequest, b as BrowserProfileListResponse, c as BrowserProfileCreateRequest, d as BrowserProfileDescriptor } from '../browser-profile-client-OUHaODro.js';
4
4
 
5
5
  type ParsedProfileArgs = {
6
6
  mode: 'help';
@@ -1,23 +1,485 @@
1
1
  import {
2
2
  BrowserProfileClient
3
- } from "../chunk-WJI7TGBQ.js";
3
+ } from "../chunk-6B6LOYU3.js";
4
4
  import {
5
+ createKeychainStore,
5
6
  ensureCloudCredentialsForCommand
6
- } from "../chunk-7RMY26CM.js";
7
+ } from "../chunk-54KNQTOL.js";
7
8
  import {
8
- Opensteer,
9
- expandHome,
10
- loadCookiesFromLocalProfileDir
11
- } from "../chunk-F2VDVOJO.js";
9
+ Opensteer
10
+ } from "../chunk-G6V2DJRN.js";
12
11
  import {
13
12
  resolveConfigWithEnv
14
- } from "../chunk-WDRMHPWL.js";
13
+ } from "../chunk-KPPOTU3D.js";
14
+ import {
15
+ expandHome
16
+ } from "../chunk-K5CL76MG.js";
15
17
  import "../chunk-3H5RRIMZ.js";
16
18
 
17
19
  // src/cli/profile.ts
18
20
  import path from "path";
19
21
  import { createInterface } from "readline/promises";
20
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
+
21
483
  // src/cli/profile-sync.ts
22
484
  function normalizeCookieDomain(value) {
23
485
  const trimmed = value.trim().toLowerCase();