chrome-tools 1.1.15 → 1.1.18

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 (58) hide show
  1. package/dist/chrome_abe_decrypt.exe +0 -0
  2. package/dist/cli.js +8837 -0
  3. package/dist/index.d.ts +629 -0
  4. package/dist/index.js +2532 -0
  5. package/package.json +62 -29
  6. package/bin/chrome_abe_decrypt.exe +0 -0
  7. package/dist/bin/chrome_abe_decrypt.exe +0 -0
  8. package/dist/bin/cli.d.ts +0 -9
  9. package/dist/bin/cli.js +0 -590
  10. package/dist/src/core/browsers.d.ts +0 -73
  11. package/dist/src/core/browsers.js +0 -186
  12. package/dist/src/core/chrome.d.ts +0 -124
  13. package/dist/src/core/chrome.js +0 -259
  14. package/dist/src/core/crypto.d.ts +0 -52
  15. package/dist/src/core/crypto.js +0 -152
  16. package/dist/src/core/db.d.ts +0 -22
  17. package/dist/src/core/db.js +0 -227
  18. package/dist/src/core/keychain.d.ts +0 -57
  19. package/dist/src/core/keychain.js +0 -191
  20. package/dist/src/core/limit.d.ts +0 -4
  21. package/dist/src/core/limit.js +0 -12
  22. package/dist/src/core/platform/darwin.d.ts +0 -12
  23. package/dist/src/core/platform/darwin.js +0 -31
  24. package/dist/src/core/platform/index.d.ts +0 -30
  25. package/dist/src/core/platform/index.js +0 -69
  26. package/dist/src/core/platform/linux.d.ts +0 -10
  27. package/dist/src/core/platform/linux.js +0 -64
  28. package/dist/src/core/platform/types.d.ts +0 -45
  29. package/dist/src/core/platform/types.js +0 -1
  30. package/dist/src/core/platform/win32.d.ts +0 -13
  31. package/dist/src/core/platform/win32.js +0 -329
  32. package/dist/src/core/sql.d.ts +0 -2
  33. package/dist/src/core/sql.js +0 -4
  34. package/dist/src/index.d.ts +0 -28
  35. package/dist/src/index.js +0 -34
  36. package/dist/src/modules/autofill.d.ts +0 -68
  37. package/dist/src/modules/autofill.js +0 -170
  38. package/dist/src/modules/bookmarks.d.ts +0 -22
  39. package/dist/src/modules/bookmarks.js +0 -83
  40. package/dist/src/modules/cookies.d.ts +0 -40
  41. package/dist/src/modules/cookies.js +0 -163
  42. package/dist/src/modules/downloads.d.ts +0 -27
  43. package/dist/src/modules/downloads.js +0 -80
  44. package/dist/src/modules/extensions.d.ts +0 -24
  45. package/dist/src/modules/extensions.js +0 -71
  46. package/dist/src/modules/history.d.ts +0 -34
  47. package/dist/src/modules/history.js +0 -83
  48. package/dist/src/modules/local-storage.d.ts +0 -40
  49. package/dist/src/modules/local-storage.js +0 -455
  50. package/dist/src/modules/passwords.d.ts +0 -50
  51. package/dist/src/modules/passwords.js +0 -198
  52. package/dist/src/modules/search-engines.d.ts +0 -21
  53. package/dist/src/modules/search-engines.js +0 -38
  54. package/dist/src/modules/top-sites.d.ts +0 -17
  55. package/dist/src/modules/top-sites.js +0 -33
  56. package/src/native/build.bat +0 -31
  57. package/src/native/chrome_abe_decrypt.c +0 -336
  58. package/src/native/chrome_abe_decrypt.exe +0 -0
package/dist/bin/cli.js DELETED
@@ -1,590 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * chrome-tools CLI
4
- *
5
- * A comprehensive command-line interface for extracting Chrome browser data on
6
- * macOS, Windows, and Linux.
7
- * Supports subcommands and shortcut flags for quick access to all data types.
8
- */
9
- import { readFileSync } from "node:fs";
10
- import { Command, InvalidArgumentError } from "commander";
11
- import chalk from "chalk";
12
- import Table from "cli-table3";
13
- import { discoverProfiles, getProfileMetadata, getProfileStates, getDirectorySize, detectInstalledBrowsers } from "../src/core/chrome.js";
14
- import { setActiveBrowser, getActiveBrowser, isKeychainAccessDenied } from "../src/core/platform/index.js";
15
- import { describePasswordStores, getPasswords } from "../src/modules/passwords.js";
16
- import { getBookmarks } from "../src/modules/bookmarks.js";
17
- import { getHistory, getSearchTerms } from "../src/modules/history.js";
18
- import { getCookies } from "../src/modules/cookies.js";
19
- import { getLocalStorage } from "../src/modules/local-storage.js";
20
- import { getAutofillEntries, getCreditCards, getAddresses } from "../src/modules/autofill.js";
21
- import { getDownloads } from "../src/modules/downloads.js";
22
- import { getExtensions } from "../src/modules/extensions.js";
23
- import { getSearchEngines } from "../src/modules/search-engines.js";
24
- import { getTopSites } from "../src/modules/top-sites.js";
25
- // Resolved lazily per-invocation (inside handlers, after the active browser is
26
- // chosen) so `--browser` can drive the default profile path.
27
- const defaultProfilePath = process.env.PROFILE_PATH || undefined;
28
- const defaultProfileName = process.env.PROFILE_NAME || undefined;
29
- const defaultBrowser = process.env.BROWSER || process.env.CHROME_BROWSER || undefined;
30
- // ─── Output Helpers ──────────────────────────────────────────────────────────
31
- function outputJSON(data) {
32
- console.log(JSON.stringify(data, null, 2));
33
- }
34
- function makeTable(headers, colWidths) {
35
- return new Table({
36
- head: headers.map((h) => chalk.bold.white(h)),
37
- colWidths,
38
- wordWrap: true,
39
- style: { border: ["grey"], head: [] },
40
- });
41
- }
42
- /**
43
- * Prints a prominent, friendly "No keychain access" message for a denied/locked
44
- * Keychain authorization. macOS itself shows the access prompt when we read the
45
- * key — this explains what happened when that prompt is denied or unavailable.
46
- * Returns true if the error was a keychain-access denial (and was handled here).
47
- */
48
- function reportKeychainDenied(err) {
49
- if (!isKeychainAccessDenied(err))
50
- return false;
51
- console.error(chalk.red.bold("\n🔑 No keychain access\n"));
52
- console.error(chalk.yellow("chrome-tools couldn't read the browser's encryption key from your macOS\n" +
53
- "Keychain, so passwords and other encrypted data can't be decrypted."));
54
- // The typed error already carries actionable, cause-specific guidance.
55
- console.error(chalk.dim("\n" + err.message + "\n"));
56
- return true;
57
- }
58
- function printCount(label, count) {
59
- if (count === 0) {
60
- console.log(chalk.yellow(`\n⚠ No ${label} found.\n`));
61
- }
62
- else {
63
- console.log(chalk.green(`\n✔ Found ${count} ${label}.\n`));
64
- }
65
- }
66
- function renderResults(opts, results, title, label, headers, widths, toRow) {
67
- if (emit(opts, results))
68
- return;
69
- if (results.length === 0)
70
- return printCount(label, 0);
71
- console.log(chalk.bold(`\n${title}:\n`));
72
- const table = makeTable(headers, widths);
73
- for (const result of results)
74
- table.push(toRow(result));
75
- console.log(table.toString());
76
- printCount(label, results.length);
77
- }
78
- function errorMessage(err) {
79
- return err instanceof Error ? err.message : String(err);
80
- }
81
- // When set (during `--all-browsers --json`), handlers route their JSON payload
82
- // here instead of printing, so we can emit a single combined document.
83
- let _jsonCollector = null;
84
- /**
85
- * Emits a handler's results. Returns true when output was handled here (JSON or
86
- * collected), letting the caller skip its human-readable table rendering.
87
- */
88
- function emit(opts, results) {
89
- if (_jsonCollector) {
90
- _jsonCollector.push({ browser: getActiveBrowser().name, results });
91
- return true;
92
- }
93
- if (opts.json) {
94
- outputJSON(results);
95
- return true;
96
- }
97
- return false;
98
- }
99
- /**
100
- * Runs a single data handler for every installed Chromium browser, reusing the
101
- * handler's own rendering. Human output gets a per-browser section header; JSON
102
- * output is collected into one combined array.
103
- */
104
- function runAll(handler, opts) {
105
- const installed = detectInstalledBrowsers();
106
- if (installed.length === 0) {
107
- if (opts.json)
108
- return outputJSON([]);
109
- console.log(chalk.yellow("\n⚠ No supported Chromium browsers found on this machine.\n"));
110
- return;
111
- }
112
- // --all-browsers spans browsers with different dirs, so a fixed --profile-path
113
- // makes no sense; let each browser resolve its own default.
114
- const perBrowserOpts = {
115
- ...opts,
116
- browser: undefined,
117
- profilePath: undefined,
118
- };
119
- const previousBrowser = getActiveBrowser();
120
- _jsonCollector = opts.json ? [] : null;
121
- try {
122
- for (const b of installed) {
123
- setActiveBrowser(b.browser.id);
124
- if (!opts.json) {
125
- console.log(chalk.bold.magenta(`\n══════════ ${b.browser.name} ══════════`));
126
- }
127
- try {
128
- handler(perBrowserOpts);
129
- }
130
- catch (err) {
131
- if (_jsonCollector) {
132
- _jsonCollector.push({ browser: b.browser.name, results: { error: errorMessage(err) } });
133
- }
134
- else if (isKeychainAccessDenied(err)) {
135
- console.log(chalk.red(` 🔑 No keychain access — skipped (${err.service}).`));
136
- console.log(chalk.dim(` ${err.message.split("\n").join("\n ")}`));
137
- }
138
- else {
139
- console.log(chalk.red(` ⚠ Skipped: ${errorMessage(err)}`));
140
- }
141
- }
142
- }
143
- if (_jsonCollector)
144
- outputJSON(_jsonCollector);
145
- }
146
- finally {
147
- _jsonCollector = null;
148
- setActiveBrowser(previousBrowser.id);
149
- }
150
- }
151
- /**
152
- * Resolves the effective scope (browser + path + profile) from a command's
153
- * options, applying environment fallbacks with the right precedence:
154
- *
155
- * - A typed `--profile-path` / `--profile` always wins.
156
- * - When a browser is explicitly scoped (`--browser`, `--all-browsers`, or a
157
- * `BROWSER`/`CHROME_BROWSER` env var), the legacy `PROFILE_PATH` /
158
- * `PROFILE_NAME` env defaults are ignored — they point at one specific
159
- * Chrome dir and would otherwise mask the chosen browser's own location.
160
- * - Otherwise (the classic single-browser setup), `PROFILE_PATH` /
161
- * `PROFILE_NAME` apply as before.
162
- */
163
- function scopeFromCli(cmd) {
164
- const opts = cmd.optsWithGlobals();
165
- const browserScoped = Boolean(opts.browser) || Boolean(opts.allBrowsers) || Boolean(defaultBrowser);
166
- opts.browser = opts.browser || defaultBrowser || undefined;
167
- if (!opts.profilePath) {
168
- opts.profilePath = browserScoped ? undefined : defaultProfilePath;
169
- }
170
- if (!opts.profile) {
171
- opts.profile = browserScoped ? undefined : defaultProfileName;
172
- }
173
- return opts;
174
- }
175
- /**
176
- * Central entry point for data handlers: applies `--browser` (single) or fans
177
- * out across `--all-browsers`.
178
- */
179
- function dispatch(handler, opts) {
180
- if (opts.allBrowsers) {
181
- runAll(handler, opts);
182
- return;
183
- }
184
- setActiveBrowser(opts.browser);
185
- handler(opts);
186
- }
187
- // ─── Browsers Command ─────────────────────────────────────────────────────────
188
- function handleBrowsers(opts) {
189
- const detected = detectInstalledBrowsers({ includeMissing: true });
190
- if (opts.json) {
191
- return outputJSON(detected.map((d) => ({
192
- id: d.browser.id,
193
- name: d.browser.name,
194
- installed: d.installed,
195
- profileCount: d.profileCount,
196
- path: d.chromeDir,
197
- safeStorageLabel: d.browser.safeStorageLabel,
198
- })));
199
- }
200
- const installed = detected.filter((d) => d.installed);
201
- console.log(chalk.bold(`\n🌐 Supported browsers (${installed.length} installed):\n`));
202
- const table = makeTable(["Browser", "Id", "Installed", "Profiles", "Path"], [20, 14, 11, 9, 50]);
203
- for (const d of detected) {
204
- table.push([
205
- chalk.white(d.browser.name),
206
- chalk.cyan(d.browser.id),
207
- d.installed ? chalk.green("✓") : chalk.dim("✗"),
208
- d.installed ? chalk.yellow(String(d.profileCount)) : chalk.dim("—"),
209
- chalk.dim(d.chromeDir),
210
- ]);
211
- }
212
- console.log(table.toString());
213
- }
214
- // ─── Shared Options ──────────────────────────────────────────────────────────
215
- function addCommonOptions(cmd) {
216
- return cmd
217
- .option("--profile-path <path>", "Custom browser User Data Path (overrides --browser)")
218
- .option("--profile <name>", "Target a specific profile")
219
- .option("--browser <name>", "Browser to read (chrome, edge, brave, comet, vivaldi, opera, chromium…)")
220
- .option("--all-browsers", "Read from every installed Chromium browser")
221
- .option("--json", "Output as JSON");
222
- }
223
- function addSearchOption(cmd) {
224
- return cmd.option("--search <term>", "Filter results (case-insensitive)");
225
- }
226
- function addLimitOption(cmd, defaultLimit = 100) {
227
- return cmd.option("--limit <n>", "Limit number of results", parseLimit, defaultLimit);
228
- }
229
- function parseLimit(value) {
230
- const limit = Number(value);
231
- if (!Number.isSafeInteger(limit) || limit < 0) {
232
- throw new InvalidArgumentError("Limit must be a non-negative integer.");
233
- }
234
- return limit;
235
- }
236
- // ─── Program Setup ───────────────────────────────────────────────────────────
237
- const program = new Command();
238
- const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
239
- program
240
- .name("chrome-tools")
241
- .description(chalk.bold.cyan("Chrome Data Toolkit") +
242
- chalk.dim(" — Extract passwords, bookmarks, history, cookies, and more from Chrome on macOS, Windows, and Linux"))
243
- .version(pkg.version)
244
- .option("--passwords", "Shortcut: list saved passwords")
245
- .option("--bookmarks", "Shortcut: list bookmarks")
246
- .option("--local-storage", "Shortcut: list localStorage entries")
247
- .option("--profiles", "Shortcut: list profiles")
248
- .option("--browsers", "Shortcut: list supported / installed browsers")
249
- .option("--size", "Show each profile's size on disk when listing profiles (slower)");
250
- addCommonOptions(program)
251
- .option("--search <term>", "Filter results (for shortcut flags)")
252
- .option("--limit <n>", "Limit results (for shortcut flags)", parseLimit)
253
- .action((opts, cmd) => {
254
- // Default action when no subcommand is given.
255
- const o = scopeFromCli(cmd);
256
- if (o.browsers) {
257
- handleBrowsers(o);
258
- }
259
- else if (o.passwords) {
260
- dispatch(handlePasswords, o);
261
- }
262
- else if (o.bookmarks) {
263
- dispatch(handleBookmarks, o);
264
- }
265
- else if (o.localStorage) {
266
- dispatch(handleLocalStorage, o);
267
- }
268
- else {
269
- // Default (including --profiles shortcut): list profiles.
270
- dispatch(handleProfiles, o);
271
- }
272
- });
273
- // ─── Profiles Command ───────────────────────────────────────────────────────
274
- const profilesCmd = program
275
- .command("profiles")
276
- .description("List Chrome profiles");
277
- addCommonOptions(profilesCmd)
278
- .option("--size", "Compute and show each profile's size on disk (slower)")
279
- .action((opts, cmd) => {
280
- dispatch(handleProfiles, scopeFromCli(cmd));
281
- });
282
- const statesCmd = profilesCmd
283
- .command("states")
284
- .description("Show detailed profile states with available data files");
285
- addCommonOptions(statesCmd).action((opts, cmd) => dispatch(handleProfileStates, scopeFromCli(cmd)));
286
- // ─── Browsers Command ─────────────────────────────────────────────────────────
287
- const browsersCmd = program
288
- .command("browsers")
289
- .description("List supported browsers and detect which are installed");
290
- browsersCmd
291
- .option("--json", "Output as JSON")
292
- .action((opts, cmd) => handleBrowsers(scopeFromCli(cmd)));
293
- function handleProfiles(opts) {
294
- const profiles = discoverProfiles(opts.profilePath);
295
- const metadata = getProfileMetadata(opts.profilePath);
296
- const withSize = Boolean(opts.size);
297
- if (opts.json) {
298
- const metaMap = new Map(metadata.map((m) => [m.directory, m]));
299
- const enriched = profiles.map((p) => ({
300
- ...p,
301
- ...metaMap.get(p.name),
302
- ...(withSize ? { sizeBytes: getDirectorySize(p.path) } : {}),
303
- }));
304
- emit(opts, enriched);
305
- return;
306
- }
307
- if (profiles.length === 0) {
308
- console.log(chalk.yellow("\n⚠ No Chrome profiles found.\n"));
309
- return;
310
- }
311
- const metaMap = new Map(metadata.map((m) => [m.directory, m]));
312
- console.log(chalk.bold(`\n📁 Discovered ${profiles.length} profile(s):\n`));
313
- const headers = withSize
314
- ? ["Directory", "Display Name", "Email", "Size", "Path"]
315
- : ["Directory", "Display Name", "Email", "Path"];
316
- const widths = withSize ? [16, 22, 30, 10, 42] : [16, 22, 30, 50];
317
- const table = makeTable(headers, widths);
318
- for (const p of profiles) {
319
- const meta = metaMap.get(p.name);
320
- const row = [
321
- chalk.cyan(p.name),
322
- chalk.white(meta?.name || p.name),
323
- chalk.dim(meta?.email || "—"),
324
- ];
325
- if (withSize) {
326
- row.push(chalk.yellow(formatBytes(getDirectorySize(p.path))));
327
- }
328
- row.push(chalk.dim(p.path));
329
- table.push(row);
330
- }
331
- console.log(table.toString());
332
- printCount("profile(s)", profiles.length);
333
- }
334
- function handleProfileStates(opts) {
335
- const states = getProfileStates(opts.profilePath);
336
- if (opts.json) {
337
- emit(opts, states);
338
- return;
339
- }
340
- if (states.length === 0) {
341
- console.log(chalk.yellow("\n⚠ No Chrome profiles found.\n"));
342
- return;
343
- }
344
- console.log(chalk.bold(`\n📊 Profile States (${states.length} profile(s)):\n`));
345
- for (const state of states) {
346
- console.log(chalk.bold.cyan(` ${state.displayName}`) + chalk.dim(` (${state.directory})`));
347
- if (state.email) {
348
- console.log(chalk.dim(` Email: ${state.email}`));
349
- }
350
- console.log(chalk.dim(` Path: ${state.path}`));
351
- const files = state.dataFiles;
352
- const available = Object.entries(files)
353
- .filter(([, exists]) => exists)
354
- .map(([name]) => name);
355
- const missing = Object.entries(files)
356
- .filter(([, exists]) => !exists)
357
- .map(([name]) => name);
358
- console.log(` Available: ${available.map((f) => chalk.green(f)).join(", ") || chalk.dim("none")}`);
359
- if (missing.length > 0) {
360
- console.log(` Missing: ${missing.map((f) => chalk.dim(f)).join(", ")}`);
361
- }
362
- console.log();
363
- }
364
- }
365
- // ─── Passwords Command ──────────────────────────────────────────────────────
366
- const passwordsCmd = program.command("passwords").description("List saved passwords");
367
- addSearchOption(addCommonOptions(passwordsCmd))
368
- .option("--diagnose", "Report the stores that would be read — profile, file, row count, and any read error — instead of the passwords themselves")
369
- .action((opts, cmd) => dispatch(handlePasswords, scopeFromCli(cmd)));
370
- function handlePasswords(opts) {
371
- if (opts.diagnose)
372
- return diagnosePasswords(opts);
373
- const results = getPasswords(opts);
374
- if (emit(opts, results))
375
- return;
376
- if (results.length === 0) {
377
- return printCount("passwords", 0);
378
- }
379
- console.log(chalk.bold("\n🔑 Saved Passwords:\n"));
380
- const showProfile = new Set(results.map((r) => r.profile)).size > 1;
381
- // Always show the source (device vs account) — it explains where each entry
382
- // lives and why a device-only listing can look empty when sync is on.
383
- const head = ["Source", "URL", "Username", "Password", "Last Used"];
384
- const widths = [9, 42, 24, 22, 20];
385
- if (showProfile) {
386
- head.unshift("Profile");
387
- widths.unshift(12);
388
- }
389
- const table = makeTable(head, widths);
390
- for (const r of results) {
391
- const row = [
392
- chalk.blue(r.source ?? "?"),
393
- chalk.cyan(r.url),
394
- chalk.green(r.username || "<empty>"),
395
- chalk.yellow(r.password || "<empty>"),
396
- chalk.dim(r.lastUsed),
397
- ];
398
- if (showProfile)
399
- row.unshift(chalk.magenta(r.profile));
400
- table.push(row);
401
- }
402
- console.log(table.toString());
403
- printCount("password(s)", results.length);
404
- }
405
- /**
406
- * Explains an empty listing. A store that cannot be opened is skipped silently
407
- * during a normal read, which leaves "no passwords found" meaning either
408
- * "none saved" or "none readable" — this separates the two. Counts and errors
409
- * only: nothing here is decrypted.
410
- */
411
- function diagnosePasswords(opts) {
412
- const reports = describePasswordStores(opts);
413
- if (emit(opts, reports))
414
- return;
415
- if (reports.length === 0) {
416
- console.log(chalk.yellow("\n⚠ No profiles found to read.\n"));
417
- return;
418
- }
419
- console.log(chalk.bold("\n🔎 Password stores:\n"));
420
- const table = makeTable(["Root", "Profile", "Store", "Exists", "Rows", "Error"], [34, 12, 24, 7, 6, 40]);
421
- for (const r of reports) {
422
- table.push([
423
- chalk.dim(r.root),
424
- r.profile,
425
- chalk.blue(r.file),
426
- r.exists ? "yes" : chalk.yellow("no"),
427
- r.rows === null ? chalk.red("?") : String(r.rows),
428
- r.error ? chalk.red(r.error) : "",
429
- ]);
430
- }
431
- console.log(table.toString());
432
- console.log();
433
- }
434
- // ─── Bookmarks Command ──────────────────────────────────────────────────────
435
- const bookmarksCmd = program.command("bookmarks").description("List bookmarks");
436
- addSearchOption(addCommonOptions(bookmarksCmd))
437
- .option("--folder <name>", "Filter by folder name")
438
- .action((opts, cmd) => dispatch(handleBookmarks, scopeFromCli(cmd)));
439
- function handleBookmarks(opts) {
440
- renderResults(opts, getBookmarks(opts), "📚 Bookmarks", "bookmark(s)", ["Title", "URL", "Folder", "Date Added"], [30, 50, 25, 20], (r) => [
441
- chalk.white(r.title),
442
- chalk.cyan(r.url),
443
- chalk.dim(r.folder || "/"),
444
- chalk.dim(r.dateAdded),
445
- ]);
446
- }
447
- // ─── History Command ─────────────────────────────────────────────────────────
448
- const historyCmd = program.command("history").description("List browsing history");
449
- addSearchOption(addLimitOption(addCommonOptions(historyCmd))).action((opts, cmd) => dispatch((mergedOpts) => {
450
- renderResults(mergedOpts, getHistory(mergedOpts), "🕐 Browsing History", "history entry(ies)", ["URL", "Title", "Visits", "Last Visit"], [50, 30, 8, 22], (r) => [
451
- chalk.cyan(r.url),
452
- chalk.white(r.title || "—"),
453
- chalk.yellow(String(r.visitCount)),
454
- chalk.dim(r.lastVisit),
455
- ]);
456
- }, scopeFromCli(cmd)));
457
- // ─── Search Terms Command ────────────────────────────────────────────────────
458
- const searchTermsCmd = program.command("search-terms").description("List search terms from history");
459
- addLimitOption(addCommonOptions(searchTermsCmd)).action((opts, cmd) => dispatch((mergedOpts) => {
460
- renderResults(mergedOpts, getSearchTerms(mergedOpts), "🔍 Search Terms", "search term(s)", ["Term", "URL"], [40, 80], (r) => [chalk.white(r.term), chalk.dim(r.url)]);
461
- }, scopeFromCli(cmd)));
462
- // ─── Cookies Command ─────────────────────────────────────────────────────────
463
- const cookiesCmd = program.command("cookies").description("List cookies (encrypted values decrypted)");
464
- addLimitOption(addCommonOptions(cookiesCmd))
465
- .option("--domain <domain>", "Filter by domain")
466
- .option("--url <url>", "Filter by URL with path")
467
- .option("--no-decrypt", "Skip decryption of encrypted values")
468
- .action((opts, cmd) => dispatch((mergedOpts) => {
469
- renderResults(mergedOpts, getCookies(mergedOpts), "🍪 Cookies", "cookie(s)", ["Domain", "Name", "Value", "Secure", "Expires"], [30, 25, 30, 8, 22], (r) => [
470
- chalk.cyan(r.domain),
471
- chalk.white(r.name),
472
- chalk.dim(truncate(r.value, 50)),
473
- r.secure ? chalk.green("✓") : chalk.dim("✗"),
474
- chalk.dim(r.expires),
475
- ]);
476
- }, scopeFromCli(cmd)));
477
- // ─── Local Storage Command ──────────────────────────────────────────────────
478
- const localStorageCmd = program.command("local-storage").description("List localStorage entries");
479
- addSearchOption(addLimitOption(addCommonOptions(localStorageCmd)))
480
- .option("--origin <origin>", "Filter by origin")
481
- .option("--url <url>", "Filter by URL origin")
482
- .option("--key <key>", "Filter by localStorage key")
483
- .action((opts, cmd) => dispatch(handleLocalStorage, scopeFromCli(cmd)));
484
- function handleLocalStorage(opts) {
485
- renderResults(opts, getLocalStorage(opts), "💾 localStorage", "localStorage entries", ["Origin", "Key", "Value"], [36, 28, 46], (r) => [
486
- chalk.cyan(r.origin),
487
- chalk.white(r.key),
488
- chalk.dim(truncate(r.value, 80)),
489
- ]);
490
- }
491
- // ─── Autofill Command ────────────────────────────────────────────────────────
492
- const autofillCmd = program.command("autofill").description("List autofill entries");
493
- addSearchOption(addLimitOption(addCommonOptions(autofillCmd))).action((opts, cmd) => dispatch((mergedOpts) => {
494
- renderResults(mergedOpts, getAutofillEntries(mergedOpts), "📝 Autofill Entries", "autofill entry(ies)", ["Field Name", "Value", "Count", "Last Used"], [25, 40, 8, 22], (r) => [
495
- chalk.cyan(r.name),
496
- chalk.white(r.value),
497
- chalk.yellow(String(r.count)),
498
- chalk.dim(r.dateLastUsed),
499
- ]);
500
- }, scopeFromCli(cmd)));
501
- // ─── Credit Cards Command ────────────────────────────────────────────────────
502
- const creditCardsCmd = program.command("credit-cards").description("List saved credit cards");
503
- addCommonOptions(creditCardsCmd).action((opts, cmd) => dispatch((mergedOpts) => {
504
- renderResults(mergedOpts, getCreditCards(mergedOpts), "💳 Saved Credit Cards", "credit card(s)", ["Name on Card", "Card Number", "Expires", "Nickname"], [25, 25, 14, 20], (r) => [
505
- chalk.white(r.nameOnCard || "—"),
506
- chalk.yellow(r.cardNumber || "—"),
507
- chalk.dim(`${r.expirationMonth}/${r.expirationYear}`),
508
- chalk.dim(r.nickname || "—"),
509
- ]);
510
- }, scopeFromCli(cmd)));
511
- // ─── Addresses Command ───────────────────────────────────────────────────────
512
- const addressesCmd = program.command("addresses").description("List saved addresses");
513
- addCommonOptions(addressesCmd).action((opts, cmd) => dispatch((mergedOpts) => {
514
- renderResults(mergedOpts, getAddresses(mergedOpts), "🏠 Saved Addresses", "address(es)", ["Name", "Email", "Phone", "Address", "City/State/Zip"], [20, 25, 16, 30, 25], (r) => [
515
- chalk.white(r.fullName || "—"),
516
- chalk.cyan(r.email || "—"),
517
- chalk.dim(r.phone || "—"),
518
- chalk.white(r.streetAddress || "—"),
519
- chalk.dim([r.city, r.state, r.zipcode].filter(Boolean).join(", ") || "—"),
520
- ]);
521
- }, scopeFromCli(cmd)));
522
- // ─── Downloads Command ───────────────────────────────────────────────────────
523
- const downloadsCmd = program.command("downloads").description("List download history");
524
- addSearchOption(addLimitOption(addCommonOptions(downloadsCmd))).action((opts, cmd) => dispatch((mergedOpts) => {
525
- renderResults(mergedOpts, getDownloads(mergedOpts), "⬇️ Download History", "download(s)", ["File", "URL", "Size", "State", "Date"], [35, 40, 12, 12, 20], (r) => [
526
- chalk.white(r.targetPath ? r.targetPath.split(/[\\/]/).pop() || "" : "—"),
527
- chalk.cyan(truncate(r.url, 60)),
528
- chalk.dim(r.totalBytes > 0 ? formatBytes(r.totalBytes) : "—"),
529
- r.state === "complete" ? chalk.green(r.state) : chalk.yellow(r.state),
530
- chalk.dim(r.startTime),
531
- ]);
532
- }, scopeFromCli(cmd)));
533
- // ─── Extensions Command ─────────────────────────────────────────────────────
534
- const extensionsCmd = program.command("extensions").description("List installed extensions");
535
- addSearchOption(addCommonOptions(extensionsCmd)).action((opts, cmd) => dispatch((mergedOpts) => {
536
- renderResults(mergedOpts, getExtensions(mergedOpts), "🧩 Installed Extensions", "extension(s)", ["Name", "Version", "ID", "Enabled", "Webstore"], [30, 12, 36, 9, 9], (r) => [
537
- chalk.white(r.name),
538
- chalk.dim(r.version),
539
- chalk.dim(r.id),
540
- r.enabled ? chalk.green("✓") : chalk.red("✗"),
541
- r.fromWebstore ? chalk.green("✓") : chalk.dim("✗"),
542
- ]);
543
- }, scopeFromCli(cmd)));
544
- // ─── Search Engines Command ─────────────────────────────────────────────────
545
- const searchEnginesCmd = program.command("search-engines").description("List configured search engines");
546
- addCommonOptions(searchEnginesCmd).action((opts, cmd) => dispatch((mergedOpts) => {
547
- renderResults(mergedOpts, getSearchEngines(mergedOpts), "🔎 Search Engines", "search engine(s)", ["Name", "Keyword", "URL", "Active"], [20, 16, 60, 8], (r) => [
548
- chalk.white(r.name),
549
- chalk.cyan(r.keyword),
550
- chalk.dim(truncate(r.url, 80)),
551
- r.isActive ? chalk.green("✓") : chalk.dim("✗"),
552
- ]);
553
- }, scopeFromCli(cmd)));
554
- // ─── Top Sites Command ──────────────────────────────────────────────────────
555
- const topSitesCmd = program.command("top-sites").description("List top sites");
556
- addCommonOptions(topSitesCmd).action((opts, cmd) => dispatch((mergedOpts) => {
557
- renderResults(mergedOpts, getTopSites(mergedOpts), "⭐ Top Sites", "top site(s)", ["Rank", "Title", "URL"], [8, 30, 80], (r) => [
558
- chalk.yellow(String(r.rank + 1)),
559
- chalk.white(r.title || "—"),
560
- chalk.cyan(r.url),
561
- ]);
562
- }, scopeFromCli(cmd)));
563
- // ─── Utilities ───────────────────────────────────────────────────────────────
564
- function formatBytes(bytes) {
565
- if (bytes === 0)
566
- return "0 B";
567
- const units = ["B", "KB", "MB", "GB", "TB"];
568
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
569
- return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
570
- }
571
- function truncate(value, maxLength) {
572
- if (!value)
573
- return "—";
574
- return value.length > maxLength ? value.slice(0, maxLength) + "…" : value;
575
- }
576
- // ─── Run ─────────────────────────────────────────────────────────────────────
577
- try {
578
- program.parse();
579
- }
580
- catch (err) {
581
- // Print a clean, actionable message instead of dumping the minified source and
582
- // a Node stack trace for expected failures (bad --browser, keychain denial, …).
583
- // Set DEBUG=1 to see the full stack.
584
- if (!reportKeychainDenied(err)) {
585
- console.error(chalk.red(`\n✖ ${errorMessage(err)}\n`));
586
- }
587
- if (process.env.DEBUG)
588
- console.error(err);
589
- process.exit(1);
590
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * Chromium-browser registry — the single source of truth for what differs
3
- * between Chromium-based browsers (Chrome, Edge, Brave, Comet, …).
4
- *
5
- * Every supported browser is a Chromium fork, so the *decryption* is identical
6
- * per OS (AES-128-CBC + PBKDF2 on macOS/Linux, AES-256-GCM + DPAPI on Windows).
7
- * Only two things vary by browser:
8
- * 1. the default profile data directory, and
9
- * 2. the OS-keystore "Safe Storage" label (macOS Keychain service name /
10
- * Linux Secret Service application).
11
- *
12
- * "Browser" is therefore a small dimension orthogonal to "platform": the
13
- * platform backends (darwin/win32/linux) receive a browser descriptor and stay
14
- * otherwise unchanged.
15
- */
16
- export type BrowserId = "chrome" | "chrome-beta" | "chrome-canary" | "chromium" | "edge" | "brave" | "comet" | "vivaldi" | "opera";
17
- /** Subpaths to a browser's User Data dir, relative to each OS's app-data base. */
18
- export interface BrowserDirs {
19
- /** Under `~/Library/Application Support`. */
20
- darwin: string;
21
- /** Under the selected Windows app-data root. Includes any `User Data` segment. */
22
- win32: string;
23
- /** Windows application-data root. Chromium browsers normally use Local. */
24
- win32Base?: "local" | "roaming";
25
- /** Under `$XDG_CONFIG_HOME` (or `~/.config`). */
26
- linux: string;
27
- }
28
- export interface BrowserDescriptor {
29
- /** Stable identifier used on the CLI (`--browser <id>`) and in the API. */
30
- readonly id: BrowserId;
31
- /** Human-readable display name, e.g. "Microsoft Edge". */
32
- readonly name: string;
33
- /** Extra names accepted on the CLI (case-insensitive), e.g. "msedge". */
34
- readonly aliases?: readonly string[];
35
- /**
36
- * macOS Keychain service name (a.k.a. the "<Browser> Safe Storage" item) used
37
- * as the basis for the AES key. Also reused as a human label in messages.
38
- */
39
- readonly safeStorageLabel: string;
40
- /** Linux Secret Service `application` attribute used by `secret-tool lookup`. */
41
- readonly linuxSecretApp?: string;
42
- /** Per-OS User Data subpaths. */
43
- readonly dirs: BrowserDirs;
44
- }
45
- /**
46
- * Supported browsers. macOS paths/labels are verified; a few Windows/Linux
47
- * paths for niche browsers (Comet, Opera) are best-effort and easy to correct —
48
- * each is a single edit here, with no other code change required.
49
- */
50
- export declare const BROWSERS: Record<BrowserId, BrowserDescriptor>;
51
- /** All supported browsers, in a stable display order. */
52
- export declare function listBrowsers(): BrowserDescriptor[];
53
- /**
54
- * Resolves a browser by id, display name, or alias (case-insensitive).
55
- * @returns The descriptor, or `null` if unrecognized.
56
- */
57
- export declare function resolveBrowser(idOrName: string): BrowserDescriptor | null;
58
- /** Resolves a browser or throws an actionable error without changing global state. */
59
- export declare function requireBrowser(idOrName: string): BrowserDescriptor;
60
- /** The browser currently targeted by dir/key lookups. Defaults to Chrome. */
61
- export declare function getActiveBrowser(): BrowserDescriptor;
62
- /**
63
- * Sets the active browser for subsequent dir/key lookups.
64
- *
65
- * Caches in the platform backends are keyed by their real inputs (Keychain
66
- * service label, chromeDir, Linux secret app), so switching browsers never
67
- * returns a stale key — no cache flush is needed here.
68
- *
69
- * @param idOrName - Browser id/name/alias. Empty/undefined resets to Chrome.
70
- * @returns The now-active descriptor.
71
- * @throws If `idOrName` is non-empty but unrecognized.
72
- */
73
- export declare function setActiveBrowser(idOrName?: string): BrowserDescriptor;