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.
- package/dist/chrome_abe_decrypt.exe +0 -0
- package/dist/cli.js +8837 -0
- package/dist/index.d.ts +629 -0
- package/dist/index.js +2532 -0
- package/package.json +62 -29
- package/bin/chrome_abe_decrypt.exe +0 -0
- package/dist/bin/chrome_abe_decrypt.exe +0 -0
- package/dist/bin/cli.d.ts +0 -9
- package/dist/bin/cli.js +0 -590
- package/dist/src/core/browsers.d.ts +0 -73
- package/dist/src/core/browsers.js +0 -186
- package/dist/src/core/chrome.d.ts +0 -124
- package/dist/src/core/chrome.js +0 -259
- package/dist/src/core/crypto.d.ts +0 -52
- package/dist/src/core/crypto.js +0 -152
- package/dist/src/core/db.d.ts +0 -22
- package/dist/src/core/db.js +0 -227
- package/dist/src/core/keychain.d.ts +0 -57
- package/dist/src/core/keychain.js +0 -191
- package/dist/src/core/limit.d.ts +0 -4
- package/dist/src/core/limit.js +0 -12
- package/dist/src/core/platform/darwin.d.ts +0 -12
- package/dist/src/core/platform/darwin.js +0 -31
- package/dist/src/core/platform/index.d.ts +0 -30
- package/dist/src/core/platform/index.js +0 -69
- package/dist/src/core/platform/linux.d.ts +0 -10
- package/dist/src/core/platform/linux.js +0 -64
- package/dist/src/core/platform/types.d.ts +0 -45
- package/dist/src/core/platform/types.js +0 -1
- package/dist/src/core/platform/win32.d.ts +0 -13
- package/dist/src/core/platform/win32.js +0 -329
- package/dist/src/core/sql.d.ts +0 -2
- package/dist/src/core/sql.js +0 -4
- package/dist/src/index.d.ts +0 -28
- package/dist/src/index.js +0 -34
- package/dist/src/modules/autofill.d.ts +0 -68
- package/dist/src/modules/autofill.js +0 -170
- package/dist/src/modules/bookmarks.d.ts +0 -22
- package/dist/src/modules/bookmarks.js +0 -83
- package/dist/src/modules/cookies.d.ts +0 -40
- package/dist/src/modules/cookies.js +0 -163
- package/dist/src/modules/downloads.d.ts +0 -27
- package/dist/src/modules/downloads.js +0 -80
- package/dist/src/modules/extensions.d.ts +0 -24
- package/dist/src/modules/extensions.js +0 -71
- package/dist/src/modules/history.d.ts +0 -34
- package/dist/src/modules/history.js +0 -83
- package/dist/src/modules/local-storage.d.ts +0 -40
- package/dist/src/modules/local-storage.js +0 -455
- package/dist/src/modules/passwords.d.ts +0 -50
- package/dist/src/modules/passwords.js +0 -198
- package/dist/src/modules/search-engines.d.ts +0 -21
- package/dist/src/modules/search-engines.js +0 -38
- package/dist/src/modules/top-sites.d.ts +0 -17
- package/dist/src/modules/top-sites.js +0 -33
- package/src/native/build.bat +0 -31
- package/src/native/chrome_abe_decrypt.c +0 -336
- 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;
|