chrome-tools 1.0.6 → 1.0.8
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/README.md +1 -246
- package/dist/bin/cli.js +1 -542
- package/dist/src/core/chrome.js +1 -159
- package/dist/src/core/crypto.js +1 -69
- package/dist/src/core/db.js +1 -78
- package/dist/src/core/keychain.js +1 -53
- package/dist/src/index.js +1 -27
- package/dist/src/modules/autofill.js +1 -178
- package/dist/src/modules/bookmarks.js +1 -92
- package/dist/src/modules/cookies.js +1 -159
- package/dist/src/modules/downloads.js +1 -78
- package/dist/src/modules/extensions.js +1 -70
- package/dist/src/modules/history.js +1 -84
- package/dist/src/modules/passwords.js +1 -50
- package/dist/src/modules/search-engines.js +1 -40
- package/dist/src/modules/top-sites.js +1 -35
- package/package.json +4 -3
package/dist/bin/cli.js
CHANGED
|
@@ -1,543 +1,2 @@
|
|
|
1
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
|
-
}
|
|
2
|
+
import{readFileSync as o,existsSync as e}from"node:fs";import{join as t}from"node:path";import{Command as s}from"commander";import i from"chalk";import r from"cli-table3";import{discoverProfiles as n,getProfileMetadata as l,getProfileStates as a,getDefaultChromeDir as c}from"../src/core/chrome.js";import{getPasswords as m}from"../src/modules/passwords.js";import{getBookmarks as p}from"../src/modules/bookmarks.js";import{getHistory as d,getSearchTerms as f}from"../src/modules/history.js";import{getCookies as h}from"../src/modules/cookies.js";import{getAutofillEntries as u,getCreditCards as g,getAddresses as b}from"../src/modules/autofill.js";import{getDownloads as y}from"../src/modules/downloads.js";import{getExtensions as w}from"../src/modules/extensions.js";import{getSearchEngines as S}from"../src/modules/search-engines.js";import{getTopSites as k}from"../src/modules/top-sites.js";const P=function(){const s={...process.env},i=t(process.cwd(),".env");if(e(i))try{const e=o(i,"utf8");for(const o of e.split(/\r?\n/)){const e=o.trim();if(!e||e.startsWith("#"))continue;const t=e.indexOf("=");if(-1!==t){const o=e.substring(0,t).trim();let i=e.substring(t+1).trim();(i.startsWith('"')&&i.endsWith('"')||i.startsWith("'")&&i.endsWith("'"))&&(i=i.slice(1,-1)),s[o]=i}}}catch{}return s}(),j=P.PROFILE_PATH||c(),L=P.PROFILE_NAME||void 0;function v(o){console.log(JSON.stringify(o,null,2))}function W(o,e){return new r({head:o.map(o=>i.bold.white(o)),colWidths:e,wordWrap:!0,style:{border:["grey"],head:[]}})}function D(o,e){0===e?console.log(i.yellow(`\n⚠ No ${o} found.\n`)):console.log(i.green(`\n✔ Found ${e} ${o}.\n`))}function N(o){return o.option("--profile-path <path>","Custom Chrome Profile Path",j).option("--profile <name>","Target a specific profile",L).option("--json","Output as JSON")}function C(o){return o.option("--search <term>","Filter results (case-insensitive)")}function U(o,e=100){return o.option("--limit <n>","Limit number of results",String(e))}const G=new s,O=JSON.parse(o(new URL("../../package.json",import.meta.url),"utf8"));G.name("chrome-tools").description(i.bold.cyan("Chrome Data Toolkit")+i.dim(" — Extract passwords, bookmarks, history, cookies, and more from Chrome on macOS")).version(O.version).option("--passwords","Shortcut: list saved passwords").option("--bookmarks","Shortcut: list bookmarks").option("--profiles","Shortcut: list profiles").option("--profile-path <path>","Custom Chrome Profile Path",j).option("--profile <name>","Target a specific profile",L).option("--json","Output as JSON").option("--search <term>","Filter results (for shortcut flags)").option("--limit <n>","Limit results (for shortcut flags)");const $=G.command("profiles").description("List Chrome profiles");function x(o){const e=n(o.profilePath),t=l(o.profilePath);if(o.json){const o=new Map(t.map(o=>[o.directory,o]));return v(e.map(e=>({...e,...o.get(e.name)||{}})))}if(0===e.length)return void console.log(i.yellow("\n⚠ No Chrome profiles found.\n"));const s=new Map(t.map(o=>[o.directory,o]));console.log(i.bold(`\n📁 Discovered ${e.length} profile(s):\n`));const r=W(["Directory","Display Name","Email","Path"],[16,22,30,50]);for(const o of e){const e=s.get(o.name)||{};r.push([i.cyan(o.name),i.white(e.name||o.name),i.dim(e.email||"—"),i.dim(o.path)])}console.log(r.toString()),D("profile(s)",e.length)}N($).action((o,e)=>{x(e.optsWithGlobals())}),$.command("states").description("Show detailed profile states with available data files").option("--profile-path <path>","Custom Chrome Profile Path",j).option("--json","Output as JSON").action((o,e)=>function(o){const e=a(o.profilePath);if(o.json)return v(e);if(0===e.length)return void console.log(i.yellow("\n⚠ No Chrome profiles found.\n"));console.log(i.bold(`\n📊 Profile States (${e.length} profile(s)):\n`));for(const o of e){console.log(i.bold.cyan(` ${o.displayName}`)+i.dim(` (${o.directory})`)),o.email&&console.log(i.dim(` Email: ${o.email}`)),console.log(i.dim(` Path: ${o.path}`));const e=o.dataFiles,t=Object.entries(e).filter(([,o])=>o).map(([o])=>o),s=Object.entries(e).filter(([,o])=>!o).map(([o])=>o);console.log(` Available: ${t.map(o=>i.green(o)).join(", ")||i.dim("none")}`),s.length>0&&console.log(` Missing: ${s.map(o=>i.dim(o)).join(", ")}`),console.log()}}(e.optsWithGlobals()));function E(o){const e=m({chromeDir:o.profilePath,profile:o.profile,search:o.search});if(o.json)return v(e);if(0===e.length)return D("passwords",0);console.log(i.bold("\n🔑 Saved Passwords:\n"));const t=new Set(e.map(o=>o.profile)).size>1,s=W(t?["Profile","URL","Username","Password","Last Used"]:["URL","Username","Password","Last Used"],t?[12,40,24,22,20]:[45,28,25,22]);for(const o of e){const e=[i.cyan(o.url),i.green(o.username||"<empty>"),i.yellow(o.password||"<empty>"),i.dim(o.lastUsed)];t&&e.unshift(i.magenta(o.profile)),s.push(e)}console.log(s.toString()),D("password(s)",e.length)}C(N(G.command("passwords").description("List saved passwords"))).action((o,e)=>E(e.optsWithGlobals()));function F(o){const e=p({chromeDir:o.profilePath,profile:o.profile,folder:o.folder,search:o.search});if(o.json)return v(e);if(0===e.length)return D("bookmarks",0);console.log(i.bold("\n📚 Bookmarks:\n"));const t=W(["Title","URL","Folder","Date Added"],[30,50,25,20]);for(const o of e)t.push([i.white(o.title),i.cyan(o.url),i.dim(o.folder||"/"),i.dim(o.dateAdded)]);console.log(t.toString()),D("bookmark(s)",e.length)}C(N(G.command("bookmarks").description("List bookmarks"))).option("--folder <name>","Filter by folder name").action((o,e)=>F(e.optsWithGlobals()));C(U(N(G.command("history").description("List browsing history")))).action((o,e)=>{const t=e.optsWithGlobals(),s=d({chromeDir:t.profilePath,profile:t.profile,search:t.search,limit:parseInt(t.limit,10)||100});if(t.json)return v(s);if(0===s.length)return D("history entries",0);console.log(i.bold("\n🕐 Browsing History:\n"));const r=W(["URL","Title","Visits","Last Visit"],[50,30,8,22]);for(const o of s)r.push([i.cyan(o.url),i.white(o.title||"—"),i.yellow(String(o.visitCount)),i.dim(o.lastVisit)]);console.log(r.toString()),D("history entry(ies)",s.length)});U(N(G.command("search-terms").description("List search terms from history"))).action((o,e)=>{const t=e.optsWithGlobals(),s=f({chromeDir:t.profilePath,profile:t.profile,limit:parseInt(t.limit,10)||100});if(t.json)return v(s);if(0===s.length)return D("search terms",0);console.log(i.bold("\n🔍 Search Terms:\n"));const r=W(["Term","URL"],[40,80]);for(const o of s)r.push([i.white(o.term),i.dim(o.url)]);console.log(r.toString()),D("search term(s)",s.length)});U(N(G.command("cookies").description("List cookies (encrypted values decrypted)"))).option("--domain <domain>","Filter by domain").option("--url <url>","Filter by URL with path").option("--no-decrypt","Skip decryption of encrypted values").action((o,e)=>{const t=e.optsWithGlobals(),s=h({chromeDir:t.profilePath,profile:t.profile,domain:t.domain,url:t.url,decrypt:!1!==t.decrypt,limit:parseInt(t.limit,10)||100});if(t.json)return v(s);if(0===s.length)return D("cookies",0);console.log(i.bold("\n🍪 Cookies:\n"));const r=W(["Domain","Name","Value","Secure","Expires"],[30,25,30,8,22]);for(const o of s)r.push([i.cyan(o.domain),i.white(o.name),i.dim(o.value?o.value.substring(0,50)+(o.value.length>50?"…":""):"—"),o.secure?i.green("✓"):i.dim("✗"),i.dim(o.expires)]);console.log(r.toString()),D("cookie(s)",s.length)});C(U(N(G.command("autofill").description("List autofill entries")))).action((o,e)=>{const t=e.optsWithGlobals(),s=u({chromeDir:t.profilePath,profile:t.profile,search:t.search,limit:parseInt(t.limit,10)||100});if(t.json)return v(s);if(0===s.length)return D("autofill entries",0);console.log(i.bold("\n📝 Autofill Entries:\n"));const r=W(["Field Name","Value","Count","Last Used"],[25,40,8,22]);for(const o of s)r.push([i.cyan(o.name),i.white(o.value),i.yellow(String(o.count)),i.dim(o.dateLastUsed)]);console.log(r.toString()),D("autofill entry(ies)",s.length)});N(G.command("credit-cards").description("List saved credit cards")).action((o,e)=>{const t=e.optsWithGlobals(),s=g({chromeDir:t.profilePath,profile:t.profile});if(t.json)return v(s);if(0===s.length)return D("credit cards",0);console.log(i.bold("\n💳 Saved Credit Cards:\n"));const r=W(["Name on Card","Card Number","Expires","Nickname"],[25,25,14,20]);for(const o of s)r.push([i.white(o.nameOnCard||"—"),i.yellow(o.cardNumber||"—"),i.dim(`${o.expirationMonth}/${o.expirationYear}`),i.dim(o.nickname||"—")]);console.log(r.toString()),D("credit card(s)",s.length)});N(G.command("addresses").description("List saved addresses")).action((o,e)=>{const t=e.optsWithGlobals(),s=b({chromeDir:t.profilePath,profile:t.profile});if(t.json)return v(s);if(0===s.length)return D("addresses",0);console.log(i.bold("\n🏠 Saved Addresses:\n"));const r=W(["Name","Email","Phone","Address","City/State/Zip"],[20,25,16,30,25]);for(const o of s)r.push([i.white(o.fullName||"—"),i.cyan(o.email||"—"),i.dim(o.phone||"—"),i.white(o.streetAddress||"—"),i.dim([o.city,o.state,o.zipcode].filter(Boolean).join(", ")||"—")]);console.log(r.toString()),D("address(es)",s.length)});C(U(N(G.command("downloads").description("List download history")))).action((o,e)=>{const t=e.optsWithGlobals(),s=y({chromeDir:t.profilePath,profile:t.profile,search:t.search,limit:parseInt(t.limit,10)||100});if(t.json)return v(s);if(0===s.length)return D("downloads",0);console.log(i.bold("\n⬇️ Download History:\n"));const r=W(["File","URL","Size","State","Date"],[35,40,12,12,20]);for(const o of s){const e=o.targetPath?o.targetPath.split("/").pop():"—",t=o.totalBytes>0?R(o.totalBytes):"—";r.push([i.white(e||""),i.cyan(o.url?o.url.substring(0,60):"—"),i.dim(t),"complete"===o.state?i.green(o.state):i.yellow(o.state),i.dim(o.startTime)])}console.log(r.toString()),D("download(s)",s.length)});C(N(G.command("extensions").description("List installed extensions"))).action((o,e)=>{const t=e.optsWithGlobals(),s=w({chromeDir:t.profilePath,profile:t.profile,search:t.search});if(t.json)return v(s);if(0===s.length)return D("extensions",0);console.log(i.bold("\n🧩 Installed Extensions:\n"));const r=W(["Name","Version","ID","Enabled","Webstore"],[30,12,36,9,9]);for(const o of s)r.push([i.white(o.name),i.dim(o.version),i.dim(o.id),o.enabled?i.green("✓"):i.red("✗"),o.fromWebstore?i.green("✓"):i.dim("✗")]);console.log(r.toString()),D("extension(s)",s.length)});N(G.command("search-engines").description("List configured search engines")).action((o,e)=>{const t=e.optsWithGlobals(),s=S({chromeDir:t.profilePath,profile:t.profile});if(t.json)return v(s);if(0===s.length)return D("search engines",0);console.log(i.bold("\n🔎 Search Engines:\n"));const r=W(["Name","Keyword","URL","Active"],[20,16,60,8]);for(const o of s)r.push([i.white(o.name),i.cyan(o.keyword),i.dim(o.url?o.url.substring(0,80):"—"),o.isActive?i.green("✓"):i.dim("✗")]);console.log(r.toString()),D("search engine(s)",s.length)});function R(o){if(0===o)return"0 B";const e=Math.floor(Math.log(o)/Math.log(1024));return`${(o/Math.pow(1024,e)).toFixed(1)} ${["B","KB","MB","GB","TB"][e]}`}N(G.command("top-sites").description("List top sites")).action((o,e)=>{const t=e.optsWithGlobals(),s=k({chromeDir:t.profilePath,profile:t.profile});if(t.json)return v(s);if(0===s.length)return D("top sites",0);console.log(i.bold("\n⭐ Top Sites:\n"));const r=W(["Rank","Title","URL"],[8,30,80]);for(const o of s)r.push([i.yellow(String(o.rank+1)),i.white(o.title||"—"),i.cyan(o.url)]);console.log(r.toString()),D("top site(s)",s.length)}),G.parse();const T=G.opts();G.args.length>0||(T.passwords?E(T):T.bookmarks?F(T):T.profiles?x(T):G.help());
|