chrome-tools 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 r}from"node:path";import{Command as t}from"commander";import i from"chalk";import s 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{getLocalStorage as b}from"../src/modules/local-storage.js";import{getAutofillEntries as y,getCreditCards as S,getAddresses as v}from"../src/modules/autofill.js";import{getDownloads as k}from"../src/modules/downloads.js";import{getExtensions as L}from"../src/modules/extensions.js";import{getSearchEngines as P}from"../src/modules/search-engines.js";import{getTopSites as D}from"../src/modules/top-sites.js";const j=function(){const t={...process.env},i=r(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 r=e.indexOf("=");if(-1!==r){const o=e.substring(0,r).trim();let i=e.substring(r+1).trim();(i.startsWith('"')&&i.endsWith('"')||i.startsWith("'")&&i.endsWith("'"))&&(i=i.slice(1,-1)),t[o]=i}}}catch{}return t}(),B=j.PROFILE_PATH||void 0,C=j.PROFILE_NAME||void 0,N=j.BROWSER||j.CHROME_BROWSER||void 0;function R(o){console.log(JSON.stringify(o,null,2))}function U(o,e){return new s({head:o.map(o=>i.bold.white(o)),colWidths:e,wordWrap:!0,style:{border:["grey"],head:[]}})}function O(o,e){0===e?console.log(i.yellow(`\n⚠ No ${o} found.\n`)):console.log(i.green(`\n✔ Found ${e} ${o}.\n`))}let $=null;function E(o,e){return $?($.push({browser:p().name,results:e}),!0):!!o.json&&(R(e),!0)}function x(o){const e=o.optsWithGlobals(),r=Boolean(e.browser)||Boolean(e.allBrowsers)||Boolean(N);return e.browser=e.browser||N||void 0,e.profilePath||(e.profilePath=r?void 0:B),e.profile||(e.profile=r?void 0:C),e}function F(o,e){e.allBrowsers?function(o,e){const r=d();if(0===r.length)return e.json?R([]):void console.log(i.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)}})}}R($)}finally{$=null}}else for(const e of r){m(e.browser.id),console.log(i.bold.magenta(`\n══════════ ${e.browser.name} ══════════`));try{o(t)}catch(o){console.log(i.red(` ⚠ Skipped: ${o?.message??String(o)}`))}}}(o,e):(m(e.browser),o(e))}function W(o){const e=d({includeMissing:!0});if(o.json)return R(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(i.bold(`\n🌐 Supported browsers (${r.length} installed):\n`));const t=U(["Browser","Id","Installed","Profiles","Path"],[20,14,11,9,50]);for(const o of e)t.push([i.white(o.browser.name),i.cyan(o.browser.id),o.installed?i.green("✓"):i.dim("✗"),o.installed?i.yellow(String(o.profileCount)):i.dim("—"),i.dim(o.chromeDir)]);console.log(t.toString())}function I(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,z=JSON.parse(o(new URL("../../package.json",import.meta.url),"utf8"));A.name("chrome-tools").description(i.bold.cyan("Chrome Data Toolkit")+i.dim(" — Extract passwords, bookmarks, history, cookies, and more from Chrome on macOS, Windows, and Linux")).version(z.version).option("--passwords","Shortcut: list saved passwords").option("--bookmarks","Shortcut: list bookmarks").option("--local-storage","Shortcut: list localStorage entries").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=x(e);r.browsers?W(r):r.passwords?F(G,r):r.bookmarks?F(K,r):r.localStorage?F(_,r):F(J,r)});const V=A.command("profiles").description("List Chrome profiles");I(V).option("--size","Compute and show each profile's size on disk (slower)").action((o,e)=>{F(J,x(e))});I(V.command("states").description("Show detailed profile states with available data files")).action((o,e)=>F(H,x(e)));function J(o){const e=n(o.profilePath),r=l(o.profilePath),t=Boolean(o.size);if(o.json){const i=new Map(r.map(o=>[o.directory,o]));return void E(o,e.map(o=>({...o,...i.get(o.name)||{},...t?{sizeBytes:c(o.path)}:{}})))}if(0===e.length)return void console.log(i.yellow("\n⚠ No Chrome profiles found.\n"));const s=new Map(r.map(o=>[o.directory,o]));console.log(i.bold(`\n📁 Discovered ${e.length} profile(s):\n`));const a=U(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=s.get(o.name)||{},r=[i.cyan(o.name),i.white(e.name||o.name),i.dim(e.email||"—")];t&&r.push(i.yellow(Y(c(o.path)))),r.push(i.dim(o.path)),a.push(r)}console.log(a.toString()),O("profile(s)",e.length)}function H(o){const e=a(o.profilePath);if(o.json)E(o,e);else if(0!==e.length){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,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=>i.green(o)).join(", ")||i.dim("none")}`),t.length>0&&console.log(` Missing: ${t.map(o=>i.dim(o)).join(", ")}`),console.log()}}else console.log(i.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)=>W(x(e)));function G(o){const e=f({chromeDir:o.profilePath,profile:o.profile,search:o.search});if(E(o,e))return;if(0===e.length)return O("passwords",0);console.log(i.bold("\n🔑 Saved Passwords:\n"));const r=new Set(e.map(o=>o.profile)).size>1,t=U(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=[i.cyan(o.url),i.green(o.username||"<empty>"),i.yellow(o.password||"<empty>"),i.dim(o.lastUsed)];r&&e.unshift(i.magenta(o.profile)),t.push(e)}console.log(t.toString()),O("password(s)",e.length)}M(I(A.command("passwords").description("List saved passwords"))).action((o,e)=>F(G,x(e)));function K(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 O("bookmarks",0);console.log(i.bold("\n📚 Bookmarks:\n"));const r=U(["Title","URL","Folder","Date Added"],[30,50,25,20]);for(const o of e)r.push([i.white(o.title),i.cyan(o.url),i.dim(o.folder||"/"),i.dim(o.dateAdded)]);console.log(r.toString()),O("bookmark(s)",e.length)}M(I(A.command("bookmarks").description("List bookmarks"))).option("--folder <name>","Filter by folder name").action((o,e)=>F(K,x(e)));M(T(I(A.command("history").description("List browsing history")))).action((o,e)=>F(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 O("history entries",0);console.log(i.bold("\n🕐 Browsing History:\n"));const r=U(["URL","Title","Visits","Last Visit"],[50,30,8,22]);for(const o of e)r.push([i.cyan(o.url),i.white(o.title||"—"),i.yellow(String(o.visitCount)),i.dim(o.lastVisit)]);console.log(r.toString()),O("history entry(ies)",e.length)},x(e)));T(I(A.command("search-terms").description("List search terms from history"))).action((o,e)=>F(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 O("search terms",0);console.log(i.bold("\n🔍 Search Terms:\n"));const r=U(["Term","URL"],[40,80]);for(const o of e)r.push([i.white(o.term),i.dim(o.url)]);console.log(r.toString()),O("search term(s)",e.length)},x(e)));T(I(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)=>F(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 O("cookies",0);console.log(i.bold("\n🍪 Cookies:\n"));const r=U(["Domain","Name","Value","Secure","Expires"],[30,25,30,8,22]);for(const o of e)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()),O("cookie(s)",e.length)},x(e)));function _(o){const e=b({chromeDir:o.profilePath,profile:o.profile,origin:o.origin,url:o.url,key:o.key,search:o.search,limit:parseInt(o.limit,10)||100});if(E(o,e))return;if(0===e.length)return O("localStorage entries",0);console.log(i.bold("\n💾 localStorage:\n"));const r=U(["Origin","Key","Value"],[36,28,46]);for(const o of e)r.push([i.cyan(o.origin),i.white(o.key),i.dim(o.value?o.value.substring(0,80)+(o.value.length>80?"…":""):"—")]);console.log(r.toString()),O("localStorage entries",e.length)}M(T(I(A.command("local-storage").description("List localStorage entries")))).option("--origin <origin>","Filter by origin").option("--url <url>","Filter by URL origin").option("--key <key>","Filter by localStorage key").action((o,e)=>F(_,x(e)));M(T(I(A.command("autofill").description("List autofill entries")))).action((o,e)=>F(o=>{const e=y({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 O("autofill entries",0);console.log(i.bold("\n📝 Autofill Entries:\n"));const r=U(["Field Name","Value","Count","Last Used"],[25,40,8,22]);for(const o of e)r.push([i.cyan(o.name),i.white(o.value),i.yellow(String(o.count)),i.dim(o.dateLastUsed)]);console.log(r.toString()),O("autofill entry(ies)",e.length)},x(e)));I(A.command("credit-cards").description("List saved credit cards")).action((o,e)=>F(o=>{const e=S({chromeDir:o.profilePath,profile:o.profile});if(E(o,e))return;if(0===e.length)return O("credit cards",0);console.log(i.bold("\n💳 Saved Credit Cards:\n"));const r=U(["Name on Card","Card Number","Expires","Nickname"],[25,25,14,20]);for(const o of e)r.push([i.white(o.nameOnCard||"—"),i.yellow(o.cardNumber||"—"),i.dim(`${o.expirationMonth}/${o.expirationYear}`),i.dim(o.nickname||"—")]);console.log(r.toString()),O("credit card(s)",e.length)},x(e)));I(A.command("addresses").description("List saved addresses")).action((o,e)=>F(o=>{const e=v({chromeDir:o.profilePath,profile:o.profile});if(E(o,e))return;if(0===e.length)return O("addresses",0);console.log(i.bold("\n🏠 Saved Addresses:\n"));const r=U(["Name","Email","Phone","Address","City/State/Zip"],[20,25,16,30,25]);for(const o of e)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()),O("address(es)",e.length)},x(e)));M(T(I(A.command("downloads").description("List download history")))).action((o,e)=>F(o=>{const e=k({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 O("downloads",0);console.log(i.bold("\n⬇️ Download History:\n"));const r=U(["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?Y(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()),O("download(s)",e.length)},x(e)));M(I(A.command("extensions").description("List installed extensions"))).action((o,e)=>F(o=>{const e=L({chromeDir:o.profilePath,profile:o.profile,search:o.search});if(E(o,e))return;if(0===e.length)return O("extensions",0);console.log(i.bold("\n🧩 Installed Extensions:\n"));const r=U(["Name","Version","ID","Enabled","Webstore"],[30,12,36,9,9]);for(const o of e)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()),O("extension(s)",e.length)},x(e)));I(A.command("search-engines").description("List configured search engines")).action((o,e)=>F(o=>{const e=P({chromeDir:o.profilePath,profile:o.profile});if(E(o,e))return;if(0===e.length)return O("search engines",0);console.log(i.bold("\n🔎 Search Engines:\n"));const r=U(["Name","Keyword","URL","Active"],[20,16,60,8]);for(const o of e)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()),O("search engine(s)",e.length)},x(e)));function Y(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]}`}I(A.command("top-sites").description("List top sites")).action((o,e)=>F(o=>{const e=D({chromeDir:o.profilePath,profile:o.profile});if(E(o,e))return;if(0===e.length)return O("top sites",0);console.log(i.bold("\n⭐ Top Sites:\n"));const r=U(["Rank","Title","URL"],[8,30,80]);for(const o of e)r.push([i.yellow(String(o.rank+1)),i.white(o.title||"—"),i.cyan(o.url)]);console.log(r.toString()),O("top site(s)",e.length)},x(e)));try{A.parse()}catch(o){console.error(i.red(`\n✖ ${o?.message??String(o)}\n`)),process.env.DEBUG&&console.error(o),process.exit(1)}
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,isKeychainAccessDenied as f}from"../src/core/platform/index.js";import{getPasswords as h}from"../src/modules/passwords.js";import{getBookmarks as u}from"../src/modules/bookmarks.js";import{getHistory as g,getSearchTerms as w}from"../src/modules/history.js";import{getCookies as b}from"../src/modules/cookies.js";import{getLocalStorage as y}from"../src/modules/local-storage.js";import{getAutofillEntries as S,getCreditCards as v,getAddresses as k}from"../src/modules/autofill.js";import{getDownloads as L}from"../src/modules/downloads.js";import{getExtensions as P}from"../src/modules/extensions.js";import{getSearchEngines as D}from"../src/modules/search-engines.js";import{getTopSites as j}from"../src/modules/top-sites.js";const N=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}(),B=N.PROFILE_PATH||void 0,C=N.PROFILE_NAME||void 0,R=N.BROWSER||N.CHROME_BROWSER||void 0;function U(o){console.log(JSON.stringify(o,null,2))}function $(o,e){return new i({head:o.map(o=>s.bold.white(o)),colWidths:e,wordWrap:!0,style:{border:["grey"],head:[]}})}function O(o,e){0===e?console.log(s.yellow(`\n⚠ No ${o} found.\n`)):console.log(s.green(`\n✔ Found ${e} ${o}.\n`))}let E=null;function x(o,e){return E?(E.push({browser:p().name,results:e}),!0):!!o.json&&(U(e),!0)}function F(o){const e=o.optsWithGlobals(),r=Boolean(e.browser)||Boolean(e.allBrowsers)||Boolean(R);return e.browser=e.browser||R||void 0,e.profilePath||(e.profilePath=r?void 0:B),e.profile||(e.profile=r?void 0:C),e}function W(o,e){e.allBrowsers?function(o,e){const r=d();if(0===r.length)return e.json?U([]):void console.log(s.yellow("\n⚠ No supported Chromium browsers found on this machine.\n"));const t={...e,profilePath:void 0};if(e.json){E=[];try{for(const e of r){m(e.browser.id);try{o(t)}catch(o){E.push({browser:e.browser.name,results:{error:o?.message??String(o)}})}}U(E)}finally{E=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){f(o)?(console.log(s.red(` 🔑 No keychain access — skipped (${o.service}).`)),console.log(s.dim(` ${o.message.split("\n").join("\n ")}`))):console.log(s.red(` ⚠ Skipped: ${o?.message??String(o)}`))}}}(o,e):(m(e.browser),o(e))}function I(o){const e=d({includeMissing:!0});if(o.json)return U(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=$(["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 M(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 T(o){return o.option("--search <term>","Filter results (case-insensitive)")}function A(o,e=100){return o.option("--limit <n>","Limit number of results",String(e))}const z=new t,V=JSON.parse(o(new URL("../../package.json",import.meta.url),"utf8"));z.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(V.version).option("--passwords","Shortcut: list saved passwords").option("--bookmarks","Shortcut: list bookmarks").option("--local-storage","Shortcut: list localStorage entries").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=F(e);r.browsers?I(r):r.passwords?W(G,r):r.bookmarks?W(_,r):r.localStorage?W(Y,r):W(H,r)});const J=z.command("profiles").description("List Chrome profiles");M(J).option("--size","Compute and show each profile's size on disk (slower)").action((o,e)=>{W(H,F(e))});M(J.command("states").description("Show detailed profile states with available data files")).action((o,e)=>W(K,F(e)));function H(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 x(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=$(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(Z(c(o.path)))),r.push(s.dim(o.path)),a.push(r)}console.log(a.toString()),O("profile(s)",e.length)}function K(o){const e=a(o.profilePath);if(o.json)x(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"))}z.command("browsers").description("List supported browsers and detect which are installed").option("--json","Output as JSON").action((o,e)=>I(F(e)));function G(o){const e=h({chromeDir:o.profilePath,profile:o.profile,search:o.search});if(x(o,e))return;if(0===e.length)return O("passwords",0);console.log(s.bold("\n🔑 Saved Passwords:\n"));const r=new Set(e.map(o=>o.profile)).size>1,t=$(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()),O("password(s)",e.length)}T(M(z.command("passwords").description("List saved passwords"))).action((o,e)=>W(G,F(e)));function _(o){const e=u({chromeDir:o.profilePath,profile:o.profile,folder:o.folder,search:o.search});if(x(o,e))return;if(0===e.length)return O("bookmarks",0);console.log(s.bold("\n📚 Bookmarks:\n"));const 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()),O("bookmark(s)",e.length)}T(M(z.command("bookmarks").description("List bookmarks"))).option("--folder <name>","Filter by folder name").action((o,e)=>W(_,F(e)));T(A(M(z.command("history").description("List browsing history")))).action((o,e)=>W(o=>{const e=g({chromeDir:o.profilePath,profile:o.profile,search:o.search,limit:parseInt(o.limit,10)||100});if(x(o,e))return;if(0===e.length)return O("history entries",0);console.log(s.bold("\n🕐 Browsing History:\n"));const 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()),O("history entry(ies)",e.length)},F(e)));A(M(z.command("search-terms").description("List search terms from history"))).action((o,e)=>W(o=>{const e=w({chromeDir:o.profilePath,profile:o.profile,limit:parseInt(o.limit,10)||100});if(x(o,e))return;if(0===e.length)return O("search terms",0);console.log(s.bold("\n🔍 Search Terms:\n"));const r=$(["Term","URL"],[40,80]);for(const o of e)r.push([s.white(o.term),s.dim(o.url)]);console.log(r.toString()),O("search term(s)",e.length)},F(e)));A(M(z.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)=>W(o=>{const e=b({chromeDir:o.profilePath,profile:o.profile,domain:o.domain,url:o.url,decrypt:!1!==o.decrypt,limit:parseInt(o.limit,10)||100});if(x(o,e))return;if(0===e.length)return O("cookies",0);console.log(s.bold("\n🍪 Cookies:\n"));const 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()),O("cookie(s)",e.length)},F(e)));function Y(o){const e=y({chromeDir:o.profilePath,profile:o.profile,origin:o.origin,url:o.url,key:o.key,search:o.search,limit:parseInt(o.limit,10)||100});if(x(o,e))return;if(0===e.length)return O("localStorage entries",0);console.log(s.bold("\n💾 localStorage:\n"));const r=$(["Origin","Key","Value"],[36,28,46]);for(const o of e)r.push([s.cyan(o.origin),s.white(o.key),s.dim(o.value?o.value.substring(0,80)+(o.value.length>80?"…":""):"—")]);console.log(r.toString()),O("localStorage entries",e.length)}T(A(M(z.command("local-storage").description("List localStorage entries")))).option("--origin <origin>","Filter by origin").option("--url <url>","Filter by URL origin").option("--key <key>","Filter by localStorage key").action((o,e)=>W(Y,F(e)));T(A(M(z.command("autofill").description("List autofill entries")))).action((o,e)=>W(o=>{const e=S({chromeDir:o.profilePath,profile:o.profile,search:o.search,limit:parseInt(o.limit,10)||100});if(x(o,e))return;if(0===e.length)return O("autofill entries",0);console.log(s.bold("\n📝 Autofill Entries:\n"));const 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()),O("autofill entry(ies)",e.length)},F(e)));M(z.command("credit-cards").description("List saved credit cards")).action((o,e)=>W(o=>{const e=v({chromeDir:o.profilePath,profile:o.profile});if(x(o,e))return;if(0===e.length)return O("credit cards",0);console.log(s.bold("\n💳 Saved Credit Cards:\n"));const 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()),O("credit card(s)",e.length)},F(e)));M(z.command("addresses").description("List saved addresses")).action((o,e)=>W(o=>{const e=k({chromeDir:o.profilePath,profile:o.profile});if(x(o,e))return;if(0===e.length)return O("addresses",0);console.log(s.bold("\n🏠 Saved Addresses:\n"));const 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()),O("address(es)",e.length)},F(e)));T(A(M(z.command("downloads").description("List download history")))).action((o,e)=>W(o=>{const e=L({chromeDir:o.profilePath,profile:o.profile,search:o.search,limit:parseInt(o.limit,10)||100});if(x(o,e))return;if(0===e.length)return O("downloads",0);console.log(s.bold("\n⬇️ Download History:\n"));const 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?Z(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()),O("download(s)",e.length)},F(e)));T(M(z.command("extensions").description("List installed extensions"))).action((o,e)=>W(o=>{const e=P({chromeDir:o.profilePath,profile:o.profile,search:o.search});if(x(o,e))return;if(0===e.length)return O("extensions",0);console.log(s.bold("\n🧩 Installed Extensions:\n"));const 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()),O("extension(s)",e.length)},F(e)));M(z.command("search-engines").description("List configured search engines")).action((o,e)=>W(o=>{const e=D({chromeDir:o.profilePath,profile:o.profile});if(x(o,e))return;if(0===e.length)return O("search engines",0);console.log(s.bold("\n🔎 Search Engines:\n"));const 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()),O("search engine(s)",e.length)},F(e)));function Z(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]}`}M(z.command("top-sites").description("List top sites")).action((o,e)=>W(o=>{const e=j({chromeDir:o.profilePath,profile:o.profile});if(x(o,e))return;if(0===e.length)return O("top sites",0);console.log(s.bold("\n⭐ Top Sites:\n"));const 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()),O("top site(s)",e.length)},F(e)));try{z.parse()}catch(o){(function(o){return!!f(o)&&(console.error(s.red.bold("\n🔑 No keychain access\n")),console.error(s.yellow("chrome-tools couldn't read the browser's encryption key from your macOS\nKeychain, so passwords and other encrypted data can't be decrypted.")),console.error(s.dim("\n"+o.message+"\n")),!0)})(o)||console.error(s.red(`\n✖ ${o?.message??String(o)}\n`)),process.env.DEBUG&&console.error(o),process.exit(1)}
@@ -1,3 +1,22 @@
1
+ /** How a Keychain read failed, so callers (and the CLI) can react specifically. */
2
+ export type KeychainErrorKind = "access-denied" | "not-found" | "empty" | "unknown";
3
+ /**
4
+ * Error thrown when a browser's Safe Storage key cannot be read from the
5
+ * Keychain. Carries the failing `service` and a `kind` so the CLI can show a
6
+ * clear "No keychain access" message instead of a generic failure.
7
+ */
8
+ export declare class KeychainAccessError extends Error {
9
+ readonly service: string;
10
+ readonly kind: KeychainErrorKind;
11
+ constructor(message: string, service: string, kind: KeychainErrorKind);
12
+ /** True when the failure means we couldn't get permission to read the key. */
13
+ get isAccessDenied(): boolean;
14
+ }
15
+ /**
16
+ * Narrows an unknown error to a {@link KeychainAccessError} caused by missing
17
+ * Keychain permission (denied/dismissed prompt, locked, or non-interactive).
18
+ */
19
+ export declare function isKeychainAccessDenied(err: unknown): err is KeychainAccessError;
1
20
  /**
2
21
  * Retrieves a browser's "Safe Storage" password from the macOS Keychain.
3
22
  * Caches the result per service for subsequent calls within the same process.
@@ -1 +1 @@
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()}
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",s=44,o=36,i=45;export class KeychainAccessError extends Error{service;kind;constructor(e,n,t){super(e),this.name="KeychainAccessError",this.service=n,this.kind=t}get isAccessDenied(){return"access-denied"===this.kind}}export function isKeychainAccessDenied(e){return e instanceof KeychainAccessError&&e.isAccessDenied}const c=new Map,a=new Map;function h(e,n,t){const r="See docs/remote-keychain-access.md for non-interactive / SSH setup.";let c,a;n===s||/could not be found/i.test(t)?(c="not-found",a=`No '${e}' item found in the Keychain.\nRun this browser at least once on this machine so it creates the key.`):n===o||/interaction.*not allowed/i.test(t)?(c="access-denied",a="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===i?(c="access-denied",a=`Keychain authorization failed (wrong password or denied).\nTry: security unlock-keychain ~/Library/Keychains/login.keychain-db\n${r}`):(c="access-denied",a=`macOS showed a Keychain access prompt that was denied or dismissed.\nRe-run the command and click "Allow" (or "Always Allow"), or authorize once with:\n security find-generic-password -w -s "${e}"\n${r}`);const h=[null!==n?`exit ${n}`:null,t||null].filter(Boolean).join(": ");return new KeychainAccessError(`Could not retrieve ${e} key from macOS Keychain.\n${a}`+(h?`\n(security: ${h})`:""),e,c)}export function getKeychainPassword(n=r){const t=c.get(n);if(void 0!==t)return t;let s;try{s=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(!s)throw new KeychainAccessError(`Keychain returned an empty ${n} key.\nThe item may exist with no password. Re-run the browser to recreate it.`,n,"empty");return c.set(n,s),s}export function getChromeKeychainPassword(){return getKeychainPassword(r)}export function deriveEncryptionKey(e=r,s){const o=a.get(e);if(void 0!==o)return o;const i=s||getKeychainPassword(e);if(!i)throw new Error("Keychain password cannot be empty for key derivation.");const c=n(i,t,1003,16,"sha1");return a.set(e,c),c}export function clearKeychainCache(){c.clear(),a.clear()}
@@ -8,6 +8,7 @@
8
8
  import type { ChromePlatform } from "./types.js";
9
9
  export type { ChromePlatform } from "./types.js";
10
10
  export { BROWSERS, listBrowsers, resolveBrowser, getActiveBrowser, setActiveBrowser, type BrowserId, type BrowserDescriptor, } from "../browsers.js";
11
+ export { KeychainAccessError, isKeychainAccessDenied, type KeychainErrorKind, } from "../keychain.js";
11
12
  /**
12
13
  * Returns the backend for the current OS, cached after first call.
13
14
  * @throws On an unsupported platform.
@@ -1 +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()}
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";export{KeychainAccessError,isKeychainAccessDenied}from"../keychain.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()}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-tools",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "CLI & Node.js library for chrome",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",