chrome-tools 1.0.1

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