chrome-tools 1.0.4 → 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/README.md +24 -23
- 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 -595
- package/src/core/chrome.js +0 -177
- 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/README.md
CHANGED
|
@@ -80,8 +80,8 @@ npx chrome-tools passwords
|
|
|
80
80
|
# Filter credentials using a search term (matches URL or username)
|
|
81
81
|
npx chrome-tools passwords --search "gmail"
|
|
82
82
|
|
|
83
|
-
# Target a specific profile directory only
|
|
84
|
-
npx chrome-tools
|
|
83
|
+
# Target a specific profile directory only (placed before the subcommand)
|
|
84
|
+
npx chrome-tools --profile "Profile 1" passwords
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
#### Bookmarks
|
|
@@ -149,20 +149,20 @@ npx chrome-tools top-sites
|
|
|
149
149
|
|
|
150
150
|
### Global Options
|
|
151
151
|
|
|
152
|
-
These options apply globally to all subcommands and
|
|
152
|
+
These options apply globally to all subcommands and should be placed before the command (e.g. `npx chrome-tools --profile-path <path> --profile <name> cookies --url <url>`):
|
|
153
153
|
|
|
154
|
-
- `--
|
|
155
|
-
- `--profile <name>`:
|
|
154
|
+
- `--profile-path <path>`: Specify a custom Chrome User Data/profile folder (e.g., a backed-up profile from another machine). Defaults to the `PROFILE_PATH` env/`.env` variable, or falls back to the standard macOS Chrome User Data location.
|
|
155
|
+
- `--profile <name>`: Target a specific profile directory (e.g., `Default`, `Profile 1`). Defaults to the `PROFILE_NAME` env/`.env` variable.
|
|
156
156
|
- `--json`: Output strictly in JSON format (ideal for programmatic pipes or exporting to files).
|
|
157
157
|
- `--limit <number>`: Restrict the output results count (for history, autofill, cookies, etc.).
|
|
158
158
|
|
|
159
159
|
#### Custom Directory & JSON Examples
|
|
160
160
|
```bash
|
|
161
161
|
# Export bookmarks from a backed-up Chrome folder directly to a JSON file
|
|
162
|
-
npx chrome-tools
|
|
162
|
+
npx chrome-tools --profile-path "/path/to/backup/Chrome" bookmarks --json > bookmarks.json
|
|
163
163
|
|
|
164
164
|
# Retrieve all history from a specific profile in JSON format
|
|
165
|
-
npx chrome-tools
|
|
165
|
+
npx chrome-tools --profile-path "/path/to/backup/Chrome" --profile "Profile 2" history --json --limit 10
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
---
|
|
@@ -187,7 +187,7 @@ console.log(profiles);
|
|
|
187
187
|
|
|
188
188
|
// 2. Fetch bookmarks programmatically
|
|
189
189
|
const githubBookmarks = getBookmarks({
|
|
190
|
-
|
|
190
|
+
profilePath: "/path/to/chrome/User Data",
|
|
191
191
|
search: "github"
|
|
192
192
|
});
|
|
193
193
|
|
|
@@ -214,27 +214,28 @@ for (const cred of credentials) {
|
|
|
214
214
|
### Available API Modules & Signatures
|
|
215
215
|
|
|
216
216
|
All core functions accept an options object with the following fields:
|
|
217
|
-
- `
|
|
217
|
+
- `profilePath` *(string)*: Custom Chrome User Data/profile directory (alias: `chromeDir`).
|
|
218
|
+
- `chromeDir` *(string)*: Alias for `profilePath`.
|
|
218
219
|
- `profile` *(string)*: Specific profile subdirectory name (e.g., `"Default"`).
|
|
219
220
|
- `search` *(string)*: Case-insensitive filter term.
|
|
220
221
|
- `limit` *(number)*: Maximum returned rows count.
|
|
221
222
|
|
|
222
223
|
| Function | Module Path | Returns |
|
|
223
224
|
| --- | --- | --- |
|
|
224
|
-
| `discoverProfiles(
|
|
225
|
-
| `getProfileStates(
|
|
226
|
-
| `getPasswords({chromeDir, profile, search})` | `chrome-tools/passwords` | `Array<Credentials>` |
|
|
227
|
-
| `getBookmarks({chromeDir, profile, folder, search})` | `chrome-tools/bookmarks` | `Array<Bookmark>` |
|
|
228
|
-
| `getHistory({chromeDir, profile, search, limit})` | `chrome-tools/history` | `Array<HistoryEntry>` |
|
|
229
|
-
| `getSearchTerms({chromeDir, profile, limit})` | `chrome-tools/history` | `Array<SearchTerm>` |
|
|
230
|
-
| `getCookies({chromeDir, profile, domain, url, decrypt, limit})` | `chrome-tools/cookies` | `Array<Cookie>` |
|
|
231
|
-
| `getAutofillEntries({chromeDir, profile, search, limit})` | `chrome-tools/autofill` | `Array<AutofillEntry>` |
|
|
232
|
-
| `getCreditCards({chromeDir, profile})` | `chrome-tools/autofill` | `Array<CreditCard>` |
|
|
233
|
-
| `getAddresses({chromeDir, profile})` | `chrome-tools/autofill` | `Array<Address>` |
|
|
234
|
-
| `getDownloads({chromeDir, profile, search, limit})` | `chrome-tools/downloads` | `Array<Download>` |
|
|
235
|
-
| `getExtensions({chromeDir, profile, search})` | `chrome-tools/extensions` | `Array<Extension>` |
|
|
236
|
-
| `getSearchEngines({chromeDir, profile})` | `chrome-tools/search-engines` | `Array<SearchEngine>` |
|
|
237
|
-
| `getTopSites({chromeDir, profile})` | `chrome-tools/top-sites` | `Array<TopSite>` |
|
|
225
|
+
| `discoverProfiles(profilePath)` | `chrome-tools` | `Array<{name, path}>` |
|
|
226
|
+
| `getProfileStates(profilePath)` | `chrome-tools` | `Array<ProfileState>` |
|
|
227
|
+
| `getPasswords({profilePath, chromeDir, profile, search})` | `chrome-tools/passwords` | `Array<Credentials>` |
|
|
228
|
+
| `getBookmarks({profilePath, chromeDir, profile, folder, search})` | `chrome-tools/bookmarks` | `Array<Bookmark>` |
|
|
229
|
+
| `getHistory({profilePath, chromeDir, profile, search, limit})` | `chrome-tools/history` | `Array<HistoryEntry>` |
|
|
230
|
+
| `getSearchTerms({profilePath, chromeDir, profile, limit})` | `chrome-tools/history` | `Array<SearchTerm>` |
|
|
231
|
+
| `getCookies({profilePath, chromeDir, profile, domain, url, decrypt, limit})` | `chrome-tools/cookies` | `Array<Cookie>` |
|
|
232
|
+
| `getAutofillEntries({profilePath, chromeDir, profile, search, limit})` | `chrome-tools/autofill` | `Array<AutofillEntry>` |
|
|
233
|
+
| `getCreditCards({profilePath, chromeDir, profile})` | `chrome-tools/autofill` | `Array<CreditCard>` |
|
|
234
|
+
| `getAddresses({profilePath, chromeDir, profile})` | `chrome-tools/autofill` | `Array<Address>` |
|
|
235
|
+
| `getDownloads({profilePath, chromeDir, profile, search, limit})` | `chrome-tools/downloads` | `Array<Download>` |
|
|
236
|
+
| `getExtensions({profilePath, chromeDir, profile, search})` | `chrome-tools/extensions` | `Array<Extension>` |
|
|
237
|
+
| `getSearchEngines({profilePath, chromeDir, profile})` | `chrome-tools/search-engines` | `Array<SearchEngine>` |
|
|
238
|
+
| `getTopSites({profilePath, chromeDir, profile})` | `chrome-tools/top-sites` | `Array<TopSite>` |
|
|
238
239
|
|
|
239
240
|
---
|
|
240
241
|
|
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
|
+
}
|