chrome-tools 1.0.9 → 1.1.2

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 (41) hide show
  1. package/dist/bin/cli.d.ts +2 -1
  2. package/dist/bin/cli.js +1 -1
  3. package/dist/src/core/browsers.d.ts +69 -0
  4. package/dist/src/core/browsers.js +1 -0
  5. package/dist/src/core/chrome.d.ts +29 -2
  6. package/dist/src/core/chrome.js +1 -1
  7. package/dist/src/core/crypto.d.ts +15 -3
  8. package/dist/src/core/crypto.js +1 -1
  9. package/dist/src/core/keychain.d.ts +14 -6
  10. package/dist/src/core/keychain.js +1 -1
  11. package/dist/src/core/platform/darwin.d.ts +12 -0
  12. package/dist/src/core/platform/darwin.js +1 -0
  13. package/dist/src/core/platform/index.d.ts +28 -0
  14. package/dist/src/core/platform/index.js +1 -0
  15. package/dist/src/core/platform/linux.d.ts +10 -0
  16. package/dist/src/core/platform/linux.js +1 -0
  17. package/dist/src/core/platform/types.d.ts +34 -0
  18. package/dist/src/core/platform/types.js +1 -0
  19. package/dist/src/core/platform/win32.d.ts +12 -0
  20. package/dist/src/core/platform/win32.js +1 -0
  21. package/dist/src/index.d.ts +5 -3
  22. package/dist/src/index.js +1 -1
  23. package/dist/src/modules/autofill.d.ts +6 -3
  24. package/dist/src/modules/autofill.js +1 -1
  25. package/dist/src/modules/bookmarks.d.ts +2 -1
  26. package/dist/src/modules/bookmarks.js +1 -1
  27. package/dist/src/modules/cookies.d.ts +2 -1
  28. package/dist/src/modules/cookies.js +1 -1
  29. package/dist/src/modules/downloads.d.ts +2 -1
  30. package/dist/src/modules/downloads.js +1 -1
  31. package/dist/src/modules/extensions.d.ts +2 -1
  32. package/dist/src/modules/extensions.js +1 -1
  33. package/dist/src/modules/history.d.ts +4 -2
  34. package/dist/src/modules/history.js +1 -1
  35. package/dist/src/modules/passwords.d.ts +2 -1
  36. package/dist/src/modules/passwords.js +1 -1
  37. package/dist/src/modules/search-engines.d.ts +2 -1
  38. package/dist/src/modules/search-engines.js +1 -1
  39. package/dist/src/modules/top-sites.d.ts +2 -1
  40. package/dist/src/modules/top-sites.js +1 -1
  41. package/package.json +5 -1
package/dist/bin/cli.d.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * chrome-tools CLI
4
4
  *
5
- * A comprehensive command-line interface for extracting Chrome browser data on macOS.
5
+ * A comprehensive command-line interface for extracting Chrome browser data on
6
+ * macOS, Windows, and Linux.
6
7
  * Supports subcommands and shortcut flags for quick access to all data types.
7
8
  */
8
9
  export {};
package/dist/bin/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
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,getDirectorySize as m}from"../src/core/chrome.js";import{getPasswords as p}from"../src/modules/passwords.js";import{getBookmarks as d}from"../src/modules/bookmarks.js";import{getHistory as h,getSearchTerms as f}from"../src/modules/history.js";import{getCookies as u}from"../src/modules/cookies.js";import{getAutofillEntries as g,getCreditCards as y,getAddresses as w}from"../src/modules/autofill.js";import{getDownloads as b}from"../src/modules/downloads.js";import{getExtensions as S}from"../src/modules/extensions.js";import{getSearchEngines as k}from"../src/modules/search-engines.js";import{getTopSites as P}from"../src/modules/top-sites.js";const j=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}(),L=j.PROFILE_PATH||c(),v=j.PROFILE_NAME||void 0;function D(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 N(o,e){0===e?console.log(i.yellow(`\n⚠ No ${o} found.\n`)):console.log(i.green(`\n✔ Found ${e} ${o}.\n`))}function C(o){return o.option("--profile-path <path>","Custom Chrome Profile Path",L).option("--profile <name>","Target a specific profile",v).option("--json","Output as JSON")}function U(o){return o.option("--search <term>","Filter results (case-insensitive)")}function G(o,e=100){return o.option("--limit <n>","Limit number of results",String(e))}const O=new s,$=JSON.parse(o(new URL("../../package.json",import.meta.url),"utf8"));O.name("chrome-tools").description(i.bold.cyan("Chrome Data Toolkit")+i.dim(" — Extract passwords, bookmarks, history, cookies, and more from Chrome on macOS")).version($.version).option("--passwords","Shortcut: list saved passwords").option("--bookmarks","Shortcut: list bookmarks").option("--profiles","Shortcut: list profiles").option("--size","Show each profile's size on disk when listing profiles (slower)").option("--profile-path <path>","Custom Chrome Profile Path",L).option("--profile <name>","Target a specific profile",v).option("--json","Output as JSON").option("--search <term>","Filter results (for shortcut flags)").option("--limit <n>","Limit results (for shortcut flags)").action(o=>{o.passwords?B(o):o.bookmarks?F(o):E(o)});const x=O.command("profiles").description("List Chrome profiles");function E(o){const e=n(o.profilePath),t=l(o.profilePath),s=Boolean(o.size);if(o.json){const o=new Map(t.map(o=>[o.directory,o]));return D(e.map(e=>({...e,...o.get(e.name)||{},...s?{sizeBytes:m(e.path)}:{}})))}if(0===e.length)return void console.log(i.yellow("\n⚠ No Chrome profiles found.\n"));const r=new Map(t.map(o=>[o.directory,o]));console.log(i.bold(`\n📁 Discovered ${e.length} profile(s):\n`));const a=W(s?["Directory","Display Name","Email","Size","Path"]:["Directory","Display Name","Email","Path"],s?[16,22,30,10,42]:[16,22,30,50]);for(const o of e){const e=r.get(o.name)||{},t=[i.cyan(o.name),i.white(e.name||o.name),i.dim(e.email||"—")];s&&t.push(i.yellow(R(m(o.path)))),t.push(i.dim(o.path)),a.push(t)}console.log(a.toString()),N("profile(s)",e.length)}C(x).option("--size","Compute and show each profile's size on disk (slower)").action((o,e)=>{E(e.optsWithGlobals())}),x.command("states").description("Show detailed profile states with available data files").option("--profile-path <path>","Custom Chrome Profile Path",L).option("--json","Output as JSON").action((o,e)=>function(o){const e=a(o.profilePath);if(o.json)return D(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 B(o){const e=p({chromeDir:o.profilePath,profile:o.profile,search:o.search});if(o.json)return D(e);if(0===e.length)return N("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()),N("password(s)",e.length)}U(C(O.command("passwords").description("List saved passwords"))).action((o,e)=>B(e.optsWithGlobals()));function F(o){const e=d({chromeDir:o.profilePath,profile:o.profile,folder:o.folder,search:o.search});if(o.json)return D(e);if(0===e.length)return N("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()),N("bookmark(s)",e.length)}U(C(O.command("bookmarks").description("List bookmarks"))).option("--folder <name>","Filter by folder name").action((o,e)=>F(e.optsWithGlobals()));U(G(C(O.command("history").description("List browsing history")))).action((o,e)=>{const t=e.optsWithGlobals(),s=h({chromeDir:t.profilePath,profile:t.profile,search:t.search,limit:parseInt(t.limit,10)||100});if(t.json)return D(s);if(0===s.length)return N("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()),N("history entry(ies)",s.length)});G(C(O.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 D(s);if(0===s.length)return N("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()),N("search term(s)",s.length)});G(C(O.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=u({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 D(s);if(0===s.length)return N("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()),N("cookie(s)",s.length)});U(G(C(O.command("autofill").description("List autofill entries")))).action((o,e)=>{const t=e.optsWithGlobals(),s=g({chromeDir:t.profilePath,profile:t.profile,search:t.search,limit:parseInt(t.limit,10)||100});if(t.json)return D(s);if(0===s.length)return N("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()),N("autofill entry(ies)",s.length)});C(O.command("credit-cards").description("List saved credit cards")).action((o,e)=>{const t=e.optsWithGlobals(),s=y({chromeDir:t.profilePath,profile:t.profile});if(t.json)return D(s);if(0===s.length)return N("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()),N("credit card(s)",s.length)});C(O.command("addresses").description("List saved addresses")).action((o,e)=>{const t=e.optsWithGlobals(),s=w({chromeDir:t.profilePath,profile:t.profile});if(t.json)return D(s);if(0===s.length)return N("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()),N("address(es)",s.length)});U(G(C(O.command("downloads").description("List download history")))).action((o,e)=>{const t=e.optsWithGlobals(),s=b({chromeDir:t.profilePath,profile:t.profile,search:t.search,limit:parseInt(t.limit,10)||100});if(t.json)return D(s);if(0===s.length)return N("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()),N("download(s)",s.length)});U(C(O.command("extensions").description("List installed extensions"))).action((o,e)=>{const t=e.optsWithGlobals(),s=S({chromeDir:t.profilePath,profile:t.profile,search:t.search});if(t.json)return D(s);if(0===s.length)return N("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()),N("extension(s)",s.length)});C(O.command("search-engines").description("List configured search engines")).action((o,e)=>{const t=e.optsWithGlobals(),s=k({chromeDir:t.profilePath,profile:t.profile});if(t.json)return D(s);if(0===s.length)return N("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()),N("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]}`}C(O.command("top-sites").description("List top sites")).action((o,e)=>{const t=e.optsWithGlobals(),s=P({chromeDir:t.profilePath,profile:t.profile});if(t.json)return D(s);if(0===s.length)return N("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()),N("top site(s)",s.length)}),O.parse();
2
+ import{readFileSync as o,existsSync as e}from"node:fs";import{join as r}from"node:path";import{Command as t}from"commander";import s from"chalk";import i from"cli-table3";import{discoverProfiles as n,getProfileMetadata as l,getProfileStates as a,getDirectorySize as c,detectInstalledBrowsers as d}from"../src/core/chrome.js";import{setActiveBrowser as m,getActiveBrowser as p}from"../src/core/platform/index.js";import{getPasswords as f}from"../src/modules/passwords.js";import{getBookmarks as h}from"../src/modules/bookmarks.js";import{getHistory as u,getSearchTerms as g}from"../src/modules/history.js";import{getCookies as w}from"../src/modules/cookies.js";import{getAutofillEntries as b,getCreditCards as y,getAddresses as S}from"../src/modules/autofill.js";import{getDownloads as v}from"../src/modules/downloads.js";import{getExtensions as k}from"../src/modules/extensions.js";import{getSearchEngines as L}from"../src/modules/search-engines.js";import{getTopSites as P}from"../src/modules/top-sites.js";const D=function(){const t={...process.env},s=r(process.cwd(),".env");if(e(s))try{const e=o(s,"utf8");for(const o of e.split(/\r?\n/)){const e=o.trim();if(!e||e.startsWith("#"))continue;const r=e.indexOf("=");if(-1!==r){const o=e.substring(0,r).trim();let s=e.substring(r+1).trim();(s.startsWith('"')&&s.endsWith('"')||s.startsWith("'")&&s.endsWith("'"))&&(s=s.slice(1,-1)),t[o]=s}}}catch{}return t}(),j=D.PROFILE_PATH||void 0,B=D.PROFILE_NAME||void 0,C=D.BROWSER||D.CHROME_BROWSER||void 0;function N(o){console.log(JSON.stringify(o,null,2))}function R(o,e){return new i({head:o.map(o=>s.bold.white(o)),colWidths:e,wordWrap:!0,style:{border:["grey"],head:[]}})}function U(o,e){0===e?console.log(s.yellow(`\n⚠ No ${o} found.\n`)):console.log(s.green(`\n✔ Found ${e} ${o}.\n`))}let $=null;function E(o,e){return $?($.push({browser:p().name,results:e}),!0):!!o.json&&(N(e),!0)}function O(o){const e=o.optsWithGlobals(),r=Boolean(e.browser)||Boolean(e.allBrowsers)||Boolean(C);return e.browser=e.browser||C||void 0,e.profilePath||(e.profilePath=r?void 0:j),e.profile||(e.profile=r?void 0:B),e}function x(o,e){e.allBrowsers?function(o,e){const r=d();if(0===r.length)return e.json?N([]):void console.log(s.yellow("\n⚠ No supported Chromium browsers found on this machine.\n"));const t={...e,profilePath:void 0};if(e.json){$=[];try{for(const e of r){m(e.browser.id);try{o(t)}catch(o){$.push({browser:e.browser.name,results:{error:o?.message??String(o)}})}}N($)}finally{$=null}}else for(const e of r){m(e.browser.id),console.log(s.bold.magenta(`\n══════════ ${e.browser.name} ══════════`));try{o(t)}catch(o){console.log(s.red(` ⚠ Skipped: ${o?.message??String(o)}`))}}}(o,e):(m(e.browser),o(e))}function F(o){const e=d({includeMissing:!0});if(o.json)return N(e.map(o=>({id:o.browser.id,name:o.browser.name,installed:o.installed,profileCount:o.profileCount,path:o.chromeDir,safeStorageLabel:o.browser.safeStorageLabel})));const r=e.filter(o=>o.installed);console.log(s.bold(`\n🌐 Supported browsers (${r.length} installed):\n`));const t=R(["Browser","Id","Installed","Profiles","Path"],[20,14,11,9,50]);for(const o of e)t.push([s.white(o.browser.name),s.cyan(o.browser.id),o.installed?s.green("✓"):s.dim("✗"),o.installed?s.yellow(String(o.profileCount)):s.dim("—"),s.dim(o.chromeDir)]);console.log(t.toString())}function W(o){return o.option("--profile-path <path>","Custom browser User Data Path (overrides --browser)").option("--profile <name>","Target a specific profile").option("--browser <name>","Browser to read (chrome, edge, brave, comet, vivaldi, opera, chromium…)").option("--all-browsers","Read from every installed Chromium browser").option("--json","Output as JSON")}function M(o){return o.option("--search <term>","Filter results (case-insensitive)")}function T(o,e=100){return o.option("--limit <n>","Limit number of results",String(e))}const A=new t,I=JSON.parse(o(new URL("../../package.json",import.meta.url),"utf8"));A.name("chrome-tools").description(s.bold.cyan("Chrome Data Toolkit")+s.dim(" — Extract passwords, bookmarks, history, cookies, and more from Chrome on macOS, Windows, and Linux")).version(I.version).option("--passwords","Shortcut: list saved passwords").option("--bookmarks","Shortcut: list bookmarks").option("--profiles","Shortcut: list profiles").option("--browsers","Shortcut: list supported / installed browsers").option("--size","Show each profile's size on disk when listing profiles (slower)").option("--profile-path <path>","Custom browser User Data Path (overrides --browser)").option("--profile <name>","Target a specific profile").option("--browser <name>","Browser to read (chrome, edge, brave, comet, vivaldi, opera, chromium…)").option("--all-browsers","Read from every installed Chromium browser").option("--json","Output as JSON").option("--search <term>","Filter results (for shortcut flags)").option("--limit <n>","Limit results (for shortcut flags)").action((o,e)=>{const r=O(e);r.browsers?F(r):r.passwords?x(H,r):r.bookmarks?x(G,r):x(V,r)});const z=A.command("profiles").description("List Chrome profiles");W(z).option("--size","Compute and show each profile's size on disk (slower)").action((o,e)=>{x(V,O(e))});W(z.command("states").description("Show detailed profile states with available data files")).action((o,e)=>x(J,O(e)));function V(o){const e=n(o.profilePath),r=l(o.profilePath),t=Boolean(o.size);if(o.json){const s=new Map(r.map(o=>[o.directory,o]));return void E(o,e.map(o=>({...o,...s.get(o.name)||{},...t?{sizeBytes:c(o.path)}:{}})))}if(0===e.length)return void console.log(s.yellow("\n⚠ No Chrome profiles found.\n"));const i=new Map(r.map(o=>[o.directory,o]));console.log(s.bold(`\n📁 Discovered ${e.length} profile(s):\n`));const a=R(t?["Directory","Display Name","Email","Size","Path"]:["Directory","Display Name","Email","Path"],t?[16,22,30,10,42]:[16,22,30,50]);for(const o of e){const e=i.get(o.name)||{},r=[s.cyan(o.name),s.white(e.name||o.name),s.dim(e.email||"—")];t&&r.push(s.yellow(_(c(o.path)))),r.push(s.dim(o.path)),a.push(r)}console.log(a.toString()),U("profile(s)",e.length)}function J(o){const e=a(o.profilePath);if(o.json)E(o,e);else if(0!==e.length){console.log(s.bold(`\n📊 Profile States (${e.length} profile(s)):\n`));for(const o of e){console.log(s.bold.cyan(` ${o.displayName}`)+s.dim(` (${o.directory})`)),o.email&&console.log(s.dim(` Email: ${o.email}`)),console.log(s.dim(` Path: ${o.path}`));const e=o.dataFiles,r=Object.entries(e).filter(([,o])=>o).map(([o])=>o),t=Object.entries(e).filter(([,o])=>!o).map(([o])=>o);console.log(` Available: ${r.map(o=>s.green(o)).join(", ")||s.dim("none")}`),t.length>0&&console.log(` Missing: ${t.map(o=>s.dim(o)).join(", ")}`),console.log()}}else console.log(s.yellow("\n⚠ No Chrome profiles found.\n"))}A.command("browsers").description("List supported browsers and detect which are installed").option("--json","Output as JSON").action((o,e)=>F(O(e)));function H(o){const e=f({chromeDir:o.profilePath,profile:o.profile,search:o.search});if(E(o,e))return;if(0===e.length)return U("passwords",0);console.log(s.bold("\n🔑 Saved Passwords:\n"));const r=new Set(e.map(o=>o.profile)).size>1,t=R(r?["Profile","URL","Username","Password","Last Used"]:["URL","Username","Password","Last Used"],r?[12,40,24,22,20]:[45,28,25,22]);for(const o of e){const e=[s.cyan(o.url),s.green(o.username||"<empty>"),s.yellow(o.password||"<empty>"),s.dim(o.lastUsed)];r&&e.unshift(s.magenta(o.profile)),t.push(e)}console.log(t.toString()),U("password(s)",e.length)}M(W(A.command("passwords").description("List saved passwords"))).action((o,e)=>x(H,O(e)));function G(o){const e=h({chromeDir:o.profilePath,profile:o.profile,folder:o.folder,search:o.search});if(E(o,e))return;if(0===e.length)return U("bookmarks",0);console.log(s.bold("\n📚 Bookmarks:\n"));const r=R(["Title","URL","Folder","Date Added"],[30,50,25,20]);for(const o of e)r.push([s.white(o.title),s.cyan(o.url),s.dim(o.folder||"/"),s.dim(o.dateAdded)]);console.log(r.toString()),U("bookmark(s)",e.length)}M(W(A.command("bookmarks").description("List bookmarks"))).option("--folder <name>","Filter by folder name").action((o,e)=>x(G,O(e)));M(T(W(A.command("history").description("List browsing history")))).action((o,e)=>x(o=>{const e=u({chromeDir:o.profilePath,profile:o.profile,search:o.search,limit:parseInt(o.limit,10)||100});if(E(o,e))return;if(0===e.length)return U("history entries",0);console.log(s.bold("\n🕐 Browsing History:\n"));const r=R(["URL","Title","Visits","Last Visit"],[50,30,8,22]);for(const o of e)r.push([s.cyan(o.url),s.white(o.title||"—"),s.yellow(String(o.visitCount)),s.dim(o.lastVisit)]);console.log(r.toString()),U("history entry(ies)",e.length)},O(e)));T(W(A.command("search-terms").description("List search terms from history"))).action((o,e)=>x(o=>{const e=g({chromeDir:o.profilePath,profile:o.profile,limit:parseInt(o.limit,10)||100});if(E(o,e))return;if(0===e.length)return U("search terms",0);console.log(s.bold("\n🔍 Search Terms:\n"));const r=R(["Term","URL"],[40,80]);for(const o of e)r.push([s.white(o.term),s.dim(o.url)]);console.log(r.toString()),U("search term(s)",e.length)},O(e)));T(W(A.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)=>x(o=>{const e=w({chromeDir:o.profilePath,profile:o.profile,domain:o.domain,url:o.url,decrypt:!1!==o.decrypt,limit:parseInt(o.limit,10)||100});if(E(o,e))return;if(0===e.length)return U("cookies",0);console.log(s.bold("\n🍪 Cookies:\n"));const r=R(["Domain","Name","Value","Secure","Expires"],[30,25,30,8,22]);for(const o of e)r.push([s.cyan(o.domain),s.white(o.name),s.dim(o.value?o.value.substring(0,50)+(o.value.length>50?"…":""):"—"),o.secure?s.green("✓"):s.dim("✗"),s.dim(o.expires)]);console.log(r.toString()),U("cookie(s)",e.length)},O(e)));M(T(W(A.command("autofill").description("List autofill entries")))).action((o,e)=>x(o=>{const e=b({chromeDir:o.profilePath,profile:o.profile,search:o.search,limit:parseInt(o.limit,10)||100});if(E(o,e))return;if(0===e.length)return U("autofill entries",0);console.log(s.bold("\n📝 Autofill Entries:\n"));const r=R(["Field Name","Value","Count","Last Used"],[25,40,8,22]);for(const o of e)r.push([s.cyan(o.name),s.white(o.value),s.yellow(String(o.count)),s.dim(o.dateLastUsed)]);console.log(r.toString()),U("autofill entry(ies)",e.length)},O(e)));W(A.command("credit-cards").description("List saved credit cards")).action((o,e)=>x(o=>{const e=y({chromeDir:o.profilePath,profile:o.profile});if(E(o,e))return;if(0===e.length)return U("credit cards",0);console.log(s.bold("\n💳 Saved Credit Cards:\n"));const r=R(["Name on Card","Card Number","Expires","Nickname"],[25,25,14,20]);for(const o of e)r.push([s.white(o.nameOnCard||"—"),s.yellow(o.cardNumber||"—"),s.dim(`${o.expirationMonth}/${o.expirationYear}`),s.dim(o.nickname||"—")]);console.log(r.toString()),U("credit card(s)",e.length)},O(e)));W(A.command("addresses").description("List saved addresses")).action((o,e)=>x(o=>{const e=S({chromeDir:o.profilePath,profile:o.profile});if(E(o,e))return;if(0===e.length)return U("addresses",0);console.log(s.bold("\n🏠 Saved Addresses:\n"));const r=R(["Name","Email","Phone","Address","City/State/Zip"],[20,25,16,30,25]);for(const o of e)r.push([s.white(o.fullName||"—"),s.cyan(o.email||"—"),s.dim(o.phone||"—"),s.white(o.streetAddress||"—"),s.dim([o.city,o.state,o.zipcode].filter(Boolean).join(", ")||"—")]);console.log(r.toString()),U("address(es)",e.length)},O(e)));M(T(W(A.command("downloads").description("List download history")))).action((o,e)=>x(o=>{const e=v({chromeDir:o.profilePath,profile:o.profile,search:o.search,limit:parseInt(o.limit,10)||100});if(E(o,e))return;if(0===e.length)return U("downloads",0);console.log(s.bold("\n⬇️ Download History:\n"));const r=R(["File","URL","Size","State","Date"],[35,40,12,12,20]);for(const o of e){const e=o.targetPath?o.targetPath.split("/").pop():"—",t=o.totalBytes>0?_(o.totalBytes):"—";r.push([s.white(e||""),s.cyan(o.url?o.url.substring(0,60):"—"),s.dim(t),"complete"===o.state?s.green(o.state):s.yellow(o.state),s.dim(o.startTime)])}console.log(r.toString()),U("download(s)",e.length)},O(e)));M(W(A.command("extensions").description("List installed extensions"))).action((o,e)=>x(o=>{const e=k({chromeDir:o.profilePath,profile:o.profile,search:o.search});if(E(o,e))return;if(0===e.length)return U("extensions",0);console.log(s.bold("\n🧩 Installed Extensions:\n"));const r=R(["Name","Version","ID","Enabled","Webstore"],[30,12,36,9,9]);for(const o of e)r.push([s.white(o.name),s.dim(o.version),s.dim(o.id),o.enabled?s.green("✓"):s.red("✗"),o.fromWebstore?s.green("✓"):s.dim("✗")]);console.log(r.toString()),U("extension(s)",e.length)},O(e)));W(A.command("search-engines").description("List configured search engines")).action((o,e)=>x(o=>{const e=L({chromeDir:o.profilePath,profile:o.profile});if(E(o,e))return;if(0===e.length)return U("search engines",0);console.log(s.bold("\n🔎 Search Engines:\n"));const r=R(["Name","Keyword","URL","Active"],[20,16,60,8]);for(const o of e)r.push([s.white(o.name),s.cyan(o.keyword),s.dim(o.url?o.url.substring(0,80):"—"),o.isActive?s.green("✓"):s.dim("✗")]);console.log(r.toString()),U("search engine(s)",e.length)},O(e)));function _(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]}`}W(A.command("top-sites").description("List top sites")).action((o,e)=>x(o=>{const e=P({chromeDir:o.profilePath,profile:o.profile});if(E(o,e))return;if(0===e.length)return U("top sites",0);console.log(s.bold("\n⭐ Top Sites:\n"));const r=R(["Rank","Title","URL"],[8,30,80]);for(const o of e)r.push([s.yellow(String(o.rank+1)),s.white(o.title||"—"),s.cyan(o.url)]);console.log(r.toString()),U("top site(s)",e.length)},O(e)));try{A.parse()}catch(o){console.error(s.red(`\n✖ ${o?.message??String(o)}\n`)),process.env.DEBUG&&console.error(o),process.exit(1)}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Chromium-browser registry — the single source of truth for what differs
3
+ * between Chromium-based browsers (Chrome, Edge, Brave, Comet, …).
4
+ *
5
+ * Every supported browser is a Chromium fork, so the *decryption* is identical
6
+ * per OS (AES-128-CBC + PBKDF2 on macOS/Linux, AES-256-GCM + DPAPI on Windows).
7
+ * Only two things vary by browser:
8
+ * 1. the default profile data directory, and
9
+ * 2. the OS-keystore "Safe Storage" label (macOS Keychain service name /
10
+ * Linux Secret Service application).
11
+ *
12
+ * "Browser" is therefore a small dimension orthogonal to "platform": the
13
+ * platform backends (darwin/win32/linux) read the active browser's dir + label
14
+ * from here and stay otherwise unchanged.
15
+ */
16
+ export type BrowserId = "chrome" | "chrome-beta" | "chrome-canary" | "chromium" | "edge" | "brave" | "comet" | "vivaldi" | "opera";
17
+ /** Subpaths to a browser's User Data dir, relative to each OS's app-data base. */
18
+ export interface BrowserDirs {
19
+ /** Under `~/Library/Application Support`. */
20
+ darwin: string;
21
+ /** Under `%LOCALAPPDATA%`. Includes the trailing `User Data` segment. */
22
+ win32: string;
23
+ /** Under `$XDG_CONFIG_HOME` (or `~/.config`). */
24
+ linux: string;
25
+ }
26
+ export interface BrowserDescriptor {
27
+ /** Stable identifier used on the CLI (`--browser <id>`) and in the API. */
28
+ readonly id: BrowserId;
29
+ /** Human-readable display name, e.g. "Microsoft Edge". */
30
+ readonly name: string;
31
+ /** Extra names accepted on the CLI (case-insensitive), e.g. "msedge". */
32
+ readonly aliases?: readonly string[];
33
+ /**
34
+ * macOS Keychain service name (a.k.a. the "<Browser> Safe Storage" item) used
35
+ * as the basis for the AES key. Also reused as a human label in messages.
36
+ */
37
+ readonly safeStorageLabel: string;
38
+ /** Linux Secret Service `application` attribute used by `secret-tool lookup`. */
39
+ readonly linuxSecretApp?: string;
40
+ /** Per-OS User Data subpaths. */
41
+ readonly dirs: BrowserDirs;
42
+ }
43
+ /**
44
+ * Supported browsers. macOS paths/labels are verified; a few Windows/Linux
45
+ * paths for niche browsers (Comet, Opera) are best-effort and easy to correct —
46
+ * each is a single edit here, with no other code change required.
47
+ */
48
+ export declare const BROWSERS: Record<BrowserId, BrowserDescriptor>;
49
+ /** All supported browsers, in a stable display order. */
50
+ export declare function listBrowsers(): BrowserDescriptor[];
51
+ /**
52
+ * Resolves a browser by id, display name, or alias (case-insensitive).
53
+ * @returns The descriptor, or `null` if unrecognized.
54
+ */
55
+ export declare function resolveBrowser(idOrName: string): BrowserDescriptor | null;
56
+ /** The browser currently targeted by dir/key lookups. Defaults to Chrome. */
57
+ export declare function getActiveBrowser(): BrowserDescriptor;
58
+ /**
59
+ * Sets the active browser for subsequent dir/key lookups.
60
+ *
61
+ * Caches in the platform backends are keyed by their real inputs (Keychain
62
+ * service label, chromeDir, Linux secret app), so switching browsers never
63
+ * returns a stale key — no cache flush is needed here.
64
+ *
65
+ * @param idOrName - Browser id/name/alias. Empty/undefined resets to Chrome.
66
+ * @returns The now-active descriptor.
67
+ * @throws If `idOrName` is non-empty but unrecognized.
68
+ */
69
+ export declare function setActiveBrowser(idOrName?: string): BrowserDescriptor;
@@ -0,0 +1 @@
1
+ export const BROWSERS={chrome:{id:"chrome",name:"Google Chrome",aliases:["google-chrome","google chrome"],safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome",win32:"Google/Chrome/User Data",linux:"google-chrome"}},"chrome-beta":{id:"chrome-beta",name:"Google Chrome Beta",safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome Beta",win32:"Google/Chrome Beta/User Data",linux:"google-chrome-beta"}},"chrome-canary":{id:"chrome-canary",name:"Google Chrome Canary",aliases:["canary"],safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome Canary",win32:"Google/Chrome SxS/User Data",linux:"google-chrome-unstable"}},chromium:{id:"chromium",name:"Chromium",safeStorageLabel:"Chromium Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Chromium",win32:"Chromium/User Data",linux:"chromium"}},edge:{id:"edge",name:"Microsoft Edge",aliases:["msedge","microsoft-edge","microsoft edge"],safeStorageLabel:"Microsoft Edge Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Microsoft Edge",win32:"Microsoft/Edge/User Data",linux:"microsoft-edge"}},brave:{id:"brave",name:"Brave",aliases:["brave-browser"],safeStorageLabel:"Brave Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"BraveSoftware/Brave-Browser",win32:"BraveSoftware/Brave-Browser/User Data",linux:"BraveSoftware/Brave-Browser"}},comet:{id:"comet",name:"Comet",aliases:["perplexity","perplexity-comet"],safeStorageLabel:"Comet Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Comet",win32:"Perplexity/Comet/User Data",linux:"comet"}},vivaldi:{id:"vivaldi",name:"Vivaldi",safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Vivaldi",win32:"Vivaldi/User Data",linux:"vivaldi"}},opera:{id:"opera",name:"Opera",safeStorageLabel:"Opera Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"com.operasoftware.Opera",win32:"Opera Software/Opera Stable",linux:"opera"}}};const e="chrome";export function listBrowsers(){return Object.values(BROWSERS)}export function resolveBrowser(e){if(!e)return null;const r=e.trim().toLowerCase();for(const e of listBrowsers()){if(e.id.toLowerCase()===r)return e;if(e.name.toLowerCase()===r)return e;if(e.aliases?.some(e=>e.toLowerCase()===r))return e}return null}let r=BROWSERS[e];export function getActiveBrowser(){return r}export function setActiveBrowser(o){if(!o||!o.trim())return r=BROWSERS[e],r;const a=resolveBrowser(o);if(!a){const e=listBrowsers().map(e=>e.id).join(", ");throw new Error(`Unknown browser "${o}". Supported: ${e}.`)}return r=a,r}
@@ -1,3 +1,4 @@
1
+ import { type BrowserDescriptor } from "./browsers.js";
1
2
  export interface DiscoveredProfile {
2
3
  name: string;
3
4
  path: string;
@@ -28,12 +29,38 @@ export interface ResolveProfilesOptions {
28
29
  profilePath?: string;
29
30
  chromeDir?: string;
30
31
  profile?: string;
32
+ /** Target a specific browser (id/name/alias). Applied only when provided. */
33
+ browser?: string;
31
34
  }
32
35
  /**
33
- * Returns the default Chrome User Data directory for macOS.
36
+ * Returns the default Chrome User Data directory for the current OS.
37
+ * Delegates to the active platform backend (macOS/Windows/Linux).
34
38
  * @returns The absolute path to Chrome's User Data.
35
39
  */
36
40
  export declare function getDefaultChromeDir(): string;
41
+ export interface InstalledBrowser {
42
+ /** Registry descriptor for the detected browser. */
43
+ browser: BrowserDescriptor;
44
+ /** Absolute path to this browser's default User Data directory. */
45
+ chromeDir: string;
46
+ /** Whether the data directory exists on disk. */
47
+ installed: boolean;
48
+ /** Number of discovered profiles (0 when not installed). */
49
+ profileCount: number;
50
+ }
51
+ /**
52
+ * Probes every supported browser for an on-disk profile directory.
53
+ *
54
+ * Temporarily switches the active browser to resolve each one's default dir,
55
+ * then restores the previously-active browser before returning.
56
+ *
57
+ * @param options.includeMissing - When true, also returns browsers with no data
58
+ * directory (installed: false). Defaults to false (installed browsers only).
59
+ * @returns One entry per matching browser.
60
+ */
61
+ export declare function detectInstalledBrowsers({ includeMissing }?: {
62
+ includeMissing?: boolean;
63
+ }): InstalledBrowser[];
37
64
  /**
38
65
  * Scans the Chrome User Data directory for profile subdirectories.
39
66
  * A directory is considered a profile if it contains at least one known data file.
@@ -83,4 +110,4 @@ export declare function resolveProfilePath(chromeDir: string, profileName: strin
83
110
  * @param options - Resolution options.
84
111
  * @returns Array of targeted profiles.
85
112
  */
86
- export declare function resolveTargetProfiles({ profilePath, chromeDir, profile, }?: ResolveProfilesOptions): DiscoveredProfile[];
113
+ export declare function resolveTargetProfiles({ profilePath, chromeDir, profile, browser, }?: ResolveProfilesOptions): DiscoveredProfile[];
@@ -1 +1 @@
1
- import{existsSync as e,readdirSync as t,readFileSync as r,statSync as o}from"node:fs";import{homedir as i}from"node:os";import{join as a}from"node:path";const n=["Login Data","History","Bookmarks","Cookies","Web Data","Preferences"];export function getDefaultChromeDir(){return a(i(),"Library","Application Support","Google","Chrome")}export function discoverProfiles(r){const o=r||getDefaultChromeDir();if(!o||!e(o))return[];try{const r=t(o,{withFileTypes:!0}),i=[];for(const t of r){if(!t.isDirectory())continue;const r=a(o,t.name);n.some(t=>e(a(r,t)))&&i.push({name:t.name,path:r})}return i}catch{return[]}}export function getProfileMetadata(t){const o=t||getDefaultChromeDir(),i=a(o,"Local State");if(!e(i))return[];try{const e=r(i,"utf-8"),t=JSON.parse(e),o=t?.profile?.info_cache;return o&&"object"==typeof o?Object.entries(o).map(([e,t])=>{const r=t;return{directory:e,name:r.name||e,gaiaName:r.gaia_name||"",userName:r.user_name||"",email:r.user_name||"",avatarIcon:r.avatar_icon||"",isUsingDefaultName:r.is_using_default_name??!0,isEphemeral:r.is_ephemeral??!1,activeTime:r.active_time||0}}):[]}catch{return[]}}export function getProfileStates(t){const r=t||getDefaultChromeDir(),o=discoverProfiles(r),i=getProfileMetadata(r),s=new Map;for(const e of i)s.set(e.directory,e);return o.map(t=>{const r=s.get(t.name),o=t.path,i={};for(const t of n)i[t.replace(/\s+/g,"_").toLowerCase()]=e(a(o,t));return i.top_sites=e(a(o,"Top Sites")),i.extensions=e(a(o,"Extensions")),{directory:t.name,path:o,displayName:r?.name||t.name,gaiaName:r?.gaiaName||"",email:r?.email||"",avatarIcon:r?.avatarIcon||"",isEphemeral:r?.isEphemeral??!1,activeTime:r?.activeTime||0,dataFiles:i}})}export function getDirectorySize(r){if(!r||!e(r))return 0;let i,n=0;try{i=t(r,{withFileTypes:!0})}catch{return 0}for(const e of i){const t=a(r,e.name);try{if(e.isSymbolicLink())continue;e.isDirectory()?n+=getDirectorySize(t):e.isFile()&&(n+=o(t).size)}catch{}}return n}export function resolveProfilePath(t,r){const o=t||getDefaultChromeDir(),i=a(o,r);return e(i)?i:null}export function resolveTargetProfiles({profilePath:e,chromeDir:t,profile:r}={}){const o=e||t||getDefaultChromeDir(),i=discoverProfiles(o);if(r){const e=i.filter(e=>e.name===r);if(0===e.length)throw new Error(`Profile "${r}" not found in ${o}`);return e}return i}
1
+ import{existsSync as e,readdirSync as t,readFileSync as r,statSync as o}from"node:fs";import{join as i}from"node:path";import{getPlatformChromeDir as n}from"./platform/index.js";import{listBrowsers as a,getActiveBrowser as s,setActiveBrowser as c}from"./browsers.js";const f=["Login Data","History","Bookmarks","Cookies","Web Data","Preferences"];export function getDefaultChromeDir(){return n()}export function detectInstalledBrowsers({includeMissing:t=!1}={}){const r=s(),o=[];try{for(const r of a()){c(r.id);const i=getDefaultChromeDir(),n=Boolean(i)&&e(i);(n||t)&&o.push({browser:r,chromeDir:i,installed:n,profileCount:n?discoverProfiles(i).length:0})}}finally{c(r.id)}return o}export function discoverProfiles(r){const o=r||getDefaultChromeDir();if(!o||!e(o))return[];try{const r=t(o,{withFileTypes:!0}),n=[];for(const t of r){if(!t.isDirectory())continue;const r=i(o,t.name);f.some(t=>e(i(r,t)))&&n.push({name:t.name,path:r})}return n}catch{return[]}}export function getProfileMetadata(t){const o=t||getDefaultChromeDir(),n=i(o,"Local State");if(!e(n))return[];try{const e=r(n,"utf-8"),t=JSON.parse(e),o=t?.profile?.info_cache;return o&&"object"==typeof o?Object.entries(o).map(([e,t])=>{const r=t;return{directory:e,name:r.name||e,gaiaName:r.gaia_name||"",userName:r.user_name||"",email:r.user_name||"",avatarIcon:r.avatar_icon||"",isUsingDefaultName:r.is_using_default_name??!0,isEphemeral:r.is_ephemeral??!1,activeTime:r.active_time||0}}):[]}catch{return[]}}export function getProfileStates(t){const r=t||getDefaultChromeDir(),o=discoverProfiles(r),n=getProfileMetadata(r),a=new Map;for(const e of n)a.set(e.directory,e);return o.map(t=>{const r=a.get(t.name),o=t.path,n={};for(const t of f)n[t.replace(/\s+/g,"_").toLowerCase()]=e(i(o,t));return n.top_sites=e(i(o,"Top Sites")),n.extensions=e(i(o,"Extensions")),{directory:t.name,path:o,displayName:r?.name||t.name,gaiaName:r?.gaiaName||"",email:r?.email||"",avatarIcon:r?.avatarIcon||"",isEphemeral:r?.isEphemeral??!1,activeTime:r?.activeTime||0,dataFiles:n}})}export function getDirectorySize(r){if(!r||!e(r))return 0;let n,a=0;try{n=t(r,{withFileTypes:!0})}catch{return 0}for(const e of n){const t=i(r,e.name);try{if(e.isSymbolicLink())continue;e.isDirectory()?a+=getDirectorySize(t):e.isFile()&&(a+=o(t).size)}catch{}}return a}export function resolveProfilePath(t,r){const o=t||getDefaultChromeDir(),n=i(o,r);return e(n)?n:null}export function resolveTargetProfiles({profilePath:e,chromeDir:t,profile:r,browser:o}={}){o&&c(o);const i=e||t||getDefaultChromeDir(),n=discoverProfiles(i);if(r){const e=n.filter(e=>e.name===r);if(0===e.length)throw new Error(`Profile "${r}" not found in ${i}`);return e}return n}
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Decrypts a password/value buffer encrypted by Chrome on macOS.
3
- * Chrome v10+ on macOS prefixes the encrypted blob with "v10" or "v11",
4
- * followed by AES-128-CBC ciphertext.
2
+ * Decrypts a password/value buffer encrypted by Chrome on macOS or Linux.
3
+ * Chrome v10+ prefixes the encrypted blob with "v10" or "v11", followed by
4
+ * AES-128-CBC ciphertext. (Windows uses AES-256-GCM — see decryptValueBufferGcm.)
5
5
  *
6
6
  * @param encryptedBlob - The encrypted blob from the database.
7
7
  * @param derivedKey - The derived 16-byte key.
@@ -16,6 +16,18 @@ export declare function decryptValue(encryptedBlob: Buffer | Uint8Array | null |
16
16
  * @returns The decrypted plaintext bytes, or an error placeholder.
17
17
  */
18
18
  export declare function decryptValueBuffer(encryptedBlob: Buffer | Uint8Array | null | undefined, derivedKey: Buffer): Buffer | string;
19
+ /**
20
+ * Decrypts a Windows Chrome `v10` blob using AES-256-GCM.
21
+ *
22
+ * Unlike the macOS/Linux CBC scheme, Windows blobs carry their own 12-byte
23
+ * nonce after the version prefix and a 16-byte GCM auth tag at the end. The
24
+ * `key` is the 32-byte AES key recovered from `Local State` via DPAPI.
25
+ *
26
+ * @param encryptedBlob - The encrypted blob from the database.
27
+ * @param key - The 32-byte AES-256 key.
28
+ * @returns The decrypted plaintext bytes, or a placeholder string.
29
+ */
30
+ export declare function decryptValueBufferGcm(encryptedBlob: Buffer | Uint8Array | null | undefined, key: Buffer): Buffer | string;
19
31
  /**
20
32
  * Converts a Chrome/Webkit timestamp (microseconds since 1601-01-01)
21
33
  * to a readable local Date string.
@@ -1 +1 @@
1
- import{createDecipheriv as r}from"node:crypto";const t=Buffer.alloc(16," ");export function decryptValue(r,t){const e=decryptValueBuffer(r,t);return Buffer.isBuffer(e)?e.toString("utf-8"):e}export function decryptValueBuffer(e,n){if(!e||0===e.length)return Buffer.alloc(0);const f=Buffer.isBuffer(e)?e:Buffer.from(e),u=f.subarray(0,3).toString("utf-8");if("v10"!==u&&"v11"!==u)return"<unsupported encryption format>";const o=f.subarray(3);try{const e=r("aes-128-cbc",n,t);let f=e.update(o);return f=Buffer.concat([f,e.final()]),f}catch{return"<decryption failed>"}}export function chromeTimeToDate(r){if(!r||0===r||0n===r)return"N/A";try{const t=BigInt(r),e=Number((t-11644473600000000n)/1000n),n=new Date(e);return isNaN(n.getTime())?"N/A":n.toLocaleString()}catch{return"N/A"}}
1
+ import{createDecipheriv as r}from"node:crypto";const t=Buffer.alloc(16," ");export function decryptValue(r,t){const e=decryptValueBuffer(r,t);return Buffer.isBuffer(e)?e.toString("utf-8"):e}export function decryptValueBuffer(e,n){if(!e||0===e.length)return Buffer.alloc(0);const f=Buffer.isBuffer(e)?e:Buffer.from(e),u=f.subarray(0,3).toString("utf-8");if("v10"!==u&&"v11"!==u)return"<unsupported encryption format>";const o=f.subarray(3);try{const e=r("aes-128-cbc",n,t);let f=e.update(o);return f=Buffer.concat([f,e.final()]),f}catch{return"<decryption failed>"}}export function decryptValueBufferGcm(t,e){if(!t||0===t.length)return Buffer.alloc(0);const n=Buffer.isBuffer(t)?t:Buffer.from(t),f=n.subarray(0,3).toString("utf-8");if("v10"!==f&&"v11"!==f)return"<unsupported encryption format>";if(n.length<31)return"<decryption failed>";const u=n.subarray(3,15),o=n.subarray(n.length-16),c=n.subarray(15,n.length-16);try{const t=r("aes-256-gcm",e,u);return t.setAuthTag(o),Buffer.concat([t.update(c),t.final()])}catch{return"<decryption failed>"}}export function chromeTimeToDate(r){if(!r||0===r||0n===r)return"N/A";try{const t=BigInt(r),e=Number((t-11644473600000000n)/1000n),n=new Date(e);return isNaN(n.getTime())?"N/A":n.toLocaleString()}catch{return"N/A"}}
@@ -1,20 +1,28 @@
1
1
  /**
2
- * Retrieves the "Chrome Safe Storage" password from the macOS Keychain.
3
- * Caches the result for subsequent calls within the same process.
2
+ * Retrieves a browser's "Safe Storage" password from the macOS Keychain.
3
+ * Caches the result per service for subsequent calls within the same process.
4
4
  *
5
+ * @param service - Keychain service name, e.g. "Chrome Safe Storage".
5
6
  * @returns The raw keychain password.
6
7
  * @throws If the password could not be retrieved.
7
8
  */
9
+ export declare function getKeychainPassword(service?: string): string;
10
+ /**
11
+ * Backward-compatible alias for {@link getKeychainPassword} using Chrome's item.
12
+ * @deprecated Prefer {@link getKeychainPassword} with an explicit service name.
13
+ */
8
14
  export declare function getChromeKeychainPassword(): string;
9
15
  /**
10
- * Derives the AES-128-CBC key from the Keychain password using PBKDF2.
11
- * Caches the result for subsequent calls.
16
+ * Derives the AES-128-CBC key from a browser's Keychain password using PBKDF2.
17
+ * Caches the result per service.
12
18
  *
19
+ * @param service - Keychain service name. Defaults to Chrome's.
13
20
  * @param keychainPassword - The raw password. If omitted, fetched from Keychain.
14
21
  * @returns The derived 16-byte key.
15
22
  */
16
- export declare function deriveEncryptionKey(keychainPassword?: string): Buffer;
23
+ export declare function deriveEncryptionKey(service?: string, keychainPassword?: string): Buffer;
17
24
  /**
18
- * Clears cached keychain data. Useful for testing or re-authentication.
25
+ * Clears cached keychain data for all browsers. Useful for testing or
26
+ * re-authentication.
19
27
  */
20
28
  export declare function clearKeychainCache(): void;
@@ -1 +1 @@
1
- import{execSync as e}from"node:child_process";import{pbkdf2Sync as n}from"node:crypto";const t=Buffer.from("saltysalt"),o="Chrome Safe Storage",r=44,i=36,s=45;let a=null,c=null;function l(e,n){const t="See docs/remote-keychain-access.md for non-interactive / SSH setup.";let a;a=e===r||/could not be found/i.test(n)?`No '${o}' item found in the Keychain.\nRun Google Chrome at least once on this machine so it creates the key.`:e===i||/interaction.*not allowed/i.test(n)?"The Keychain needs a GUI authorization prompt that can't be shown here "+(Boolean(process.env.SSH_CONNECTION||process.env.SSH_TTY)||!process.env.TERM_PROGRAM?"(you appear to be on a remote/SSH session).\n":".\n")+'Authorize once from a GUI Terminal (click "Always Allow"), then unlock the\nkeychain in this session:\n security unlock-keychain ~/Library/Keychains/login.keychain-db\n'+t:e===s?`Keychain authorization failed (wrong password or denied).\nTry: security unlock-keychain ~/Library/Keychains/login.keychain-db\n${t}`:`macOS likely showed a Keychain access prompt that was denied or dismissed.\nRun \`security find-generic-password -w -s "${o}"\` in a GUI\nTerminal and click "Always Allow".\n${t}`;const c=[null!==e?`exit ${e}`:null,n||null].filter(Boolean).join(": ");return new Error(`Could not retrieve ${o} key from macOS Keychain.\n${a}`+(c?`\n(security: ${c})`:""))}export function getChromeKeychainPassword(){if(null!==a)return a;let n;try{n=e(`security find-generic-password -w -s "${o}"`,{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch(e){const{status:n,stderr:t}=function(e){const n=e;return{status:"number"==typeof n?.status?n.status:null,stderr:(n?.stderr??"").toString().trim()}}(e);throw l(n,t)}if(!n)throw new Error(`Keychain returned an empty ${o} key.\nThe item may exist with no password. Re-run Google Chrome to recreate it.`);return a=n,n}export function deriveEncryptionKey(e){if(null!==c)return c;const o=e||getChromeKeychainPassword();if(!o)throw new Error("Keychain password cannot be empty for key derivation.");return c=n(o,t,1003,16,"sha1"),c}export function clearKeychainCache(){a=null,c=null}
1
+ import{execFileSync as e}from"node:child_process";import{pbkdf2Sync as n}from"node:crypto";const t=Buffer.from("saltysalt"),r="Chrome Safe Storage",o=44,i=36,s=45,a=new Map,c=new Map;function h(e,n,t){const r="See docs/remote-keychain-access.md for non-interactive / SSH setup.";let a;a=n===o||/could not be found/i.test(t)?`No '${e}' item found in the Keychain.\nRun this browser at least once on this machine so it creates the key.`:n===i||/interaction.*not allowed/i.test(t)?"The Keychain needs a GUI authorization prompt that can't be shown here "+(Boolean(process.env.SSH_CONNECTION||process.env.SSH_TTY)||!process.env.TERM_PROGRAM?"(you appear to be on a remote/SSH session).\n":".\n")+'Authorize once from a GUI Terminal (click "Always Allow"), then unlock the\nkeychain in this session:\n security unlock-keychain ~/Library/Keychains/login.keychain-db\n'+r:n===s?`Keychain authorization failed (wrong password or denied).\nTry: security unlock-keychain ~/Library/Keychains/login.keychain-db\n${r}`:`macOS likely showed a Keychain access prompt that was denied or dismissed.\nRun \`security find-generic-password -w -s "${e}"\` in a GUI\nTerminal and click "Always Allow".\n${r}`;const c=[null!==n?`exit ${n}`:null,t||null].filter(Boolean).join(": ");return new Error(`Could not retrieve ${e} key from macOS Keychain.\n${a}`+(c?`\n(security: ${c})`:""))}export function getKeychainPassword(n=r){const t=a.get(n);if(void 0!==t)return t;let o;try{o=e("security",["find-generic-password","-w","-s",n],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch(e){const{status:t,stderr:r}=function(e){const n=e;return{status:"number"==typeof n?.status?n.status:null,stderr:(n?.stderr??"").toString().trim()}}(e);throw h(n,t,r)}if(!o)throw new Error(`Keychain returned an empty ${n} key.\nThe item may exist with no password. Re-run the browser to recreate it.`);return a.set(n,o),o}export function getChromeKeychainPassword(){return getKeychainPassword(r)}export function deriveEncryptionKey(e=r,o){const i=c.get(e);if(void 0!==i)return i;const s=o||getKeychainPassword(e);if(!s)throw new Error("Keychain password cannot be empty for key derivation.");const a=n(s,t,1003,16,"sha1");return c.set(e,a),a}export function clearKeychainCache(){a.clear(),c.clear()}
@@ -0,0 +1,12 @@
1
+ import type { ChromePlatform } from "./types.js";
2
+ /**
3
+ * macOS backend.
4
+ *
5
+ * Key source: the active browser's "<Browser> Safe Storage" password from the
6
+ * login Keychain (e.g. "Chrome Safe Storage", "Microsoft Edge Safe Storage"),
7
+ * stretched with PBKDF2 (1003 iterations) — implemented in `core/keychain.ts`.
8
+ * Cipher: AES-128-CBC with a fixed 16-space IV — implemented in `core/crypto.ts`.
9
+ * The browser only changes *which* Keychain item and data dir are used; the
10
+ * crypto is identical across all Chromium forks.
11
+ */
12
+ export declare const darwin: ChromePlatform;
@@ -0,0 +1 @@
1
+ import{homedir as r}from"node:os";import{join as o}from"node:path";import{deriveEncryptionKey as e,clearKeychainCache as t}from"../keychain.js";import{decryptValueBuffer as i}from"../crypto.js";import{getActiveBrowser as p}from"../browsers.js";export const darwin={name:"darwin",getDefaultChromeDir:()=>o(r(),"Library","Application Support",...p().dirs.darwin.split("/")),getDecryptionKey:()=>e(p().safeStorageLabel),decrypt:(r,o)=>i(r,o),clearCache(){t()}};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Platform selection + facades.
3
+ *
4
+ * This is the ONLY place that reads `process.platform`. Everything else in the
5
+ * codebase imports the facades below (`getPlatformChromeDir`, `deriveEncryptionKey`,
6
+ * `decryptValue`, `decryptValueBuffer`, `clearKeyCache`) and stays OS-agnostic.
7
+ */
8
+ import type { ChromePlatform } from "./types.js";
9
+ export type { ChromePlatform } from "./types.js";
10
+ export { BROWSERS, listBrowsers, resolveBrowser, getActiveBrowser, setActiveBrowser, type BrowserId, type BrowserDescriptor, } from "../browsers.js";
11
+ /**
12
+ * Returns the backend for the current OS, cached after first call.
13
+ * @throws On an unsupported platform.
14
+ */
15
+ export declare function selectPlatform(): ChromePlatform;
16
+ /** Default Chrome User Data directory for the current OS. */
17
+ export declare function getPlatformChromeDir(): string;
18
+ /**
19
+ * Returns the symmetric key for decrypting this profile's secrets.
20
+ * @param chromeDir - Needed on Windows, where the key lives in `Local State`.
21
+ */
22
+ export declare function deriveEncryptionKey(chromeDir?: string): Buffer;
23
+ /** Decrypts a Chrome blob and returns the plaintext bytes (or a placeholder string). */
24
+ export declare function decryptValueBuffer(blob: Buffer | Uint8Array | null | undefined, key: Buffer): Buffer | string;
25
+ /** Decrypts a Chrome blob and returns the plaintext as a UTF-8 string. */
26
+ export declare function decryptValue(blob: Buffer | Uint8Array | null | undefined, key: Buffer): string;
27
+ /** Clears the active backend's cached key material. */
28
+ export declare function clearKeyCache(): void;
@@ -0,0 +1 @@
1
+ import{darwin as r}from"./darwin.js";import{win32 as e}from"./win32.js";import{linux as t}from"./linux.js";export{BROWSERS,listBrowsers,resolveBrowser,getActiveBrowser,setActiveBrowser}from"../browsers.js";let o=null;export function selectPlatform(){if(o)return o;switch(process.platform){case"darwin":o=r;break;case"win32":o=e;break;case"linux":o=t;break;default:throw new Error(`Unsupported platform: ${process.platform}. chrome-tools supports macOS, Windows, and Linux.`)}return o}export function getPlatformChromeDir(){return selectPlatform().getDefaultChromeDir()}export function deriveEncryptionKey(r){return selectPlatform().getDecryptionKey(r)}export function decryptValueBuffer(r,e){if(!r||0===r.length)return Buffer.alloc(0);const t=Buffer.isBuffer(r)?r:Buffer.from(r);return selectPlatform().decrypt(t,e)}export function decryptValue(r,e){const t=decryptValueBuffer(r,e);return Buffer.isBuffer(t)?t.toString("utf-8"):t}export function clearKeyCache(){selectPlatform().clearCache()}
@@ -0,0 +1,10 @@
1
+ import type { ChromePlatform } from "./types.js";
2
+ /**
3
+ * Linux backend.
4
+ *
5
+ * Key source: the active browser's "Safe Storage" password from the Secret
6
+ * Service (gnome-keyring / KWallet) via `secret-tool`, or the hardcoded
7
+ * "peanuts" fallback, stretched with PBKDF2 (1 iteration).
8
+ * Cipher: AES-128-CBC with a fixed 16-space IV (same primitive as macOS).
9
+ */
10
+ export declare const linux: ChromePlatform;
@@ -0,0 +1 @@
1
+ import{execFileSync as o}from"node:child_process";import{homedir as r}from"node:os";import{join as t}from"node:path";import{pbkdf2Sync as e}from"node:crypto";import{decryptValueBuffer as n}from"../crypto.js";import{getActiveBrowser as i}from"../browsers.js";const p=Buffer.from("saltysalt"),c=new Map;export const linux={name:"linux",getDefaultChromeDir(){const o=process.env.XDG_CONFIG_HOME||t(r(),".config");return t(o,...i().dirs.linux.split("/"))},getDecryptionKey(){const r=i().linuxSecretApp||"chrome",t=c.get(r);if(void 0!==t)return t;const n=e(function(r){try{const t=o("secret-tool",["lookup","application",r],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim();if(t)return t}catch{}return"peanuts"}(r),p,1,16,"sha1");return c.set(r,n),n},decrypt:(o,r)=>n(o,r),clearCache(){c.clear()}};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The platform-specific backend contract.
3
+ *
4
+ * Everything that differs between operating systems is bundled here because the
5
+ * three concerns are coupled — a Windows key only works with the Windows cipher,
6
+ * etc. Each backend (darwin/win32/linux) implements this once; the rest of the
7
+ * codebase stays platform-agnostic and talks to the selected backend through the
8
+ * facades in `./index.ts`.
9
+ */
10
+ export interface ChromePlatform {
11
+ /** Node platform identifier this backend handles (e.g. "darwin", "win32", "linux"). */
12
+ readonly name: string;
13
+ /** Absolute path to Chrome's User Data directory for this OS. */
14
+ getDefaultChromeDir(): string;
15
+ /**
16
+ * Returns the symmetric key used to decrypt this profile's secrets, ready to
17
+ * hand to {@link decrypt}. May read the OS key store and is cached internally.
18
+ *
19
+ * @param chromeDir - User Data dir, needed on platforms (Windows) where the
20
+ * key lives inside `Local State`. Defaults to {@link getDefaultChromeDir}.
21
+ * @throws An actionable Error if the key cannot be obtained.
22
+ */
23
+ getDecryptionKey(chromeDir?: string): Buffer;
24
+ /**
25
+ * Decrypts a single Chrome `v10`/`v11` blob using this platform's scheme
26
+ * (AES-128-CBC on macOS/Linux, AES-256-GCM on Windows).
27
+ *
28
+ * @returns The plaintext bytes, or a `<...>` placeholder string on a
29
+ * recognized but undecryptable blob (never throws).
30
+ */
31
+ decrypt(blob: Buffer, key: Buffer): Buffer | string;
32
+ /** Clears any cached key material. Useful for tests / re-authentication. */
33
+ clearCache(): void;
34
+ }
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,12 @@
1
+ import type { ChromePlatform } from "./types.js";
2
+ /**
3
+ * Windows backend.
4
+ *
5
+ * Key source: `os_crypt.encrypted_key` in Local State — base64, "DPAPI"-prefixed,
6
+ * unprotected via DPAPI into a raw 32-byte AES-256 key (no PBKDF2 on Windows).
7
+ * Cipher: AES-256-GCM (nonce + tag carried in each blob).
8
+ *
9
+ * Note: Chrome 127+ adds app-bound ("v20") keys requiring elevation; those are
10
+ * not handled here. Standard v10/v11 cookies and passwords decrypt fine.
11
+ */
12
+ export declare const win32: ChromePlatform;
@@ -0,0 +1 @@
1
+ import{execFileSync as e}from"node:child_process";import{existsSync as r,readFileSync as t}from"node:fs";import{join as o}from"node:path";import{decryptValueBufferGcm as n}from"../crypto.js";import{getActiveBrowser as s}from"../browsers.js";const c=Buffer.from("DPAPI","ascii"),a=new Map;export const win32={name:"win32",getDefaultChromeDir(){const e=process.env.LOCALAPPDATA||(process.env.USERPROFILE?o(process.env.USERPROFILE,"AppData","Local"):"");return o(e,...s().dirs.win32.split("/"))},getDecryptionKey(n){const s=n||this.getDefaultChromeDir(),i=a.get(s);if(void 0!==i)return i;const p=o(s,"Local State");if(!r(p))throw new Error(`Could not find Chrome's "Local State" at ${p}.\nRun Google Chrome at least once so it creates the encryption key.`);let y;try{const e=t(p,"utf-8");y=JSON.parse(e)?.os_crypt?.encrypted_key}catch(e){throw new Error(`Failed to read os_crypt.encrypted_key from Local State: ${e.message}`)}if(!y)throw new Error("No os_crypt.encrypted_key found in Local State.\nThis profile may predate Chrome's encrypted-key scheme.");const m=Buffer.from(y,"base64");if(!m.subarray(0,c.length).equals(c))throw new Error('Unexpected encrypted_key format (missing "DPAPI" prefix).');const l=function(r){const t=["$ErrorActionPreference='Stop';","Add-Type -AssemblyName System.Security;","$b=[Console]::In.ReadToEnd().Trim();","$enc=[Convert]::FromBase64String($b);","$dec=[System.Security.Cryptography.ProtectedData]::Unprotect($enc,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);","[Console]::Out.Write([Convert]::ToBase64String($dec));"].join(" ");let o;try{o=e("powershell.exe",["-NoProfile","-NonInteractive","-Command",t],{input:r.toString("base64"),encoding:"utf-8"})}catch(e){throw new Error(`DPAPI decryption via PowerShell failed.\nThe encrypted_key can only be unprotected by the same Windows user that created it. Run as that user on the same machine.\n(powershell: ${e?.message??e})`)}return Buffer.from(o.trim(),"base64")}(m.subarray(c.length));return a.set(s,l),l},decrypt:(e,r)=>n(e,r),clearCache(){a.clear()}};
@@ -10,9 +10,11 @@
10
10
  * const bookmarks = getBookmarks({ search: 'github' });
11
11
  * const profiles = discoverProfiles();
12
12
  */
13
- export { getDefaultChromeDir, discoverProfiles, getProfileMetadata, getProfileStates, resolveProfilePath, resolveTargetProfiles, type DiscoveredProfile, type ProfileMetadata, type ProfileState, type ResolveProfilesOptions, } from "./core/chrome.js";
14
- export { getChromeKeychainPassword, deriveEncryptionKey, clearKeychainCache, } from "./core/keychain.js";
15
- export { decryptValue, chromeTimeToDate, } from "./core/crypto.js";
13
+ export { getDefaultChromeDir, discoverProfiles, getProfileMetadata, getProfileStates, resolveProfilePath, resolveTargetProfiles, detectInstalledBrowsers, type DiscoveredProfile, type ProfileMetadata, type ProfileState, type ResolveProfilesOptions, type InstalledBrowser, } from "./core/chrome.js";
14
+ export { BROWSERS, listBrowsers, resolveBrowser, getActiveBrowser, setActiveBrowser, type BrowserId, type BrowserDescriptor, } from "./core/browsers.js";
15
+ export { selectPlatform, getPlatformChromeDir, deriveEncryptionKey, decryptValue, decryptValueBuffer, clearKeyCache, type ChromePlatform, } from "./core/platform/index.js";
16
+ export { getKeychainPassword, getChromeKeychainPassword, clearKeychainCache, } from "./core/keychain.js";
17
+ export { chromeTimeToDate, } from "./core/crypto.js";
16
18
  export { queryDatabase, tableExists, } from "./core/db.js";
17
19
  export { getPasswords } from "./modules/passwords.js";
18
20
  export { getBookmarks } from "./modules/bookmarks.js";
package/dist/src/index.js CHANGED
@@ -1 +1 @@
1
- export{getDefaultChromeDir,discoverProfiles,getProfileMetadata,getProfileStates,resolveProfilePath,resolveTargetProfiles}from"./core/chrome.js";export{getChromeKeychainPassword,deriveEncryptionKey,clearKeychainCache}from"./core/keychain.js";export{decryptValue,chromeTimeToDate}from"./core/crypto.js";export{queryDatabase,tableExists}from"./core/db.js";export{getPasswords}from"./modules/passwords.js";export{getBookmarks}from"./modules/bookmarks.js";export{getHistory,getSearchTerms}from"./modules/history.js";export{getCookies}from"./modules/cookies.js";export{getAutofillEntries,getCreditCards,getAddresses}from"./modules/autofill.js";export{getDownloads}from"./modules/downloads.js";export{getExtensions}from"./modules/extensions.js";export{getSearchEngines}from"./modules/search-engines.js";export{getTopSites}from"./modules/top-sites.js";
1
+ export{getDefaultChromeDir,discoverProfiles,getProfileMetadata,getProfileStates,resolveProfilePath,resolveTargetProfiles,detectInstalledBrowsers}from"./core/chrome.js";export{BROWSERS,listBrowsers,resolveBrowser,getActiveBrowser,setActiveBrowser}from"./core/browsers.js";export{selectPlatform,getPlatformChromeDir,deriveEncryptionKey,decryptValue,decryptValueBuffer,clearKeyCache}from"./core/platform/index.js";export{getKeychainPassword,getChromeKeychainPassword,clearKeychainCache}from"./core/keychain.js";export{chromeTimeToDate}from"./core/crypto.js";export{queryDatabase,tableExists}from"./core/db.js";export{getPasswords}from"./modules/passwords.js";export{getBookmarks}from"./modules/bookmarks.js";export{getHistory,getSearchTerms}from"./modules/history.js";export{getCookies}from"./modules/cookies.js";export{getAutofillEntries,getCreditCards,getAddresses}from"./modules/autofill.js";export{getDownloads}from"./modules/downloads.js";export{getExtensions}from"./modules/extensions.js";export{getSearchEngines}from"./modules/search-engines.js";export{getTopSites}from"./modules/top-sites.js";
@@ -1,6 +1,7 @@
1
1
  export interface AutofillOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  search?: string;
5
6
  limit?: number;
6
7
  }
@@ -15,6 +16,7 @@ export interface AutofillEntry {
15
16
  export interface CreditCardOptions {
16
17
  chromeDir?: string;
17
18
  profile?: string;
19
+ browser?: string;
18
20
  }
19
21
  export interface CreditCardEntry {
20
22
  profile: string;
@@ -31,6 +33,7 @@ export interface CreditCardEntry {
31
33
  export interface AddressOptions {
32
34
  chromeDir?: string;
33
35
  profile?: string;
36
+ browser?: string;
34
37
  }
35
38
  export interface AddressEntry {
36
39
  profile: string;
@@ -54,7 +57,7 @@ export interface AddressEntry {
54
57
  * @param options - Filtering and targeting options.
55
58
  * @returns Array of autofill entries.
56
59
  */
57
- export declare function getAutofillEntries({ chromeDir, profile, search, limit, }?: AutofillOptions): AutofillEntry[];
60
+ export declare function getAutofillEntries({ chromeDir, profile, browser, search, limit, }?: AutofillOptions): AutofillEntry[];
58
61
  /**
59
62
  * Retrieves saved credit cards from Chrome's Web Data database.
60
63
  * Card numbers are encrypted and will be decrypted.
@@ -62,11 +65,11 @@ export declare function getAutofillEntries({ chromeDir, profile, search, limit,
62
65
  * @param options - Targeting options.
63
66
  * @returns Array of credit card entries.
64
67
  */
65
- export declare function getCreditCards({ chromeDir, profile }?: CreditCardOptions): CreditCardEntry[];
68
+ export declare function getCreditCards({ chromeDir, profile, browser }?: CreditCardOptions): CreditCardEntry[];
66
69
  /**
67
70
  * Retrieves saved addresses from Chrome's Web Data database.
68
71
  *
69
72
  * @param options - Targeting options.
70
73
  * @returns Array of address entries.
71
74
  */
72
- export declare function getAddresses({ chromeDir, profile }?: AddressOptions): AddressEntry[];
75
+ export declare function getAddresses({ chromeDir, profile, browser }?: AddressOptions): AddressEntry[];
@@ -1 +1 @@
1
- import{join as e}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{deriveEncryptionKey as o}from"../core/keychain.js";import{decryptValue as n,chromeTimeToDate as i}from"../core/crypto.js";import{queryDatabase as a,tableExists as r}from"../core/db.js";export function getAutofillEntries({chromeDir:o,profile:n,search:d,limit:c=100}={}){const s=t({chromeDir:o,profile:n}),u=[];for(const t of s){const o=e(t.path,"Web Data");try{if(!r(o,"autofill"))continue;let e="SELECT name, value, date_created, date_last_used, count\n FROM autofill ORDER BY date_last_used DESC";c&&(e+=` LIMIT ${parseInt(c,10)}`);const n=a(o,e);for(const e of n){const o={profile:t.name,name:e.name||"",value:e.value||"",dateCreated:i(e.date_created?1000000n*BigInt(e.date_created)+11644473600000000n:0),dateLastUsed:i(e.date_last_used?1000000n*BigInt(e.date_last_used)+11644473600000000n:0),count:e.count||0};if(d){const e=d.toLowerCase();if(!(o.name&&o.name.toLowerCase().includes(e)||o.value&&o.value.toLowerCase().includes(e)))continue}u.push(o)}}catch{}}return u}export function getCreditCards({chromeDir:d,profile:c}={}){const s=t({chromeDir:d,profile:c}),u=o(),l=[];for(const t of s){const o=e(t.path,"Web Data");try{if(!r(o,"credit_cards"))continue;const e=a(o,"SELECT guid, name_on_card, expiration_month, expiration_year,\n card_number_encrypted, date_modified, origin, use_count,\n use_date, nickname\n FROM credit_cards ORDER BY use_date DESC");for(const o of e){let e="";o.card_number_encrypted&&(e=n(o.card_number_encrypted,u)),l.push({profile:t.name,guid:o.guid||"",nameOnCard:o.name_on_card||"",expirationMonth:o.expiration_month||0,expirationYear:o.expiration_year||0,cardNumber:e,nickname:o.nickname||"",origin:o.origin||"",useCount:o.use_count||0,dateModified:i(o.date_modified?1000000n*BigInt(o.date_modified)+11644473600000000n:0)})}}catch{}}return l}export function getAddresses({chromeDir:o,profile:n}={}){const d=t({chromeDir:o,profile:n}),c=[];for(const t of d){const o=e(t.path,"Web Data");try{if(!r(o,"autofill_profiles"))continue;const e=a(o,"SELECT guid, company_name, street_address, dependent_locality,\n city, state, zipcode, country_code, date_modified,\n origin, use_count, use_date, language_code\n FROM autofill_profiles ORDER BY use_date DESC");let n=[],d=[],s=[];try{r(o,"autofill_profile_names")&&(n=a(o,"SELECT guid, first_name, middle_name, last_name, full_name FROM autofill_profile_names"))}catch{}try{r(o,"autofill_profile_emails")&&(d=a(o,"SELECT guid, email FROM autofill_profile_emails"))}catch{}try{r(o,"autofill_profile_phones")&&(s=a(o,"SELECT guid, number FROM autofill_profile_phones"))}catch{}const u=new Map;for(const e of n)u.set(e.guid,e.full_name||[e.first_name,e.middle_name,e.last_name].filter(Boolean).join(" "));const l=new Map;for(const e of d)l.set(e.guid,e.email||"");const f=new Map;for(const e of s)f.set(e.guid,e.number||"");for(const o of e)c.push({profile:t.name,guid:o.guid||"",fullName:u.get(o.guid)||"",email:l.get(o.guid)||"",phone:f.get(o.guid)||"",companyName:o.company_name||"",streetAddress:o.street_address||"",city:o.city||"",state:o.state||"",zipcode:o.zipcode||"",countryCode:o.country_code||"",origin:o.origin||"",useCount:o.use_count||0,dateModified:i(o.date_modified?1000000n*BigInt(o.date_modified)+11644473600000000n:0)})}catch{}}return c}
1
+ import{join as e}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{deriveEncryptionKey as o,decryptValue as n}from"../core/platform/index.js";import{chromeTimeToDate as r}from"../core/crypto.js";import{queryDatabase as i,tableExists as a}from"../core/db.js";export function getAutofillEntries({chromeDir:o,profile:n,browser:d,search:c,limit:s=100}={}){const u=t({chromeDir:o,profile:n,browser:d}),l=[];for(const t of u){const o=e(t.path,"Web Data");try{if(!a(o,"autofill"))continue;let e="SELECT name, value, date_created, date_last_used, count\n FROM autofill ORDER BY date_last_used DESC";s&&(e+=` LIMIT ${parseInt(s,10)}`);const n=i(o,e);for(const e of n){const o={profile:t.name,name:e.name||"",value:e.value||"",dateCreated:r(e.date_created?1000000n*BigInt(e.date_created)+11644473600000000n:0),dateLastUsed:r(e.date_last_used?1000000n*BigInt(e.date_last_used)+11644473600000000n:0),count:e.count||0};if(c){const e=c.toLowerCase();if(!(o.name&&o.name.toLowerCase().includes(e)||o.value&&o.value.toLowerCase().includes(e)))continue}l.push(o)}}catch{}}return l}export function getCreditCards({chromeDir:d,profile:c,browser:s}={}){const u=t({chromeDir:d,profile:c,browser:s}),l=o(),f=[];for(const t of u){const o=e(t.path,"Web Data");try{if(!a(o,"credit_cards"))continue;const e=i(o,"SELECT guid, name_on_card, expiration_month, expiration_year,\n card_number_encrypted, date_modified, origin, use_count,\n use_date, nickname\n FROM credit_cards ORDER BY use_date DESC");for(const o of e){let e="";o.card_number_encrypted&&(e=n(o.card_number_encrypted,l)),f.push({profile:t.name,guid:o.guid||"",nameOnCard:o.name_on_card||"",expirationMonth:o.expiration_month||0,expirationYear:o.expiration_year||0,cardNumber:e,nickname:o.nickname||"",origin:o.origin||"",useCount:o.use_count||0,dateModified:r(o.date_modified?1000000n*BigInt(o.date_modified)+11644473600000000n:0)})}}catch{}}return f}export function getAddresses({chromeDir:o,profile:n,browser:d}={}){const c=t({chromeDir:o,profile:n,browser:d}),s=[];for(const t of c){const o=e(t.path,"Web Data");try{if(!a(o,"autofill_profiles"))continue;const e=i(o,"SELECT guid, company_name, street_address, dependent_locality,\n city, state, zipcode, country_code, date_modified,\n origin, use_count, use_date, language_code\n FROM autofill_profiles ORDER BY use_date DESC");let n=[],d=[],c=[];try{a(o,"autofill_profile_names")&&(n=i(o,"SELECT guid, first_name, middle_name, last_name, full_name FROM autofill_profile_names"))}catch{}try{a(o,"autofill_profile_emails")&&(d=i(o,"SELECT guid, email FROM autofill_profile_emails"))}catch{}try{a(o,"autofill_profile_phones")&&(c=i(o,"SELECT guid, number FROM autofill_profile_phones"))}catch{}const u=new Map;for(const e of n)u.set(e.guid,e.full_name||[e.first_name,e.middle_name,e.last_name].filter(Boolean).join(" "));const l=new Map;for(const e of d)l.set(e.guid,e.email||"");const f=new Map;for(const e of c)f.set(e.guid,e.number||"");for(const o of e)s.push({profile:t.name,guid:o.guid||"",fullName:u.get(o.guid)||"",email:l.get(o.guid)||"",phone:f.get(o.guid)||"",companyName:o.company_name||"",streetAddress:o.street_address||"",city:o.city||"",state:o.state||"",zipcode:o.zipcode||"",countryCode:o.country_code||"",origin:o.origin||"",useCount:o.use_count||0,dateModified:r(o.date_modified?1000000n*BigInt(o.date_modified)+11644473600000000n:0)})}catch{}}return s}
@@ -1,6 +1,7 @@
1
1
  export interface BookmarkOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  folder?: string;
5
6
  search?: string;
6
7
  }
@@ -20,4 +21,4 @@ export interface BookmarkEntry {
20
21
  * @param options - Options for filtering and targeting.
21
22
  * @returns Filtered bookmark entries.
22
23
  */
23
- export declare function getBookmarks({ chromeDir, profile, folder, search }?: BookmarkOptions): BookmarkEntry[];
24
+ export declare function getBookmarks({ chromeDir, profile, folder, search, browser }?: BookmarkOptions): BookmarkEntry[];
@@ -1 +1 @@
1
- import{existsSync as o,readFileSync as r}from"node:fs";import{join as e}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{chromeTimeToDate as n}from"../core/crypto.js";function s(o){return o&&"0"!==o?n(BigInt(o)):"N/A"}function i(o,r=""){const e=[];if(!o)return e;const t=r?`${r}/${o.name||""}`:o.name||"";if("url"===o.type&&e.push({title:o.name||"",url:o.url||"",folder:r,dateAdded:s(o.date_added),dateLastUsed:s(o.date_last_used),id:o.id,guid:o.guid||""}),o.children&&Array.isArray(o.children))for(const r of o.children)e.push(...i(r,t));return e}export function getBookmarks({chromeDir:n,profile:s,folder:c,search:f}={}){const d=t({chromeDir:n,profile:s}),a=[];for(const t of d){const n=e(t.path,"Bookmarks");if(o(n))try{const o=r(n,"utf-8"),e=JSON.parse(o).roots;if(!e)continue;const s=["bookmark_bar","other","synced"];for(const o of s)if(e[o]){const r=i(e[o],"");for(const o of r)o.profile=t.name,a.push(o)}}catch{}}let l=a;if(c){const o=c.toLowerCase();l=l.filter(r=>r.folder.toLowerCase().includes(o))}if(f){const o=f.toLowerCase();l=l.filter(r=>r.title.toLowerCase().includes(o)||r.url.toLowerCase().includes(o))}return l}
1
+ import{existsSync as o,readFileSync as r}from"node:fs";import{join as e}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{chromeTimeToDate as s}from"../core/crypto.js";function n(o){return o&&"0"!==o?s(BigInt(o)):"N/A"}function i(o,r=""){const e=[];if(!o)return e;const t=r?`${r}/${o.name||""}`:o.name||"";if("url"===o.type&&e.push({title:o.name||"",url:o.url||"",folder:r,dateAdded:n(o.date_added),dateLastUsed:n(o.date_last_used),id:o.id,guid:o.guid||""}),o.children&&Array.isArray(o.children))for(const r of o.children)e.push(...i(r,t));return e}export function getBookmarks({chromeDir:s,profile:n,folder:c,search:f,browser:d}={}){const a=t({chromeDir:s,profile:n,browser:d}),l=[];for(const t of a){const s=e(t.path,"Bookmarks");if(o(s))try{const o=r(s,"utf-8"),e=JSON.parse(o).roots;if(!e)continue;const n=["bookmark_bar","other","synced"];for(const o of n)if(e[o]){const r=i(e[o],"");for(const o of r)o.profile=t.name,l.push(o)}}catch{}}let u=l;if(c){const o=c.toLowerCase();u=u.filter(r=>r.folder.toLowerCase().includes(o))}if(f){const o=f.toLowerCase();u=u.filter(r=>r.title.toLowerCase().includes(o)||r.url.toLowerCase().includes(o))}return u}
@@ -1,6 +1,7 @@
1
1
  export interface CookieOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  domain?: string;
5
6
  url?: string;
6
7
  decrypt?: boolean;
@@ -30,7 +31,7 @@ declare function pathMatch(urlPath: string, cookiePath: string): boolean;
30
31
  * @param options - Targeting and filtering options.
31
32
  * @returns Array of cookie entries.
32
33
  */
33
- export declare function getCookies({ chromeDir, profile, domain, url, decrypt, limit, }?: CookieOptions): CookieEntry[];
34
+ export declare function getCookies({ chromeDir, profile, browser, domain, url, decrypt, limit, }?: CookieOptions): CookieEntry[];
34
35
  export declare const _test: {
35
36
  parseURL: typeof parseURL;
36
37
  getPossibleHostKeys: typeof getPossibleHostKeys;
@@ -1 +1 @@
1
- import{join as t}from"node:path";import{createHash as e}from"node:crypto";import{resolveTargetProfiles as n}from"../core/chrome.js";import{deriveEncryptionKey as o}from"../core/keychain.js";import{decryptValueBuffer as s,chromeTimeToDate as r}from"../core/crypto.js";import{queryDatabase as c,tableExists as i}from"../core/db.js";function a(t,n,o){const r=s(t,n);if(!Buffer.isBuffer(r))return r;const c=e("sha256").update(o).digest();return(r.length>=c.length&&r.subarray(0,c.length).equals(c)?r.subarray(c.length):r).toString("utf-8")}function u(t){if(!t)return null;try{return new URL(t)}catch{try{if(!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(t))return new URL("https://"+t)}catch{}}return null}function h(t){const e=new Set;e.add(t),e.add("."+t);const n=t.split(".");for(let t=1;t<n.length;t++){const o=n.slice(t).join(".");e.add(o),e.add("."+o)}return Array.from(e)}function p(t,e){return t===e||!(!e.startsWith(".")||t!==e.slice(1)&&!t.endsWith(e))}function l(t,e){const n=t||"/",o=e||"/";return"/"===o||(n===o||!(!n.startsWith(o)||!o.endsWith("/")&&"/"!==n[o.length]))}export function getCookies({chromeDir:e,profile:s,domain:f,url:m,decrypt:d=!0,limit:y=100}={}){const _=n({chromeDir:e,profile:s}),k=d?o():null,g=[],j=u(m);for(const e of _){const n=t(e.path,"Cookies");try{if(!i(n,"cookies"))continue;let t="SELECT host_key, name, value, encrypted_value, path,\n expires_utc, is_secure, is_httponly, samesite,\n creation_utc, last_access_utc\n FROM cookies";const o=[],s=[];if(f)o.push("host_key LIKE ?"),s.push(`%${f}%`);else if(j){const t=h(j.hostname),e=t.map(()=>"?").join(", ");o.push(`host_key IN (${e})`),s.push(...t)}o.length>0&&(t+=` WHERE ${o.join(" AND ")}`),t+=" ORDER BY host_key, name",y&&!m&&(t+=` LIMIT ${parseInt(y,10)}`);const u=c(n,t,s);for(const t of u){if(j){if(!p(j.hostname,t.host_key||""))continue;if(!l(j.pathname||"/",t.path||"/"))continue;if(t.is_secure&&"https:"!==j.protocol)continue}let n=t.value||"";!n&&t.encrypted_value&&k&&(n=a(t.encrypted_value,k,t.host_key||"")),g.push({profile:e.name,domain:t.host_key||"",name:t.name||"",value:n,path:t.path||"/",expires:r(t.expires_utc||0),secure:Boolean(t.is_secure),httpOnly:Boolean(t.is_httponly),sameSite:["unspecified","lax","strict","none"][t.samesite]||"unspecified",created:r(t.creation_utc||0),lastAccessed:r(t.last_access_utc||0)})}}catch{}}return y&&m?g.slice(0,parseInt(y,10)):g}export const _test={parseURL:u,getPossibleHostKeys:h,domainMatch:p,pathMatch:l};
1
+ import{join as t}from"node:path";import{createHash as e}from"node:crypto";import{resolveTargetProfiles as o}from"../core/chrome.js";import{deriveEncryptionKey as n,decryptValueBuffer as s}from"../core/platform/index.js";import{chromeTimeToDate as r}from"../core/crypto.js";import{queryDatabase as i,tableExists as c}from"../core/db.js";function a(t,o,n){const r=s(t,o);if(!Buffer.isBuffer(r))return r;const i=e("sha256").update(n).digest();return(r.length>=i.length&&r.subarray(0,i.length).equals(i)?r.subarray(i.length):r).toString("utf-8")}function u(t){if(!t)return null;try{return new URL(t)}catch{try{if(!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(t))return new URL("https://"+t)}catch{}}return null}function p(t){const e=new Set;e.add(t),e.add("."+t);const o=t.split(".");for(let t=1;t<o.length;t++){const n=o.slice(t).join(".");e.add(n),e.add("."+n)}return Array.from(e)}function h(t,e){return t===e||!(!e.startsWith(".")||t!==e.slice(1)&&!t.endsWith(e))}function l(t,e){const o=t||"/",n=e||"/";return"/"===n||(o===n||!(!o.startsWith(n)||!n.endsWith("/")&&"/"!==o[n.length]))}export function getCookies({chromeDir:e,profile:s,browser:f,domain:m,url:d,decrypt:_=!0,limit:y=100}={}){const g=o({chromeDir:e,profile:s,browser:f}),k=_?n():null,j=[],x=u(d);for(const e of g){const o=t(e.path,"Cookies");try{if(!c(o,"cookies"))continue;let t="SELECT host_key, name, value, encrypted_value, path,\n expires_utc, is_secure, is_httponly, samesite,\n creation_utc, last_access_utc\n FROM cookies";const n=[],s=[];if(m)n.push("host_key LIKE ?"),s.push(`%${m}%`);else if(x){const t=p(x.hostname),e=t.map(()=>"?").join(", ");n.push(`host_key IN (${e})`),s.push(...t)}n.length>0&&(t+=` WHERE ${n.join(" AND ")}`),t+=" ORDER BY host_key, name",y&&!d&&(t+=` LIMIT ${parseInt(y,10)}`);const u=i(o,t,s);for(const t of u){if(x){if(!h(x.hostname,t.host_key||""))continue;if(!l(x.pathname||"/",t.path||"/"))continue;if(t.is_secure&&"https:"!==x.protocol)continue}let o=t.value||"";!o&&t.encrypted_value&&k&&(o=a(t.encrypted_value,k,t.host_key||"")),j.push({profile:e.name,domain:t.host_key||"",name:t.name||"",value:o,path:t.path||"/",expires:r(t.expires_utc||0),secure:Boolean(t.is_secure),httpOnly:Boolean(t.is_httponly),sameSite:["unspecified","lax","strict","none"][t.samesite]||"unspecified",created:r(t.creation_utc||0),lastAccessed:r(t.last_access_utc||0)})}}catch{}}return y&&d?j.slice(0,parseInt(y,10)):j}export const _test={parseURL:u,getPossibleHostKeys:p,domainMatch:h,pathMatch:l};
@@ -1,6 +1,7 @@
1
1
  export interface DownloadOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  search?: string;
5
6
  limit?: number;
6
7
  }
@@ -25,4 +26,4 @@ export interface DownloadEntry {
25
26
  * @param options - Targeting, filtering, and limit options.
26
27
  * @returns Array of download entries.
27
28
  */
28
- export declare function getDownloads({ chromeDir, profile, search, limit, }?: DownloadOptions): DownloadEntry[];
29
+ export declare function getDownloads({ chromeDir, profile, browser, search, limit, }?: DownloadOptions): DownloadEntry[];
@@ -1 +1 @@
1
- import{join as e}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{chromeTimeToDate as r}from"../core/crypto.js";import{queryDatabase as o,tableExists as n}from"../core/db.js";const a={0:"in_progress",1:"complete",2:"cancelled",3:"interrupted"},s={0:"not_dangerous",1:"dangerous_file",2:"dangerous_url",3:"dangerous_content",4:"maybe_dangerous_content",5:"uncommon_content",6:"user_validated",7:"dangerous_host",8:"potentially_unwanted",9:"allowlisted_by_policy"};export function getDownloads({chromeDir:i,profile:_,search:l,limit:d=100}={}){const c=t({chromeDir:i,profile:_}),m=[];for(const t of c){const i=e(t.path,"History");try{if(!n(i,"downloads"))continue;let e="SELECT id, target_path, tab_url, total_bytes, received_bytes,\n start_time, end_time, mime_type, original_mime_type,\n state, danger_type, opened, referrer, site_url\n FROM downloads ORDER BY start_time DESC";d&&(e+=` LIMIT ${parseInt(d,10)}`);const _=o(i,e);for(const e of _){const o={profile:t.name,targetPath:e.target_path||"",url:e.tab_url||"",siteUrl:e.site_url||"",referrer:e.referrer||"",totalBytes:e.total_bytes||0,receivedBytes:e.received_bytes||0,mimeType:e.mime_type||e.original_mime_type||"",state:a[e.state]||`unknown(${e.state})`,dangerType:s[e.danger_type]||`unknown(${e.danger_type})`,opened:Boolean(e.opened),startTime:r(e.start_time||0),endTime:r(e.end_time||0)};if(l){const e=l.toLowerCase();if(!(o.targetPath&&o.targetPath.toLowerCase().includes(e)||o.url&&o.url.toLowerCase().includes(e)))continue}m.push(o)}}catch{}}return m}
1
+ import{join as e}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{chromeTimeToDate as r}from"../core/crypto.js";import{queryDatabase as o,tableExists as n}from"../core/db.js";const a={0:"in_progress",1:"complete",2:"cancelled",3:"interrupted"},s={0:"not_dangerous",1:"dangerous_file",2:"dangerous_url",3:"dangerous_content",4:"maybe_dangerous_content",5:"uncommon_content",6:"user_validated",7:"dangerous_host",8:"potentially_unwanted",9:"allowlisted_by_policy"};export function getDownloads({chromeDir:i,profile:_,browser:l,search:d,limit:c=100}={}){const m=t({chromeDir:i,profile:_,browser:l}),p=[];for(const t of m){const i=e(t.path,"History");try{if(!n(i,"downloads"))continue;let e="SELECT id, target_path, tab_url, total_bytes, received_bytes,\n start_time, end_time, mime_type, original_mime_type,\n state, danger_type, opened, referrer, site_url\n FROM downloads ORDER BY start_time DESC";c&&(e+=` LIMIT ${parseInt(c,10)}`);const _=o(i,e);for(const e of _){const o={profile:t.name,targetPath:e.target_path||"",url:e.tab_url||"",siteUrl:e.site_url||"",referrer:e.referrer||"",totalBytes:e.total_bytes||0,receivedBytes:e.received_bytes||0,mimeType:e.mime_type||e.original_mime_type||"",state:a[e.state]||`unknown(${e.state})`,dangerType:s[e.danger_type]||`unknown(${e.danger_type})`,opened:Boolean(e.opened),startTime:r(e.start_time||0),endTime:r(e.end_time||0)};if(d){const e=d.toLowerCase();if(!(o.targetPath&&o.targetPath.toLowerCase().includes(e)||o.url&&o.url.toLowerCase().includes(e)))continue}p.push(o)}}catch{}}return p}
@@ -1,6 +1,7 @@
1
1
  export interface ExtensionOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  search?: string;
5
6
  }
6
7
  export interface ExtensionEntry {
@@ -22,4 +23,4 @@ export interface ExtensionEntry {
22
23
  * @param options - Targeting and filtering options.
23
24
  * @returns Array of extension entries.
24
25
  */
25
- export declare function getExtensions({ chromeDir, profile, search }?: ExtensionOptions): ExtensionEntry[];
26
+ export declare function getExtensions({ chromeDir, profile, search, browser }?: ExtensionOptions): ExtensionEntry[];
@@ -1 +1 @@
1
- import{existsSync as e,readFileSync as o}from"node:fs";import{join as t}from"node:path";import{resolveTargetProfiles as n}from"../core/chrome.js";export function getExtensions({chromeDir:r,profile:i,search:s}={}){const c=n({chromeDir:r,profile:i}),a=[];for(const n of c){let r=null;for(const i of["Secure Preferences","Preferences"]){const s=t(n.path,i);if(e(s))try{r=JSON.parse(o(s,"utf-8"));break}catch{continue}}if(!r)continue;const i=r?.extensions?.settings;if(i&&"object"==typeof i)for(const[e,o]of Object.entries(i)){const t=o;if(5===t.location||10===t.location)continue;const r=t.manifest||{},i={profile:n.name,id:e,name:r.name||t.name||e,version:r.version||t.version||"unknown",description:r.description||"",enabled:1===t.state,homepageUrl:r.homepage_url||"",updateUrl:r.update_url||"",installTime:t.install_time||"",fromWebstore:t.from_webstore||!1,location:t.location||0};if(i.name.startsWith("__MSG_")&&(i.name=e),i.description.startsWith("__MSG_")&&(i.description=""),s){const e=s.toLowerCase();if(!(i.name&&i.name.toLowerCase().includes(e)||i.id&&i.id.toLowerCase().includes(e)))continue}a.push(i)}}return a}
1
+ import{existsSync as e,readFileSync as o}from"node:fs";import{join as t}from"node:path";import{resolveTargetProfiles as n}from"../core/chrome.js";export function getExtensions({chromeDir:r,profile:i,search:s,browser:c}={}){const a=n({chromeDir:r,profile:i,browser:c}),f=[];for(const n of a){let r=null;for(const i of["Secure Preferences","Preferences"]){const s=t(n.path,i);if(e(s))try{r=JSON.parse(o(s,"utf-8"));break}catch{continue}}if(!r)continue;const i=r?.extensions?.settings;if(i&&"object"==typeof i)for(const[e,o]of Object.entries(i)){const t=o;if(5===t.location||10===t.location)continue;const r=t.manifest||{},i={profile:n.name,id:e,name:r.name||t.name||e,version:r.version||t.version||"unknown",description:r.description||"",enabled:1===t.state,homepageUrl:r.homepage_url||"",updateUrl:r.update_url||"",installTime:t.install_time||"",fromWebstore:t.from_webstore||!1,location:t.location||0};if(i.name.startsWith("__MSG_")&&(i.name=e),i.description.startsWith("__MSG_")&&(i.description=""),s){const e=s.toLowerCase();if(!(i.name&&i.name.toLowerCase().includes(e)||i.id&&i.id.toLowerCase().includes(e)))continue}f.push(i)}}return f}
@@ -1,6 +1,7 @@
1
1
  export interface HistoryOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  search?: string;
5
6
  limit?: number;
6
7
  }
@@ -14,6 +15,7 @@ export interface HistoryEntry {
14
15
  export interface SearchTermOptions {
15
16
  chromeDir?: string;
16
17
  profile?: string;
18
+ browser?: string;
17
19
  limit?: number;
18
20
  }
19
21
  export interface SearchTermEntry {
@@ -27,11 +29,11 @@ export interface SearchTermEntry {
27
29
  * @param options - Filtering and targeting options.
28
30
  * @returns Array of history entries.
29
31
  */
30
- export declare function getHistory({ chromeDir, profile, search, limit }?: HistoryOptions): HistoryEntry[];
32
+ export declare function getHistory({ chromeDir, profile, search, limit, browser }?: HistoryOptions): HistoryEntry[];
31
33
  /**
32
34
  * Retrieves search terms from Chrome's History database.
33
35
  *
34
36
  * @param options - Targeting and limit options.
35
37
  * @returns Array of search term entries.
36
38
  */
37
- export declare function getSearchTerms({ chromeDir, profile, limit }?: SearchTermOptions): SearchTermEntry[];
39
+ export declare function getSearchTerms({ chromeDir, profile, limit, browser }?: SearchTermOptions): SearchTermEntry[];
@@ -1 +1 @@
1
- import{join as t}from"node:path";import{resolveTargetProfiles as r}from"../core/chrome.js";import{chromeTimeToDate as o}from"../core/crypto.js";import{queryDatabase as e,tableExists as i}from"../core/db.js";export function getHistory({chromeDir:s,profile:c,search:n,limit:l=100}={}){const u=r({chromeDir:s,profile:c}),m=[];for(const r of u){const s=t(r.path,"History");try{if(!i(s,"urls"))continue;let t="SELECT url, title, visit_count, last_visit_time\n FROM urls ORDER BY last_visit_time DESC";l&&(t+=` LIMIT ${parseInt(l,10)}`);const c=e(s,t);for(const t of c){const e={profile:r.name,url:t.url,title:t.title||"",visitCount:t.visit_count||0,lastVisit:o(t.last_visit_time||0)};if(n){const t=n.toLowerCase();if(!(e.url&&e.url.toLowerCase().includes(t)||e.title&&e.title.toLowerCase().includes(t)))continue}m.push(e)}}catch{}}return m}export function getSearchTerms({chromeDir:o,profile:s,limit:c=100}={}){const n=r({chromeDir:o,profile:s}),l=[];for(const r of n){const o=t(r.path,"History");try{if(!i(o,"keyword_search_terms"))continue;let t="SELECT kst.term, u.url\n FROM keyword_search_terms kst\n JOIN urls u ON kst.url_id = u.id\n ORDER BY u.last_visit_time DESC";c&&(t+=` LIMIT ${parseInt(c,10)}`);const s=e(o,t);for(const t of s)l.push({profile:r.name,term:t.term||"",url:t.url||""})}catch{}}return l}
1
+ import{join as t}from"node:path";import{resolveTargetProfiles as r}from"../core/chrome.js";import{chromeTimeToDate as o}from"../core/crypto.js";import{queryDatabase as e,tableExists as i}from"../core/db.js";export function getHistory({chromeDir:s,profile:c,search:n,limit:l=100,browser:u}={}){const m=r({chromeDir:s,profile:c,browser:u}),f=[];for(const r of m){const s=t(r.path,"History");try{if(!i(s,"urls"))continue;let t="SELECT url, title, visit_count, last_visit_time\n FROM urls ORDER BY last_visit_time DESC";l&&(t+=` LIMIT ${parseInt(l,10)}`);const c=e(s,t);for(const t of c){const e={profile:r.name,url:t.url,title:t.title||"",visitCount:t.visit_count||0,lastVisit:o(t.last_visit_time||0)};if(n){const t=n.toLowerCase();if(!(e.url&&e.url.toLowerCase().includes(t)||e.title&&e.title.toLowerCase().includes(t)))continue}f.push(e)}}catch{}}return f}export function getSearchTerms({chromeDir:o,profile:s,limit:c=100,browser:n}={}){const l=r({chromeDir:o,profile:s,browser:n}),u=[];for(const r of l){const o=t(r.path,"History");try{if(!i(o,"keyword_search_terms"))continue;let t="SELECT kst.term, u.url\n FROM keyword_search_terms kst\n JOIN urls u ON kst.url_id = u.id\n ORDER BY u.last_visit_time DESC";c&&(t+=` LIMIT ${parseInt(c,10)}`);const s=e(o,t);for(const t of s)u.push({profile:r.name,term:t.term||"",url:t.url||""})}catch{}}return u}
@@ -1,6 +1,7 @@
1
1
  export interface PasswordOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  search?: string;
5
6
  }
6
7
  export interface PasswordEntry {
@@ -17,4 +18,4 @@ export interface PasswordEntry {
17
18
  * @param options - Options for profile selection and filtering.
18
19
  * @returns Array of decrypted password entries.
19
20
  */
20
- export declare function getPasswords({ chromeDir, profile, search }?: PasswordOptions): PasswordEntry[];
21
+ export declare function getPasswords({ chromeDir, profile, search, browser }?: PasswordOptions): PasswordEntry[];
@@ -1 +1 @@
1
- import{join as r}from"node:path";import{resolveTargetProfiles as o}from"../core/chrome.js";import{deriveEncryptionKey as e}from"../core/keychain.js";import{decryptValue as s,chromeTimeToDate as t}from"../core/crypto.js";import{queryDatabase as n,tableExists as a}from"../core/db.js";export function getPasswords({chromeDir:i,profile:u,search:c}={}){const l=o({chromeDir:i,profile:u}),m=e(),f=[];for(const o of l){const e=r(o.path,"Login Data");try{if(!a(e,"logins"))continue;const r=n(e,"SELECT origin_url, username_value, password_value, date_last_used\n FROM logins ORDER BY origin_url");for(const e of r){const r={profile:o.name,url:e.origin_url,username:e.username_value,password:s(e.password_value,m),lastUsed:t(e.date_last_used||0)};if(c){const o=c.toLowerCase();if(!(r.url&&r.url.toLowerCase().includes(o)||r.username&&r.username.toLowerCase().includes(o)))continue}f.push(r)}}catch(r){f.push({profile:o.name,error:r.message})}}return f.filter(r=>!r.error)}
1
+ import{join as r}from"node:path";import{resolveTargetProfiles as o}from"../core/chrome.js";import{deriveEncryptionKey as e,decryptValue as s}from"../core/platform/index.js";import{chromeTimeToDate as t}from"../core/crypto.js";import{queryDatabase as n,tableExists as a}from"../core/db.js";export function getPasswords({chromeDir:i,profile:u,search:c,browser:l}={}){const m=o({chromeDir:i,profile:u,browser:l}),f=e(),p=[];for(const o of m){const e=r(o.path,"Login Data");try{if(!a(e,"logins"))continue;const r=n(e,"SELECT origin_url, username_value, password_value, date_last_used\n FROM logins ORDER BY origin_url");for(const e of r){const r={profile:o.name,url:e.origin_url,username:e.username_value,password:s(e.password_value,f),lastUsed:t(e.date_last_used||0)};if(c){const o=c.toLowerCase();if(!(r.url&&r.url.toLowerCase().includes(o)||r.username&&r.username.toLowerCase().includes(o)))continue}p.push(r)}}catch(r){p.push({profile:o.name,error:r.message})}}return p.filter(r=>!r.error)}
@@ -1,6 +1,7 @@
1
1
  export interface SearchEngineOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  }
5
6
  export interface SearchEngineEntry {
6
7
  profile: string;
@@ -19,4 +20,4 @@ export interface SearchEngineEntry {
19
20
  * @param options - Targeting options.
20
21
  * @returns Array of search engine entries.
21
22
  */
22
- export declare function getSearchEngines({ chromeDir, profile }?: SearchEngineOptions): SearchEngineEntry[];
23
+ export declare function getSearchEngines({ chromeDir, profile, browser }?: SearchEngineOptions): SearchEngineEntry[];
@@ -1 +1 @@
1
- import{join as o}from"node:path";import{resolveTargetProfiles as r}from"../core/chrome.js";import{queryDatabase as e,tableExists as i}from"../core/db.js";export function getSearchEngines({chromeDir:t,profile:s}={}){const n=r({chromeDir:t,profile:s}),c=[];for(const r of n){const t=o(r.path,"Web Data");try{if(!i(t,"keywords"))continue;const o=e(t,"SELECT id, short_name, keyword, url, suggestions_url,\n favicon_url, prepopulate_id, is_active\n FROM keywords ORDER BY short_name");for(const e of o)c.push({profile:r.name,id:e.id,name:e.short_name||"",keyword:e.keyword||"",url:e.url||"",suggestionsUrl:e.suggestions_url||"",faviconUrl:e.favicon_url||"",isDefault:1===e.prepopulate_id,isActive:1===e.is_active})}catch{}}return c}
1
+ import{join as r}from"node:path";import{resolveTargetProfiles as o}from"../core/chrome.js";import{queryDatabase as e,tableExists as i}from"../core/db.js";export function getSearchEngines({chromeDir:t,profile:s,browser:n}={}){const c=o({chromeDir:t,profile:s,browser:n}),a=[];for(const o of c){const t=r(o.path,"Web Data");try{if(!i(t,"keywords"))continue;const r=e(t,"SELECT id, short_name, keyword, url, suggestions_url,\n favicon_url, prepopulate_id, is_active\n FROM keywords ORDER BY short_name");for(const e of r)a.push({profile:o.name,id:e.id,name:e.short_name||"",keyword:e.keyword||"",url:e.url||"",suggestionsUrl:e.suggestions_url||"",faviconUrl:e.favicon_url||"",isDefault:1===e.prepopulate_id,isActive:1===e.is_active})}catch{}}return a}
@@ -1,6 +1,7 @@
1
1
  export interface TopSiteOptions {
2
2
  chromeDir?: string;
3
3
  profile?: string;
4
+ browser?: string;
4
5
  }
5
6
  export interface TopSiteEntry {
6
7
  profile: string;
@@ -15,4 +16,4 @@ export interface TopSiteEntry {
15
16
  * @param options - Targeting options.
16
17
  * @returns Array of top site entries.
17
18
  */
18
- export declare function getTopSites({ chromeDir, profile }?: TopSiteOptions): TopSiteEntry[];
19
+ export declare function getTopSites({ chromeDir, profile, browser }?: TopSiteOptions): TopSiteEntry[];
@@ -1 +1 @@
1
- import{join as r}from"node:path";import{resolveTargetProfiles as t}from"../core/chrome.js";import{queryDatabase as o,tableExists as e}from"../core/db.js";export function getTopSites({chromeDir:i,profile:c}={}){const n=t({chromeDir:i,profile:c}),s=[];for(const t of n){const i=r(t.path,"Top Sites");try{if(!e(i,"top_sites"))continue;const r=o(i,"SELECT url, url_rank, title, redirects\n FROM top_sites ORDER BY url_rank ASC");for(const o of r)s.push({profile:t.name,url:o.url||"",title:o.title||"",rank:o.url_rank??0,redirects:o.redirects||""})}catch{}}return s}
1
+ import{join as r}from"node:path";import{resolveTargetProfiles as o}from"../core/chrome.js";import{queryDatabase as t,tableExists as e}from"../core/db.js";export function getTopSites({chromeDir:i,profile:s,browser:c}={}){const n=o({chromeDir:i,profile:s,browser:c}),p=[];for(const o of n){const i=r(o.path,"Top Sites");try{if(!e(i,"top_sites"))continue;const r=t(i,"SELECT url, url_rank, title, redirects\n FROM top_sites ORDER BY url_rank ASC");for(const t of r)p.push({profile:o.name,url:t.url||"",title:t.title||"",rank:t.url_rank??0,redirects:t.redirects||""})}catch{}}return p}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-tools",
3
- "version": "1.0.9",
3
+ "version": "1.1.2",
4
4
  "description": "CLI & Node.js library for chrome",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -39,7 +39,11 @@
39
39
  "autofill",
40
40
  "browser",
41
41
  "macos",
42
+ "windows",
43
+ "linux",
44
+ "cross-platform",
42
45
  "keychain",
46
+ "dpapi",
43
47
  "cli"
44
48
  ],
45
49
  "author": "",