chrome-tools 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/bin/cli.d.ts +8 -0
  2. package/dist/bin/cli.js +543 -0
  3. package/dist/src/core/chrome.d.ts +77 -0
  4. package/dist/src/core/chrome.js +159 -0
  5. package/dist/src/core/crypto.d.ts +26 -0
  6. package/dist/src/core/crypto.js +69 -0
  7. package/dist/src/core/db.d.ts +19 -0
  8. package/dist/src/core/db.js +78 -0
  9. package/dist/src/core/keychain.d.ts +20 -0
  10. package/dist/src/core/keychain.js +53 -0
  11. package/dist/src/index.d.ts +25 -0
  12. package/{src → dist/src}/index.js +4 -26
  13. package/dist/src/modules/autofill.d.ts +72 -0
  14. package/dist/src/modules/autofill.js +178 -0
  15. package/dist/src/modules/bookmarks.d.ts +23 -0
  16. package/dist/src/modules/bookmarks.js +92 -0
  17. package/dist/src/modules/cookies.d.ts +40 -0
  18. package/dist/src/modules/cookies.js +159 -0
  19. package/dist/src/modules/downloads.d.ts +28 -0
  20. package/dist/src/modules/downloads.js +78 -0
  21. package/dist/src/modules/extensions.d.ts +25 -0
  22. package/dist/src/modules/extensions.js +70 -0
  23. package/dist/src/modules/history.d.ts +37 -0
  24. package/dist/src/modules/history.js +84 -0
  25. package/dist/src/modules/passwords.d.ts +20 -0
  26. package/dist/src/modules/passwords.js +50 -0
  27. package/dist/src/modules/search-engines.d.ts +22 -0
  28. package/dist/src/modules/search-engines.js +40 -0
  29. package/dist/src/modules/top-sites.d.ts +18 -0
  30. package/dist/src/modules/top-sites.js +35 -0
  31. package/package.json +24 -18
  32. package/bin/cli.js +0 -629
  33. package/src/core/chrome.js +0 -178
  34. package/src/core/crypto.js +0 -75
  35. package/src/core/db.js +0 -69
  36. package/src/core/keychain.js +0 -68
  37. package/src/modules/autofill.js +0 -213
  38. package/src/modules/bookmarks.js +0 -109
  39. package/src/modules/cookies.js +0 -178
  40. package/src/modules/downloads.js +0 -91
  41. package/src/modules/extensions.js +0 -80
  42. package/src/modules/history.js +0 -102
  43. package/src/modules/passwords.js +0 -61
  44. package/src/modules/search-engines.js +0 -48
  45. package/src/modules/top-sites.js +0 -43
@@ -0,0 +1,70 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { resolveTargetProfiles } from "../core/chrome.js";
4
+ /**
5
+ * Retrieves installed Chrome extensions from the Preferences JSON file.
6
+ *
7
+ * @param options - Targeting and filtering options.
8
+ * @returns Array of extension entries.
9
+ */
10
+ export function getExtensions({ chromeDir, profile, search } = {}) {
11
+ const profiles = resolveTargetProfiles({ chromeDir, profile });
12
+ const results = [];
13
+ for (const p of profiles) {
14
+ // Try Secure Preferences first, fallback to Preferences
15
+ let prefs = null;
16
+ for (const filename of ["Secure Preferences", "Preferences"]) {
17
+ const prefsPath = join(p.path, filename);
18
+ if (existsSync(prefsPath)) {
19
+ try {
20
+ prefs = JSON.parse(readFileSync(prefsPath, "utf-8"));
21
+ break;
22
+ }
23
+ catch {
24
+ continue;
25
+ }
26
+ }
27
+ }
28
+ if (!prefs)
29
+ continue;
30
+ const settings = prefs?.extensions?.settings;
31
+ if (!settings || typeof settings !== "object")
32
+ continue;
33
+ for (const [extId, extDataAny] of Object.entries(settings)) {
34
+ const extData = extDataAny;
35
+ // Skip component extensions and internal Chrome extensions
36
+ if (extData.location === 5 || extData.location === 10)
37
+ continue;
38
+ const manifest = extData.manifest || {};
39
+ const entry = {
40
+ profile: p.name,
41
+ id: extId,
42
+ name: manifest.name || extData.name || extId,
43
+ version: manifest.version || extData.version || "unknown",
44
+ description: manifest.description || "",
45
+ enabled: extData.state === 1,
46
+ homepageUrl: manifest.homepage_url || "",
47
+ updateUrl: manifest.update_url || "",
48
+ installTime: extData.install_time || "",
49
+ fromWebstore: extData.from_webstore || false,
50
+ location: extData.location || 0,
51
+ };
52
+ // Clean up __MSG_ placeholders in name/description
53
+ if (entry.name.startsWith("__MSG_")) {
54
+ entry.name = extId; // Fallback to ID if name is a message key
55
+ }
56
+ if (entry.description.startsWith("__MSG_")) {
57
+ entry.description = "";
58
+ }
59
+ if (search) {
60
+ const term = search.toLowerCase();
61
+ if (!(entry.name && entry.name.toLowerCase().includes(term)) &&
62
+ !(entry.id && entry.id.toLowerCase().includes(term))) {
63
+ continue;
64
+ }
65
+ }
66
+ results.push(entry);
67
+ }
68
+ }
69
+ return results;
70
+ }
@@ -0,0 +1,37 @@
1
+ export interface HistoryOptions {
2
+ chromeDir?: string;
3
+ profile?: string;
4
+ search?: string;
5
+ limit?: number;
6
+ }
7
+ export interface HistoryEntry {
8
+ profile: string;
9
+ url: string;
10
+ title: string;
11
+ visitCount: number;
12
+ lastVisit: string;
13
+ }
14
+ export interface SearchTermOptions {
15
+ chromeDir?: string;
16
+ profile?: string;
17
+ limit?: number;
18
+ }
19
+ export interface SearchTermEntry {
20
+ profile: string;
21
+ term: string;
22
+ url: string;
23
+ }
24
+ /**
25
+ * Retrieves browsing history from Chrome's History database.
26
+ *
27
+ * @param options - Filtering and targeting options.
28
+ * @returns Array of history entries.
29
+ */
30
+ export declare function getHistory({ chromeDir, profile, search, limit }?: HistoryOptions): HistoryEntry[];
31
+ /**
32
+ * Retrieves search terms from Chrome's History database.
33
+ *
34
+ * @param options - Targeting and limit options.
35
+ * @returns Array of search term entries.
36
+ */
37
+ export declare function getSearchTerms({ chromeDir, profile, limit }?: SearchTermOptions): SearchTermEntry[];
@@ -0,0 +1,84 @@
1
+ import { join } from "node:path";
2
+ import { resolveTargetProfiles } from "../core/chrome.js";
3
+ import { chromeTimeToDate } from "../core/crypto.js";
4
+ import { queryDatabase, tableExists } from "../core/db.js";
5
+ /**
6
+ * Retrieves browsing history from Chrome's History database.
7
+ *
8
+ * @param options - Filtering and targeting options.
9
+ * @returns Array of history entries.
10
+ */
11
+ export function getHistory({ chromeDir, profile, search, limit = 100 } = {}) {
12
+ const profiles = resolveTargetProfiles({ chromeDir, profile });
13
+ const results = [];
14
+ for (const p of profiles) {
15
+ const dbPath = join(p.path, "History");
16
+ try {
17
+ if (!tableExists(dbPath, "urls"))
18
+ continue;
19
+ let sql = `SELECT url, title, visit_count, last_visit_time
20
+ FROM urls ORDER BY last_visit_time DESC`;
21
+ if (limit) {
22
+ sql += ` LIMIT ${parseInt(limit, 10)}`;
23
+ }
24
+ const rows = queryDatabase(dbPath, sql);
25
+ for (const row of rows) {
26
+ const entry = {
27
+ profile: p.name,
28
+ url: row.url,
29
+ title: row.title || "",
30
+ visitCount: row.visit_count || 0,
31
+ lastVisit: chromeTimeToDate(row.last_visit_time || 0),
32
+ };
33
+ if (search) {
34
+ const term = search.toLowerCase();
35
+ if (!(entry.url && entry.url.toLowerCase().includes(term)) &&
36
+ !(entry.title && entry.title.toLowerCase().includes(term))) {
37
+ continue;
38
+ }
39
+ }
40
+ results.push(entry);
41
+ }
42
+ }
43
+ catch {
44
+ // Skip profiles with errors
45
+ }
46
+ }
47
+ return results;
48
+ }
49
+ /**
50
+ * Retrieves search terms from Chrome's History database.
51
+ *
52
+ * @param options - Targeting and limit options.
53
+ * @returns Array of search term entries.
54
+ */
55
+ export function getSearchTerms({ chromeDir, profile, limit = 100 } = {}) {
56
+ const profiles = resolveTargetProfiles({ chromeDir, profile });
57
+ const results = [];
58
+ for (const p of profiles) {
59
+ const dbPath = join(p.path, "History");
60
+ try {
61
+ if (!tableExists(dbPath, "keyword_search_terms"))
62
+ continue;
63
+ let sql = `SELECT kst.term, u.url
64
+ FROM keyword_search_terms kst
65
+ JOIN urls u ON kst.url_id = u.id
66
+ ORDER BY u.last_visit_time DESC`;
67
+ if (limit) {
68
+ sql += ` LIMIT ${parseInt(limit, 10)}`;
69
+ }
70
+ const rows = queryDatabase(dbPath, sql);
71
+ for (const row of rows) {
72
+ results.push({
73
+ profile: p.name,
74
+ term: row.term || "",
75
+ url: row.url || "",
76
+ });
77
+ }
78
+ }
79
+ catch {
80
+ // Skip profiles with errors
81
+ }
82
+ }
83
+ return results;
84
+ }
@@ -0,0 +1,20 @@
1
+ export interface PasswordOptions {
2
+ chromeDir?: string;
3
+ profile?: string;
4
+ search?: string;
5
+ }
6
+ export interface PasswordEntry {
7
+ profile: string;
8
+ url?: string;
9
+ username?: string;
10
+ password?: string;
11
+ lastUsed?: string;
12
+ error?: string;
13
+ }
14
+ /**
15
+ * Extracts and decrypts saved passwords from Chrome's Login Data database.
16
+ *
17
+ * @param options - Options for profile selection and filtering.
18
+ * @returns Array of decrypted password entries.
19
+ */
20
+ export declare function getPasswords({ chromeDir, profile, search }?: PasswordOptions): PasswordEntry[];
@@ -0,0 +1,50 @@
1
+ import { join } from "node:path";
2
+ import { resolveTargetProfiles } from "../core/chrome.js";
3
+ import { deriveEncryptionKey } from "../core/keychain.js";
4
+ import { decryptValue, chromeTimeToDate } from "../core/crypto.js";
5
+ import { queryDatabase, tableExists } from "../core/db.js";
6
+ /**
7
+ * Extracts and decrypts saved passwords from Chrome's Login Data database.
8
+ *
9
+ * @param options - Options for profile selection and filtering.
10
+ * @returns Array of decrypted password entries.
11
+ */
12
+ export function getPasswords({ chromeDir, profile, search } = {}) {
13
+ const profiles = resolveTargetProfiles({ chromeDir, profile });
14
+ const derivedKey = deriveEncryptionKey();
15
+ const results = [];
16
+ for (const p of profiles) {
17
+ const dbPath = join(p.path, "Login Data");
18
+ try {
19
+ if (!tableExists(dbPath, "logins"))
20
+ continue;
21
+ const rows = queryDatabase(dbPath, `SELECT origin_url, username_value, password_value, date_last_used
22
+ FROM logins ORDER BY origin_url`);
23
+ for (const row of rows) {
24
+ const entry = {
25
+ profile: p.name,
26
+ url: row.origin_url,
27
+ username: row.username_value,
28
+ password: decryptValue(row.password_value, derivedKey),
29
+ lastUsed: chromeTimeToDate(row.date_last_used || 0),
30
+ };
31
+ if (search) {
32
+ const term = search.toLowerCase();
33
+ if (!(entry.url && entry.url.toLowerCase().includes(term)) &&
34
+ !(entry.username && entry.username.toLowerCase().includes(term))) {
35
+ continue;
36
+ }
37
+ }
38
+ results.push(entry);
39
+ }
40
+ }
41
+ catch (err) {
42
+ // Skip profiles with errors, continue with others
43
+ results.push({
44
+ profile: p.name,
45
+ error: err.message,
46
+ });
47
+ }
48
+ }
49
+ return results.filter((r) => !r.error);
50
+ }
@@ -0,0 +1,22 @@
1
+ export interface SearchEngineOptions {
2
+ chromeDir?: string;
3
+ profile?: string;
4
+ }
5
+ export interface SearchEngineEntry {
6
+ profile: string;
7
+ id: number;
8
+ name: string;
9
+ keyword: string;
10
+ url: string;
11
+ suggestionsUrl: string;
12
+ faviconUrl: string;
13
+ isDefault: boolean;
14
+ isActive: boolean;
15
+ }
16
+ /**
17
+ * Retrieves search engines configured in Chrome from the Web Data database.
18
+ *
19
+ * @param options - Targeting options.
20
+ * @returns Array of search engine entries.
21
+ */
22
+ export declare function getSearchEngines({ chromeDir, profile }?: SearchEngineOptions): SearchEngineEntry[];
@@ -0,0 +1,40 @@
1
+ import { join } from "node:path";
2
+ import { resolveTargetProfiles } from "../core/chrome.js";
3
+ import { queryDatabase, tableExists } from "../core/db.js";
4
+ /**
5
+ * Retrieves search engines configured in Chrome from the Web Data database.
6
+ *
7
+ * @param options - Targeting options.
8
+ * @returns Array of search engine entries.
9
+ */
10
+ export function getSearchEngines({ chromeDir, profile } = {}) {
11
+ const profiles = resolveTargetProfiles({ chromeDir, profile });
12
+ const results = [];
13
+ for (const p of profiles) {
14
+ const dbPath = join(p.path, "Web Data");
15
+ try {
16
+ if (!tableExists(dbPath, "keywords"))
17
+ continue;
18
+ const rows = queryDatabase(dbPath, `SELECT id, short_name, keyword, url, suggestions_url,
19
+ favicon_url, prepopulate_id, is_active
20
+ FROM keywords ORDER BY short_name`);
21
+ for (const row of rows) {
22
+ results.push({
23
+ profile: p.name,
24
+ id: row.id,
25
+ name: row.short_name || "",
26
+ keyword: row.keyword || "",
27
+ url: row.url || "",
28
+ suggestionsUrl: row.suggestions_url || "",
29
+ faviconUrl: row.favicon_url || "",
30
+ isDefault: row.prepopulate_id === 1,
31
+ isActive: row.is_active === 1,
32
+ });
33
+ }
34
+ }
35
+ catch {
36
+ // Skip profiles with errors
37
+ }
38
+ }
39
+ return results;
40
+ }
@@ -0,0 +1,18 @@
1
+ export interface TopSiteOptions {
2
+ chromeDir?: string;
3
+ profile?: string;
4
+ }
5
+ export interface TopSiteEntry {
6
+ profile: string;
7
+ url: string;
8
+ title: string;
9
+ rank: number;
10
+ redirects: string;
11
+ }
12
+ /**
13
+ * Retrieves top sites from Chrome's Top Sites database.
14
+ *
15
+ * @param options - Targeting options.
16
+ * @returns Array of top site entries.
17
+ */
18
+ export declare function getTopSites({ chromeDir, profile }?: TopSiteOptions): TopSiteEntry[];
@@ -0,0 +1,35 @@
1
+ import { join } from "node:path";
2
+ import { resolveTargetProfiles } from "../core/chrome.js";
3
+ import { queryDatabase, tableExists } from "../core/db.js";
4
+ /**
5
+ * Retrieves top sites from Chrome's Top Sites database.
6
+ *
7
+ * @param options - Targeting options.
8
+ * @returns Array of top site entries.
9
+ */
10
+ export function getTopSites({ chromeDir, profile } = {}) {
11
+ const profiles = resolveTargetProfiles({ chromeDir, profile });
12
+ const results = [];
13
+ for (const p of profiles) {
14
+ const dbPath = join(p.path, "Top Sites");
15
+ try {
16
+ if (!tableExists(dbPath, "top_sites"))
17
+ continue;
18
+ const rows = queryDatabase(dbPath, `SELECT url, url_rank, title, redirects
19
+ FROM top_sites ORDER BY url_rank ASC`);
20
+ for (const row of rows) {
21
+ results.push({
22
+ profile: p.name,
23
+ url: row.url || "",
24
+ title: row.title || "",
25
+ rank: row.url_rank ?? 0,
26
+ redirects: row.redirects || "",
27
+ });
28
+ }
29
+ }
30
+ catch {
31
+ // Skip profiles with errors
32
+ }
33
+ }
34
+ return results;
35
+ }
package/package.json CHANGED
@@ -1,34 +1,35 @@
1
1
  {
2
2
  "name": "chrome-tools",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "CLI & Node.js library to extract Chrome browser data (bookmarks, history, cookies, autofill, extensions, and more) on macOS",
5
5
  "type": "module",
6
- "main": "./src/index.js",
6
+ "main": "./dist/src/index.js",
7
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"
8
+ ".": "./dist/src/index.js",
9
+ "./passwords": "./dist/src/modules/passwords.js",
10
+ "./bookmarks": "./dist/src/modules/bookmarks.js",
11
+ "./history": "./dist/src/modules/history.js",
12
+ "./cookies": "./dist/src/modules/cookies.js",
13
+ "./autofill": "./dist/src/modules/autofill.js",
14
+ "./downloads": "./dist/src/modules/downloads.js",
15
+ "./extensions": "./dist/src/modules/extensions.js",
16
+ "./search-engines": "./dist/src/modules/search-engines.js",
17
+ "./top-sites": "./dist/src/modules/top-sites.js"
18
18
  },
19
19
  "bin": {
20
- "chrome-tools": "./bin/cli.js"
20
+ "chrome-tools": "./dist/bin/cli.js"
21
21
  },
22
22
  "files": [
23
- "bin",
24
- "src",
23
+ "dist/src",
24
+ "dist/bin",
25
25
  "README.md",
26
26
  "LICENSE"
27
27
  ],
28
28
  "scripts": {
29
- "start": "node bin/cli.js",
30
- "test": "node --test && node bin/cli.js --help",
31
- "release": "npm version patch --no-git-tag-version && npm publish"
29
+ "build": "tsc",
30
+ "start": "node dist/bin/cli.js",
31
+ "test": "npm run build && node --test dist/test/*.test.js dist/test/**/*.test.js && node dist/bin/cli.js --help",
32
+ "release": "npm run build && npm version patch --no-git-tag-version && npm publish"
32
33
  },
33
34
  "keywords": [
34
35
  "chrome",
@@ -51,5 +52,10 @@
51
52
  },
52
53
  "engines": {
53
54
  "node": ">=18.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/better-sqlite3": "^7.6.13",
58
+ "@types/node": "^25.9.1",
59
+ "typescript": "^6.0.3"
54
60
  }
55
61
  }