chrome-tools 1.0.5 → 1.0.6
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/bin/cli.d.ts +8 -0
- package/dist/bin/cli.js +543 -0
- package/dist/src/core/chrome.d.ts +77 -0
- package/dist/src/core/chrome.js +159 -0
- package/dist/src/core/crypto.d.ts +26 -0
- package/dist/src/core/crypto.js +69 -0
- package/dist/src/core/db.d.ts +19 -0
- package/dist/src/core/db.js +78 -0
- package/dist/src/core/keychain.d.ts +20 -0
- package/dist/src/core/keychain.js +53 -0
- package/dist/src/index.d.ts +25 -0
- package/{src → dist/src}/index.js +4 -26
- package/dist/src/modules/autofill.d.ts +72 -0
- package/dist/src/modules/autofill.js +178 -0
- package/dist/src/modules/bookmarks.d.ts +23 -0
- package/dist/src/modules/bookmarks.js +92 -0
- package/dist/src/modules/cookies.d.ts +40 -0
- package/dist/src/modules/cookies.js +159 -0
- package/dist/src/modules/downloads.d.ts +28 -0
- package/dist/src/modules/downloads.js +78 -0
- package/dist/src/modules/extensions.d.ts +25 -0
- package/dist/src/modules/extensions.js +70 -0
- package/dist/src/modules/history.d.ts +37 -0
- package/dist/src/modules/history.js +84 -0
- package/dist/src/modules/passwords.d.ts +20 -0
- package/dist/src/modules/passwords.js +50 -0
- package/dist/src/modules/search-engines.d.ts +22 -0
- package/dist/src/modules/search-engines.js +40 -0
- package/dist/src/modules/top-sites.d.ts +18 -0
- package/dist/src/modules/top-sites.js +35 -0
- package/package.json +24 -18
- package/bin/cli.js +0 -629
- package/src/core/chrome.js +0 -178
- package/src/core/crypto.js +0 -75
- package/src/core/db.js +0 -69
- package/src/core/keychain.js +0 -68
- package/src/modules/autofill.js +0 -213
- package/src/modules/bookmarks.js +0 -109
- package/src/modules/cookies.js +0 -178
- package/src/modules/downloads.js +0 -91
- package/src/modules/extensions.js +0 -80
- package/src/modules/history.js +0 -102
- package/src/modules/passwords.js +0 -61
- package/src/modules/search-engines.js +0 -48
- package/src/modules/top-sites.js +0 -43
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* chrome-tools CLI
|
|
4
|
+
*
|
|
5
|
+
* A comprehensive command-line interface for extracting Chrome browser data on macOS.
|
|
6
|
+
* Supports subcommands and shortcut flags for quick access to all data types.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import Table from "cli-table3";
|
|
13
|
+
import { discoverProfiles, getProfileMetadata, getProfileStates, getDefaultChromeDir } from "../src/core/chrome.js";
|
|
14
|
+
import { getPasswords } from "../src/modules/passwords.js";
|
|
15
|
+
import { getBookmarks } from "../src/modules/bookmarks.js";
|
|
16
|
+
import { getHistory, getSearchTerms } from "../src/modules/history.js";
|
|
17
|
+
import { getCookies } from "../src/modules/cookies.js";
|
|
18
|
+
import { getAutofillEntries, getCreditCards, getAddresses } from "../src/modules/autofill.js";
|
|
19
|
+
import { getDownloads } from "../src/modules/downloads.js";
|
|
20
|
+
import { getExtensions } from "../src/modules/extensions.js";
|
|
21
|
+
import { getSearchEngines } from "../src/modules/search-engines.js";
|
|
22
|
+
import { getTopSites } from "../src/modules/top-sites.js";
|
|
23
|
+
// ─── Environment Loading ─────────────────────────────────────────────────────
|
|
24
|
+
function loadEnv() {
|
|
25
|
+
const env = { ...process.env };
|
|
26
|
+
const envPath = join(process.cwd(), ".env");
|
|
27
|
+
if (existsSync(envPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(envPath, "utf8");
|
|
30
|
+
for (const line of content.split(/\r?\n/)) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
33
|
+
continue;
|
|
34
|
+
const index = trimmed.indexOf("=");
|
|
35
|
+
if (index !== -1) {
|
|
36
|
+
const key = trimmed.substring(0, index).trim();
|
|
37
|
+
let val = trimmed.substring(index + 1).trim();
|
|
38
|
+
// Remove wrapping quotes
|
|
39
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
40
|
+
val = val.slice(1, -1);
|
|
41
|
+
}
|
|
42
|
+
env[key] = val;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Ignore
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return env;
|
|
51
|
+
}
|
|
52
|
+
const env = loadEnv();
|
|
53
|
+
const defaultProfilePath = env.PROFILE_PATH || getDefaultChromeDir();
|
|
54
|
+
const defaultProfileName = env.PROFILE_NAME || undefined;
|
|
55
|
+
// ─── Output Helpers ──────────────────────────────────────────────────────────
|
|
56
|
+
function outputJSON(data) {
|
|
57
|
+
console.log(JSON.stringify(data, null, 2));
|
|
58
|
+
}
|
|
59
|
+
function makeTable(headers, colWidths) {
|
|
60
|
+
return new Table({
|
|
61
|
+
head: headers.map((h) => chalk.bold.white(h)),
|
|
62
|
+
colWidths,
|
|
63
|
+
wordWrap: true,
|
|
64
|
+
style: { border: ["grey"], head: [] },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function printCount(label, count) {
|
|
68
|
+
if (count === 0) {
|
|
69
|
+
console.log(chalk.yellow(`\n⚠ No ${label} found.\n`));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log(chalk.green(`\n✔ Found ${count} ${label}.\n`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ─── Shared Options ──────────────────────────────────────────────────────────
|
|
76
|
+
function addCommonOptions(cmd) {
|
|
77
|
+
return cmd
|
|
78
|
+
.option("--profile-path <path>", "Custom Chrome Profile Path", defaultProfilePath)
|
|
79
|
+
.option("--profile <name>", "Target a specific profile", defaultProfileName)
|
|
80
|
+
.option("--json", "Output as JSON");
|
|
81
|
+
}
|
|
82
|
+
function addSearchOption(cmd) {
|
|
83
|
+
return cmd.option("--search <term>", "Filter results (case-insensitive)");
|
|
84
|
+
}
|
|
85
|
+
function addLimitOption(cmd, defaultLimit = 100) {
|
|
86
|
+
return cmd.option("--limit <n>", "Limit number of results", String(defaultLimit));
|
|
87
|
+
}
|
|
88
|
+
// ─── Program Setup ───────────────────────────────────────────────────────────
|
|
89
|
+
const program = new Command();
|
|
90
|
+
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
91
|
+
program
|
|
92
|
+
.name("chrome-tools")
|
|
93
|
+
.description(chalk.bold.cyan("Chrome Data Toolkit") +
|
|
94
|
+
chalk.dim(" — Extract passwords, bookmarks, history, cookies, and more from Chrome on macOS"))
|
|
95
|
+
.version(pkg.version)
|
|
96
|
+
.option("--passwords", "Shortcut: list saved passwords")
|
|
97
|
+
.option("--bookmarks", "Shortcut: list bookmarks")
|
|
98
|
+
.option("--profiles", "Shortcut: list profiles")
|
|
99
|
+
.option("--profile-path <path>", "Custom Chrome Profile Path", defaultProfilePath)
|
|
100
|
+
.option("--profile <name>", "Target a specific profile", defaultProfileName)
|
|
101
|
+
.option("--json", "Output as JSON")
|
|
102
|
+
.option("--search <term>", "Filter results (for shortcut flags)")
|
|
103
|
+
.option("--limit <n>", "Limit results (for shortcut flags)");
|
|
104
|
+
// ─── Profiles Command ───────────────────────────────────────────────────────
|
|
105
|
+
const profilesCmd = program
|
|
106
|
+
.command("profiles")
|
|
107
|
+
.description("List Chrome profiles");
|
|
108
|
+
addCommonOptions(profilesCmd).action((opts, cmd) => {
|
|
109
|
+
handleProfiles(cmd.optsWithGlobals());
|
|
110
|
+
});
|
|
111
|
+
profilesCmd
|
|
112
|
+
.command("states")
|
|
113
|
+
.description("Show detailed profile states with available data files")
|
|
114
|
+
.option("--profile-path <path>", "Custom Chrome Profile Path", defaultProfilePath)
|
|
115
|
+
.option("--json", "Output as JSON")
|
|
116
|
+
.action((opts, cmd) => handleProfileStates(cmd.optsWithGlobals()));
|
|
117
|
+
function handleProfiles(opts) {
|
|
118
|
+
const profiles = discoverProfiles(opts.profilePath);
|
|
119
|
+
const metadata = getProfileMetadata(opts.profilePath);
|
|
120
|
+
if (opts.json) {
|
|
121
|
+
const metaMap = new Map(metadata.map((m) => [m.directory, m]));
|
|
122
|
+
const enriched = profiles.map((p) => ({
|
|
123
|
+
...p,
|
|
124
|
+
...(metaMap.get(p.name) || {}),
|
|
125
|
+
}));
|
|
126
|
+
return outputJSON(enriched);
|
|
127
|
+
}
|
|
128
|
+
if (profiles.length === 0) {
|
|
129
|
+
console.log(chalk.yellow("\n⚠ No Chrome profiles found.\n"));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const metaMap = new Map(metadata.map((m) => [m.directory, m]));
|
|
133
|
+
console.log(chalk.bold(`\n📁 Discovered ${profiles.length} profile(s):\n`));
|
|
134
|
+
const table = makeTable(["Directory", "Display Name", "Email", "Path"], [16, 22, 30, 50]);
|
|
135
|
+
for (const p of profiles) {
|
|
136
|
+
const meta = (metaMap.get(p.name) || {});
|
|
137
|
+
table.push([
|
|
138
|
+
chalk.cyan(p.name),
|
|
139
|
+
chalk.white(meta.name || p.name),
|
|
140
|
+
chalk.dim(meta.email || "—"),
|
|
141
|
+
chalk.dim(p.path),
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
console.log(table.toString());
|
|
145
|
+
printCount("profile(s)", profiles.length);
|
|
146
|
+
}
|
|
147
|
+
function handleProfileStates(opts) {
|
|
148
|
+
const states = getProfileStates(opts.profilePath);
|
|
149
|
+
if (opts.json) {
|
|
150
|
+
return outputJSON(states);
|
|
151
|
+
}
|
|
152
|
+
if (states.length === 0) {
|
|
153
|
+
console.log(chalk.yellow("\n⚠ No Chrome profiles found.\n"));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
console.log(chalk.bold(`\n📊 Profile States (${states.length} profile(s)):\n`));
|
|
157
|
+
for (const state of states) {
|
|
158
|
+
console.log(chalk.bold.cyan(` ${state.displayName}`) + chalk.dim(` (${state.directory})`));
|
|
159
|
+
if (state.email) {
|
|
160
|
+
console.log(chalk.dim(` Email: ${state.email}`));
|
|
161
|
+
}
|
|
162
|
+
console.log(chalk.dim(` Path: ${state.path}`));
|
|
163
|
+
const files = state.dataFiles;
|
|
164
|
+
const available = Object.entries(files)
|
|
165
|
+
.filter(([, exists]) => exists)
|
|
166
|
+
.map(([name]) => name);
|
|
167
|
+
const missing = Object.entries(files)
|
|
168
|
+
.filter(([, exists]) => !exists)
|
|
169
|
+
.map(([name]) => name);
|
|
170
|
+
console.log(` Available: ${available.map((f) => chalk.green(f)).join(", ") || chalk.dim("none")}`);
|
|
171
|
+
if (missing.length > 0) {
|
|
172
|
+
console.log(` Missing: ${missing.map((f) => chalk.dim(f)).join(", ")}`);
|
|
173
|
+
}
|
|
174
|
+
console.log();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ─── Passwords Command ──────────────────────────────────────────────────────
|
|
178
|
+
const passwordsCmd = program.command("passwords").description("List saved passwords");
|
|
179
|
+
addSearchOption(addCommonOptions(passwordsCmd)).action((opts, cmd) => handlePasswords(cmd.optsWithGlobals()));
|
|
180
|
+
function handlePasswords(opts) {
|
|
181
|
+
const results = getPasswords({
|
|
182
|
+
chromeDir: opts.profilePath,
|
|
183
|
+
profile: opts.profile,
|
|
184
|
+
search: opts.search,
|
|
185
|
+
});
|
|
186
|
+
if (opts.json)
|
|
187
|
+
return outputJSON(results);
|
|
188
|
+
if (results.length === 0) {
|
|
189
|
+
return printCount("passwords", 0);
|
|
190
|
+
}
|
|
191
|
+
console.log(chalk.bold("\n🔑 Saved Passwords:\n"));
|
|
192
|
+
const showProfile = new Set(results.map((r) => r.profile)).size > 1;
|
|
193
|
+
const head = showProfile
|
|
194
|
+
? ["Profile", "URL", "Username", "Password", "Last Used"]
|
|
195
|
+
: ["URL", "Username", "Password", "Last Used"];
|
|
196
|
+
const widths = showProfile ? [12, 40, 24, 22, 20] : [45, 28, 25, 22];
|
|
197
|
+
const table = makeTable(head, widths);
|
|
198
|
+
for (const r of results) {
|
|
199
|
+
const row = [
|
|
200
|
+
chalk.cyan(r.url),
|
|
201
|
+
chalk.green(r.username || "<empty>"),
|
|
202
|
+
chalk.yellow(r.password || "<empty>"),
|
|
203
|
+
chalk.dim(r.lastUsed),
|
|
204
|
+
];
|
|
205
|
+
if (showProfile)
|
|
206
|
+
row.unshift(chalk.magenta(r.profile));
|
|
207
|
+
table.push(row);
|
|
208
|
+
}
|
|
209
|
+
console.log(table.toString());
|
|
210
|
+
printCount("password(s)", results.length);
|
|
211
|
+
}
|
|
212
|
+
// ─── Bookmarks Command ──────────────────────────────────────────────────────
|
|
213
|
+
const bookmarksCmd = program.command("bookmarks").description("List bookmarks");
|
|
214
|
+
addSearchOption(addCommonOptions(bookmarksCmd))
|
|
215
|
+
.option("--folder <name>", "Filter by folder name")
|
|
216
|
+
.action((opts, cmd) => handleBookmarks(cmd.optsWithGlobals()));
|
|
217
|
+
function handleBookmarks(opts) {
|
|
218
|
+
const results = getBookmarks({
|
|
219
|
+
chromeDir: opts.profilePath,
|
|
220
|
+
profile: opts.profile,
|
|
221
|
+
folder: opts.folder,
|
|
222
|
+
search: opts.search,
|
|
223
|
+
});
|
|
224
|
+
if (opts.json)
|
|
225
|
+
return outputJSON(results);
|
|
226
|
+
if (results.length === 0) {
|
|
227
|
+
return printCount("bookmarks", 0);
|
|
228
|
+
}
|
|
229
|
+
console.log(chalk.bold("\n📚 Bookmarks:\n"));
|
|
230
|
+
const table = makeTable(["Title", "URL", "Folder", "Date Added"], [30, 50, 25, 20]);
|
|
231
|
+
for (const r of results) {
|
|
232
|
+
table.push([
|
|
233
|
+
chalk.white(r.title),
|
|
234
|
+
chalk.cyan(r.url),
|
|
235
|
+
chalk.dim(r.folder || "/"),
|
|
236
|
+
chalk.dim(r.dateAdded),
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
console.log(table.toString());
|
|
240
|
+
printCount("bookmark(s)", results.length);
|
|
241
|
+
}
|
|
242
|
+
// ─── History Command ─────────────────────────────────────────────────────────
|
|
243
|
+
const historyCmd = program.command("history").description("List browsing history");
|
|
244
|
+
addSearchOption(addLimitOption(addCommonOptions(historyCmd))).action((opts, cmd) => {
|
|
245
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
246
|
+
const results = getHistory({
|
|
247
|
+
chromeDir: mergedOpts.profilePath,
|
|
248
|
+
profile: mergedOpts.profile,
|
|
249
|
+
search: mergedOpts.search,
|
|
250
|
+
limit: parseInt(mergedOpts.limit, 10) || 100,
|
|
251
|
+
});
|
|
252
|
+
if (mergedOpts.json)
|
|
253
|
+
return outputJSON(results);
|
|
254
|
+
if (results.length === 0) {
|
|
255
|
+
return printCount("history entries", 0);
|
|
256
|
+
}
|
|
257
|
+
console.log(chalk.bold("\n🕐 Browsing History:\n"));
|
|
258
|
+
const table = makeTable(["URL", "Title", "Visits", "Last Visit"], [50, 30, 8, 22]);
|
|
259
|
+
for (const r of results) {
|
|
260
|
+
table.push([
|
|
261
|
+
chalk.cyan(r.url),
|
|
262
|
+
chalk.white(r.title || "—"),
|
|
263
|
+
chalk.yellow(String(r.visitCount)),
|
|
264
|
+
chalk.dim(r.lastVisit),
|
|
265
|
+
]);
|
|
266
|
+
}
|
|
267
|
+
console.log(table.toString());
|
|
268
|
+
printCount("history entry(ies)", results.length);
|
|
269
|
+
});
|
|
270
|
+
// ─── Search Terms Command ────────────────────────────────────────────────────
|
|
271
|
+
const searchTermsCmd = program.command("search-terms").description("List search terms from history");
|
|
272
|
+
addLimitOption(addCommonOptions(searchTermsCmd)).action((opts, cmd) => {
|
|
273
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
274
|
+
const results = getSearchTerms({
|
|
275
|
+
chromeDir: mergedOpts.profilePath,
|
|
276
|
+
profile: mergedOpts.profile,
|
|
277
|
+
limit: parseInt(mergedOpts.limit, 10) || 100,
|
|
278
|
+
});
|
|
279
|
+
if (mergedOpts.json)
|
|
280
|
+
return outputJSON(results);
|
|
281
|
+
if (results.length === 0) {
|
|
282
|
+
return printCount("search terms", 0);
|
|
283
|
+
}
|
|
284
|
+
console.log(chalk.bold("\n🔍 Search Terms:\n"));
|
|
285
|
+
const table = makeTable(["Term", "URL"], [40, 80]);
|
|
286
|
+
for (const r of results) {
|
|
287
|
+
table.push([chalk.white(r.term), chalk.dim(r.url)]);
|
|
288
|
+
}
|
|
289
|
+
console.log(table.toString());
|
|
290
|
+
printCount("search term(s)", results.length);
|
|
291
|
+
});
|
|
292
|
+
// ─── Cookies Command ─────────────────────────────────────────────────────────
|
|
293
|
+
const cookiesCmd = program.command("cookies").description("List cookies (encrypted values decrypted)");
|
|
294
|
+
addLimitOption(addCommonOptions(cookiesCmd))
|
|
295
|
+
.option("--domain <domain>", "Filter by domain")
|
|
296
|
+
.option("--url <url>", "Filter by URL with path")
|
|
297
|
+
.option("--no-decrypt", "Skip decryption of encrypted values")
|
|
298
|
+
.action((opts, cmd) => {
|
|
299
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
300
|
+
const results = getCookies({
|
|
301
|
+
chromeDir: mergedOpts.profilePath,
|
|
302
|
+
profile: mergedOpts.profile,
|
|
303
|
+
domain: mergedOpts.domain,
|
|
304
|
+
url: mergedOpts.url,
|
|
305
|
+
decrypt: mergedOpts.decrypt !== false,
|
|
306
|
+
limit: parseInt(mergedOpts.limit, 10) || 100,
|
|
307
|
+
});
|
|
308
|
+
if (mergedOpts.json)
|
|
309
|
+
return outputJSON(results);
|
|
310
|
+
if (results.length === 0) {
|
|
311
|
+
return printCount("cookies", 0);
|
|
312
|
+
}
|
|
313
|
+
console.log(chalk.bold("\n🍪 Cookies:\n"));
|
|
314
|
+
const table = makeTable(["Domain", "Name", "Value", "Secure", "Expires"], [30, 25, 30, 8, 22]);
|
|
315
|
+
for (const r of results) {
|
|
316
|
+
table.push([
|
|
317
|
+
chalk.cyan(r.domain),
|
|
318
|
+
chalk.white(r.name),
|
|
319
|
+
chalk.dim(r.value ? r.value.substring(0, 50) + (r.value.length > 50 ? "…" : "") : "—"),
|
|
320
|
+
r.secure ? chalk.green("✓") : chalk.dim("✗"),
|
|
321
|
+
chalk.dim(r.expires),
|
|
322
|
+
]);
|
|
323
|
+
}
|
|
324
|
+
console.log(table.toString());
|
|
325
|
+
printCount("cookie(s)", results.length);
|
|
326
|
+
});
|
|
327
|
+
// ─── Autofill Command ────────────────────────────────────────────────────────
|
|
328
|
+
const autofillCmd = program.command("autofill").description("List autofill entries");
|
|
329
|
+
addSearchOption(addLimitOption(addCommonOptions(autofillCmd))).action((opts, cmd) => {
|
|
330
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
331
|
+
const results = getAutofillEntries({
|
|
332
|
+
chromeDir: mergedOpts.profilePath,
|
|
333
|
+
profile: mergedOpts.profile,
|
|
334
|
+
search: mergedOpts.search,
|
|
335
|
+
limit: parseInt(mergedOpts.limit, 10) || 100,
|
|
336
|
+
});
|
|
337
|
+
if (mergedOpts.json)
|
|
338
|
+
return outputJSON(results);
|
|
339
|
+
if (results.length === 0) {
|
|
340
|
+
return printCount("autofill entries", 0);
|
|
341
|
+
}
|
|
342
|
+
console.log(chalk.bold("\n📝 Autofill Entries:\n"));
|
|
343
|
+
const table = makeTable(["Field Name", "Value", "Count", "Last Used"], [25, 40, 8, 22]);
|
|
344
|
+
for (const r of results) {
|
|
345
|
+
table.push([
|
|
346
|
+
chalk.cyan(r.name),
|
|
347
|
+
chalk.white(r.value),
|
|
348
|
+
chalk.yellow(String(r.count)),
|
|
349
|
+
chalk.dim(r.dateLastUsed),
|
|
350
|
+
]);
|
|
351
|
+
}
|
|
352
|
+
console.log(table.toString());
|
|
353
|
+
printCount("autofill entry(ies)", results.length);
|
|
354
|
+
});
|
|
355
|
+
// ─── Credit Cards Command ────────────────────────────────────────────────────
|
|
356
|
+
const creditCardsCmd = program.command("credit-cards").description("List saved credit cards");
|
|
357
|
+
addCommonOptions(creditCardsCmd).action((opts, cmd) => {
|
|
358
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
359
|
+
const results = getCreditCards({
|
|
360
|
+
chromeDir: mergedOpts.profilePath,
|
|
361
|
+
profile: mergedOpts.profile,
|
|
362
|
+
});
|
|
363
|
+
if (mergedOpts.json)
|
|
364
|
+
return outputJSON(results);
|
|
365
|
+
if (results.length === 0) {
|
|
366
|
+
return printCount("credit cards", 0);
|
|
367
|
+
}
|
|
368
|
+
console.log(chalk.bold("\n💳 Saved Credit Cards:\n"));
|
|
369
|
+
const table = makeTable(["Name on Card", "Card Number", "Expires", "Nickname"], [25, 25, 14, 20]);
|
|
370
|
+
for (const r of results) {
|
|
371
|
+
table.push([
|
|
372
|
+
chalk.white(r.nameOnCard || "—"),
|
|
373
|
+
chalk.yellow(r.cardNumber || "—"),
|
|
374
|
+
chalk.dim(`${r.expirationMonth}/${r.expirationYear}`),
|
|
375
|
+
chalk.dim(r.nickname || "—"),
|
|
376
|
+
]);
|
|
377
|
+
}
|
|
378
|
+
console.log(table.toString());
|
|
379
|
+
printCount("credit card(s)", results.length);
|
|
380
|
+
});
|
|
381
|
+
// ─── Addresses Command ───────────────────────────────────────────────────────
|
|
382
|
+
const addressesCmd = program.command("addresses").description("List saved addresses");
|
|
383
|
+
addCommonOptions(addressesCmd).action((opts, cmd) => {
|
|
384
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
385
|
+
const results = getAddresses({
|
|
386
|
+
chromeDir: mergedOpts.profilePath,
|
|
387
|
+
profile: mergedOpts.profile,
|
|
388
|
+
});
|
|
389
|
+
if (mergedOpts.json)
|
|
390
|
+
return outputJSON(results);
|
|
391
|
+
if (results.length === 0) {
|
|
392
|
+
return printCount("addresses", 0);
|
|
393
|
+
}
|
|
394
|
+
console.log(chalk.bold("\n🏠 Saved Addresses:\n"));
|
|
395
|
+
const table = makeTable(["Name", "Email", "Phone", "Address", "City/State/Zip"], [20, 25, 16, 30, 25]);
|
|
396
|
+
for (const r of results) {
|
|
397
|
+
table.push([
|
|
398
|
+
chalk.white(r.fullName || "—"),
|
|
399
|
+
chalk.cyan(r.email || "—"),
|
|
400
|
+
chalk.dim(r.phone || "—"),
|
|
401
|
+
chalk.white(r.streetAddress || "—"),
|
|
402
|
+
chalk.dim([r.city, r.state, r.zipcode].filter(Boolean).join(", ") || "—"),
|
|
403
|
+
]);
|
|
404
|
+
}
|
|
405
|
+
console.log(table.toString());
|
|
406
|
+
printCount("address(es)", results.length);
|
|
407
|
+
});
|
|
408
|
+
// ─── Downloads Command ───────────────────────────────────────────────────────
|
|
409
|
+
const downloadsCmd = program.command("downloads").description("List download history");
|
|
410
|
+
addSearchOption(addLimitOption(addCommonOptions(downloadsCmd))).action((opts, cmd) => {
|
|
411
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
412
|
+
const results = getDownloads({
|
|
413
|
+
chromeDir: mergedOpts.profilePath,
|
|
414
|
+
profile: mergedOpts.profile,
|
|
415
|
+
search: mergedOpts.search,
|
|
416
|
+
limit: parseInt(mergedOpts.limit, 10) || 100,
|
|
417
|
+
});
|
|
418
|
+
if (mergedOpts.json)
|
|
419
|
+
return outputJSON(results);
|
|
420
|
+
if (results.length === 0) {
|
|
421
|
+
return printCount("downloads", 0);
|
|
422
|
+
}
|
|
423
|
+
console.log(chalk.bold("\n⬇️ Download History:\n"));
|
|
424
|
+
const table = makeTable(["File", "URL", "Size", "State", "Date"], [35, 40, 12, 12, 20]);
|
|
425
|
+
for (const r of results) {
|
|
426
|
+
const filename = r.targetPath ? r.targetPath.split("/").pop() : "—";
|
|
427
|
+
const sizeStr = r.totalBytes > 0 ? formatBytes(r.totalBytes) : "—";
|
|
428
|
+
table.push([
|
|
429
|
+
chalk.white(filename || ""),
|
|
430
|
+
chalk.cyan(r.url ? r.url.substring(0, 60) : "—"),
|
|
431
|
+
chalk.dim(sizeStr),
|
|
432
|
+
r.state === "complete" ? chalk.green(r.state) : chalk.yellow(r.state),
|
|
433
|
+
chalk.dim(r.startTime),
|
|
434
|
+
]);
|
|
435
|
+
}
|
|
436
|
+
console.log(table.toString());
|
|
437
|
+
printCount("download(s)", results.length);
|
|
438
|
+
});
|
|
439
|
+
// ─── Extensions Command ─────────────────────────────────────────────────────
|
|
440
|
+
const extensionsCmd = program.command("extensions").description("List installed extensions");
|
|
441
|
+
addSearchOption(addCommonOptions(extensionsCmd)).action((opts, cmd) => {
|
|
442
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
443
|
+
const results = getExtensions({
|
|
444
|
+
chromeDir: mergedOpts.profilePath,
|
|
445
|
+
profile: mergedOpts.profile,
|
|
446
|
+
search: mergedOpts.search,
|
|
447
|
+
});
|
|
448
|
+
if (mergedOpts.json)
|
|
449
|
+
return outputJSON(results);
|
|
450
|
+
if (results.length === 0) {
|
|
451
|
+
return printCount("extensions", 0);
|
|
452
|
+
}
|
|
453
|
+
console.log(chalk.bold("\n🧩 Installed Extensions:\n"));
|
|
454
|
+
const table = makeTable(["Name", "Version", "ID", "Enabled", "Webstore"], [30, 12, 36, 9, 9]);
|
|
455
|
+
for (const r of results) {
|
|
456
|
+
table.push([
|
|
457
|
+
chalk.white(r.name),
|
|
458
|
+
chalk.dim(r.version),
|
|
459
|
+
chalk.dim(r.id),
|
|
460
|
+
r.enabled ? chalk.green("✓") : chalk.red("✗"),
|
|
461
|
+
r.fromWebstore ? chalk.green("✓") : chalk.dim("✗"),
|
|
462
|
+
]);
|
|
463
|
+
}
|
|
464
|
+
console.log(table.toString());
|
|
465
|
+
printCount("extension(s)", results.length);
|
|
466
|
+
});
|
|
467
|
+
// ─── Search Engines Command ─────────────────────────────────────────────────
|
|
468
|
+
const searchEnginesCmd = program.command("search-engines").description("List configured search engines");
|
|
469
|
+
addCommonOptions(searchEnginesCmd).action((opts, cmd) => {
|
|
470
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
471
|
+
const results = getSearchEngines({
|
|
472
|
+
chromeDir: mergedOpts.profilePath,
|
|
473
|
+
profile: mergedOpts.profile,
|
|
474
|
+
});
|
|
475
|
+
if (mergedOpts.json)
|
|
476
|
+
return outputJSON(results);
|
|
477
|
+
if (results.length === 0) {
|
|
478
|
+
return printCount("search engines", 0);
|
|
479
|
+
}
|
|
480
|
+
console.log(chalk.bold("\n🔎 Search Engines:\n"));
|
|
481
|
+
const table = makeTable(["Name", "Keyword", "URL", "Active"], [20, 16, 60, 8]);
|
|
482
|
+
for (const r of results) {
|
|
483
|
+
table.push([
|
|
484
|
+
chalk.white(r.name),
|
|
485
|
+
chalk.cyan(r.keyword),
|
|
486
|
+
chalk.dim(r.url ? r.url.substring(0, 80) : "—"),
|
|
487
|
+
r.isActive ? chalk.green("✓") : chalk.dim("✗"),
|
|
488
|
+
]);
|
|
489
|
+
}
|
|
490
|
+
console.log(table.toString());
|
|
491
|
+
printCount("search engine(s)", results.length);
|
|
492
|
+
});
|
|
493
|
+
// ─── Top Sites Command ──────────────────────────────────────────────────────
|
|
494
|
+
const topSitesCmd = program.command("top-sites").description("List top sites");
|
|
495
|
+
addCommonOptions(topSitesCmd).action((opts, cmd) => {
|
|
496
|
+
const mergedOpts = cmd.optsWithGlobals();
|
|
497
|
+
const results = getTopSites({
|
|
498
|
+
chromeDir: mergedOpts.profilePath,
|
|
499
|
+
profile: mergedOpts.profile,
|
|
500
|
+
});
|
|
501
|
+
if (mergedOpts.json)
|
|
502
|
+
return outputJSON(results);
|
|
503
|
+
if (results.length === 0) {
|
|
504
|
+
return printCount("top sites", 0);
|
|
505
|
+
}
|
|
506
|
+
console.log(chalk.bold("\n⭐ Top Sites:\n"));
|
|
507
|
+
const table = makeTable(["Rank", "Title", "URL"], [8, 30, 80]);
|
|
508
|
+
for (const r of results) {
|
|
509
|
+
table.push([
|
|
510
|
+
chalk.yellow(String(r.rank + 1)),
|
|
511
|
+
chalk.white(r.title || "—"),
|
|
512
|
+
chalk.cyan(r.url),
|
|
513
|
+
]);
|
|
514
|
+
}
|
|
515
|
+
console.log(table.toString());
|
|
516
|
+
printCount("top site(s)", results.length);
|
|
517
|
+
});
|
|
518
|
+
// ─── Utilities ───────────────────────────────────────────────────────────────
|
|
519
|
+
function formatBytes(bytes) {
|
|
520
|
+
if (bytes === 0)
|
|
521
|
+
return "0 B";
|
|
522
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
523
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
524
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
|
|
525
|
+
}
|
|
526
|
+
// ─── Run ─────────────────────────────────────────────────────────────────────
|
|
527
|
+
program.parse();
|
|
528
|
+
const rootOpts = program.opts();
|
|
529
|
+
const hasSubcommand = program.args.length > 0;
|
|
530
|
+
if (!hasSubcommand) {
|
|
531
|
+
if (rootOpts.passwords) {
|
|
532
|
+
handlePasswords(rootOpts);
|
|
533
|
+
}
|
|
534
|
+
else if (rootOpts.bookmarks) {
|
|
535
|
+
handleBookmarks(rootOpts);
|
|
536
|
+
}
|
|
537
|
+
else if (rootOpts.profiles) {
|
|
538
|
+
handleProfiles(rootOpts);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
program.help();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export interface DiscoveredProfile {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ProfileMetadata {
|
|
6
|
+
directory: string;
|
|
7
|
+
name: string;
|
|
8
|
+
gaiaName: string;
|
|
9
|
+
userName: string;
|
|
10
|
+
email: string;
|
|
11
|
+
avatarIcon: string;
|
|
12
|
+
isUsingDefaultName: boolean;
|
|
13
|
+
isEphemeral: boolean;
|
|
14
|
+
activeTime: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ProfileState {
|
|
17
|
+
directory: string;
|
|
18
|
+
path: string;
|
|
19
|
+
displayName: string;
|
|
20
|
+
gaiaName: string;
|
|
21
|
+
email: string;
|
|
22
|
+
avatarIcon: string;
|
|
23
|
+
isEphemeral: boolean;
|
|
24
|
+
activeTime: number;
|
|
25
|
+
dataFiles: Record<string, boolean>;
|
|
26
|
+
}
|
|
27
|
+
export interface ResolveProfilesOptions {
|
|
28
|
+
profilePath?: string;
|
|
29
|
+
chromeDir?: string;
|
|
30
|
+
profile?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Returns the default Chrome User Data directory for macOS.
|
|
34
|
+
* @returns The absolute path to Chrome's User Data.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getDefaultChromeDir(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Scans the Chrome User Data directory for profile subdirectories.
|
|
39
|
+
* A directory is considered a profile if it contains at least one known data file.
|
|
40
|
+
*
|
|
41
|
+
* @param chromeDir - Chrome User Data path. Defaults to standard macOS location.
|
|
42
|
+
* @returns Discovered profiles with name and absolute path.
|
|
43
|
+
*/
|
|
44
|
+
export declare function discoverProfiles(chromeDir?: string): DiscoveredProfile[];
|
|
45
|
+
/**
|
|
46
|
+
* Parses the Chrome "Local State" JSON file to extract profile metadata.
|
|
47
|
+
* Returns enriched profile info including display name, email, avatar, etc.
|
|
48
|
+
*
|
|
49
|
+
* @param chromeDir - Chrome User Data path.
|
|
50
|
+
* @returns Profile metadata objects.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getProfileMetadata(chromeDir?: string): ProfileMetadata[];
|
|
53
|
+
/**
|
|
54
|
+
* Combines filesystem profile discovery with Local State metadata
|
|
55
|
+
* to provide a comprehensive view of each profile's state.
|
|
56
|
+
*
|
|
57
|
+
* @param chromeDir - Chrome User Data path.
|
|
58
|
+
* @returns Enriched profile state objects.
|
|
59
|
+
*/
|
|
60
|
+
export declare function getProfileStates(chromeDir?: string): ProfileState[];
|
|
61
|
+
/**
|
|
62
|
+
* Resolves a profile name to its directory path.
|
|
63
|
+
*
|
|
64
|
+
* @param chromeDir - Chrome User Data path.
|
|
65
|
+
* @param profileName - Profile directory name (e.g., "Default", "Profile 1").
|
|
66
|
+
* @returns Absolute path to profile directory, or null if not found.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveProfilePath(chromeDir: string, profileName: string): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Helper to get the profiles to operate on based on options.
|
|
71
|
+
* If a specific profile is specified, returns only that one.
|
|
72
|
+
* Otherwise returns all discovered profiles.
|
|
73
|
+
*
|
|
74
|
+
* @param options - Resolution options.
|
|
75
|
+
* @returns Array of targeted profiles.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveTargetProfiles({ profilePath, chromeDir, profile, }?: ResolveProfilesOptions): DiscoveredProfile[];
|