tokentracker-cli 0.23.2 → 0.24.0

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 (37) hide show
  1. package/dashboard/dist/assets/{Card-WqeqJCTJ.js → Card-C22jvaH-.js} +1 -1
  2. package/dashboard/dist/assets/{DashboardPage-DfXV-T4E.js → DashboardPage-Dv_4x35m.js} +1 -1
  3. package/dashboard/dist/assets/{DevicePage-CKQwUIFV.js → DevicePage-BjhiTjFW.js} +1 -1
  4. package/dashboard/dist/assets/{FadeIn-BvXHu5jo.js → FadeIn-CHxDANbJ.js} +1 -1
  5. package/dashboard/dist/assets/{HeaderGithubStar-BYgV73_y.js → HeaderGithubStar-CIfUeDxZ.js} +1 -1
  6. package/dashboard/dist/assets/IpCheckPage-DIa0AzUn.js +15 -0
  7. package/dashboard/dist/assets/{LandingPage-Dymt6fsi.js → LandingPage-sh7tcPKd.js} +1 -1
  8. package/dashboard/dist/assets/{LeaderboardPage-BWJX1oGI.js → LeaderboardPage-Dg6zcGG1.js} +1 -1
  9. package/dashboard/dist/assets/{LeaderboardProfilePage-DmNl8aoL.js → LeaderboardProfilePage-BOJJ-T57.js} +1 -1
  10. package/dashboard/dist/assets/{LimitsPage-D_q9Z5nB.js → LimitsPage-CdMhF5lF.js} +1 -1
  11. package/dashboard/dist/assets/{LoginPage-BvwX7KJg.js → LoginPage-BbJO2VUw.js} +1 -1
  12. package/dashboard/dist/assets/{PopoverPopup-CrBQ_8vY.js → PopoverPopup-Csxg6Urf.js} +1 -1
  13. package/dashboard/dist/assets/{ProviderIcon-Cb-YMfcK.js → ProviderIcon-BdfV-wMB.js} +1 -1
  14. package/dashboard/dist/assets/{SettingsPage-u30V0Mqy.js → SettingsPage-B0UsE-Cz.js} +1 -1
  15. package/dashboard/dist/assets/{SkillsPage-ssktZGFE.js → SkillsPage-CpV5GRRc.js} +1 -1
  16. package/dashboard/dist/assets/{WidgetsPage-_geExCIR.js → WidgetsPage-DdQEZR9N.js} +1 -1
  17. package/dashboard/dist/assets/{WrappedPage-C5Wcs8L4.js → WrappedPage-DKYCqqJa.js} +1 -1
  18. package/dashboard/dist/assets/check-CO0YnJo0.js +1 -0
  19. package/dashboard/dist/assets/{chevron-down-BbPjwLP4.js → chevron-down-Dh0EAuAs.js} +1 -1
  20. package/dashboard/dist/assets/{download-iIollDJi.js → download-B-u2224w.js} +1 -1
  21. package/dashboard/dist/assets/{info-DjS23Duh.js → info-Bax52N3e.js} +1 -1
  22. package/dashboard/dist/assets/{leaderboard-columns-DTnRH3EZ.js → leaderboard-columns-CmMWLqut.js} +1 -1
  23. package/dashboard/dist/assets/main-JP_EYeq-.css +1 -0
  24. package/dashboard/dist/assets/{main-Cb6vMAZa.js → main-uaLO1Ae7.js} +141 -16
  25. package/dashboard/dist/assets/{use-limits-display-prefs-DZ9hgvef.js → use-limits-display-prefs-BVawA_Rp.js} +1 -1
  26. package/dashboard/dist/assets/{use-native-settings-DS1TfSil.js → use-native-settings-DHtj5DnD.js} +1 -1
  27. package/dashboard/dist/assets/{use-reduced-motion-D82galUK.js → use-reduced-motion-8tbVQBYw.js} +1 -1
  28. package/dashboard/dist/assets/{use-usage-limits-B-svr8YY.js → use-usage-limits-SwQjCSa8.js} +1 -1
  29. package/dashboard/dist/assets/{useCurrency-uA9QPua4.js → useCurrency-Bc8I7sE1.js} +1 -1
  30. package/dashboard/dist/index.html +2 -2
  31. package/dashboard/dist/share.html +2 -2
  32. package/package.json +1 -1
  33. package/src/lib/local-api.js +41 -42
  34. package/src/lib/pricing/seed-snapshot.json +1 -1
  35. package/dashboard/dist/assets/IpCheckPage-BjuJDGa8.js +0 -1
  36. package/dashboard/dist/assets/check-Cu-lm-wz.js +0 -1
  37. package/dashboard/dist/assets/main-0bSXJNLn.css +0 -1
@@ -1 +1 @@
1
- import{r as a}from"./main-Cb6vMAZa.js";const d=["claude","codex","cursor","gemini","kimi","kiro","copilot","antigravity"],S={claude:"Claude",codex:"Codex",cursor:"Cursor",gemini:"Gemini",kimi:"Kimi",kiro:"Kiro",copilot:"GitHub Copilot",antigravity:"Antigravity"},v={claude:"/brand-logos/claude-code.svg",codex:"/brand-logos/codex.svg",cursor:"/brand-logos/cursor.svg",gemini:"/brand-logos/gemini.svg",kimi:"/brand-logos/kimi.svg",kiro:"/brand-logos/kiro.svg",copilot:"/brand-logos/copilot.svg",antigravity:"/brand-logos/antigravity.svg"},l="tt.limits.providerOrder",g="tt.limits.providerVisibility";function w(){if(typeof window>"u")return[...d];try{const i=window.localStorage.getItem(l);if(!i)return[...d];const s=JSON.parse(i);if(!Array.isArray(s))return[...d];const r=s.filter(c=>d.includes(c));for(const c of d)r.includes(c)||r.push(c);return r}catch{return[...d]}}function y(){const i=Object.fromEntries(d.map(s=>[s,!0]));if(typeof window>"u")return i;try{const s=window.localStorage.getItem(g);if(!s)return i;const r=JSON.parse(s);if(!r||typeof r!="object")return i;const c={...i};for(const u of d)typeof r[u]=="boolean"&&(c[u]=r[u]);return c}catch{return i}}function C(){const[i,s]=a.useState(w),[r,c]=a.useState(y);a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(l,JSON.stringify(i))}catch{}},[i]),a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(g,JSON.stringify(r))}catch{}},[r]),a.useEffect(()=>{if(typeof window>"u")return;const o=t=>{t.key===l&&s(w()),t.key===g&&c(y())};return window.addEventListener("storage",o),()=>window.removeEventListener("storage",o)},[]);const u=a.useCallback(o=>{c(t=>({...t,[o]:!t[o]}))},[]),b=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<=0)return t;const n=[...t];return[n[e-1],n[e]]=[n[e],n[e-1]],n})},[]),p=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<0||e>=t.length-1)return t;const n=[...t];return[n[e],n[e+1]]=[n[e+1],n[e]],n})},[]),O=a.useCallback((o,t)=>{o!==t&&s(e=>{const n=e.indexOf(o),m=e.indexOf(t);if(n<0||m<0)return e;const f=[...e],[I]=f.splice(n,1);return f.splice(m,0,I),f})},[]),k=a.useCallback(()=>{s([...d]),c(Object.fromEntries(d.map(o=>[o,!0])))},[]),x=a.useMemo(()=>i.filter(o=>r[o]!==!1),[i,r]);return{order:i,visibility:r,visibleOrdered:x,toggle:u,moveUp:b,moveDown:p,moveToward:O,reset:k}}export{v as L,S as a,C as u};
1
+ import{r as a}from"./main-uaLO1Ae7.js";const d=["claude","codex","cursor","gemini","kimi","kiro","copilot","antigravity"],S={claude:"Claude",codex:"Codex",cursor:"Cursor",gemini:"Gemini",kimi:"Kimi",kiro:"Kiro",copilot:"GitHub Copilot",antigravity:"Antigravity"},v={claude:"/brand-logos/claude-code.svg",codex:"/brand-logos/codex.svg",cursor:"/brand-logos/cursor.svg",gemini:"/brand-logos/gemini.svg",kimi:"/brand-logos/kimi.svg",kiro:"/brand-logos/kiro.svg",copilot:"/brand-logos/copilot.svg",antigravity:"/brand-logos/antigravity.svg"},l="tt.limits.providerOrder",g="tt.limits.providerVisibility";function w(){if(typeof window>"u")return[...d];try{const i=window.localStorage.getItem(l);if(!i)return[...d];const s=JSON.parse(i);if(!Array.isArray(s))return[...d];const r=s.filter(c=>d.includes(c));for(const c of d)r.includes(c)||r.push(c);return r}catch{return[...d]}}function y(){const i=Object.fromEntries(d.map(s=>[s,!0]));if(typeof window>"u")return i;try{const s=window.localStorage.getItem(g);if(!s)return i;const r=JSON.parse(s);if(!r||typeof r!="object")return i;const c={...i};for(const u of d)typeof r[u]=="boolean"&&(c[u]=r[u]);return c}catch{return i}}function C(){const[i,s]=a.useState(w),[r,c]=a.useState(y);a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(l,JSON.stringify(i))}catch{}},[i]),a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(g,JSON.stringify(r))}catch{}},[r]),a.useEffect(()=>{if(typeof window>"u")return;const o=t=>{t.key===l&&s(w()),t.key===g&&c(y())};return window.addEventListener("storage",o),()=>window.removeEventListener("storage",o)},[]);const u=a.useCallback(o=>{c(t=>({...t,[o]:!t[o]}))},[]),b=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<=0)return t;const n=[...t];return[n[e-1],n[e]]=[n[e],n[e-1]],n})},[]),p=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<0||e>=t.length-1)return t;const n=[...t];return[n[e],n[e+1]]=[n[e+1],n[e]],n})},[]),O=a.useCallback((o,t)=>{o!==t&&s(e=>{const n=e.indexOf(o),m=e.indexOf(t);if(n<0||m<0)return e;const f=[...e],[I]=f.splice(n,1);return f.splice(m,0,I),f})},[]),k=a.useCallback(()=>{s([...d]),c(Object.fromEntries(d.map(o=>[o,!0])))},[]),x=a.useMemo(()=>i.filter(o=>r[o]!==!1),[i,r]);return{order:i,visibility:r,visibleOrdered:x,toggle:u,moveUp:b,moveDown:p,moveToward:O,reset:k}}export{v as L,S as a,C as u};
@@ -1 +1 @@
1
- import{C as a,a7 as x,r as n,aH as g,aI as m,aJ as b,aK as u,aL as f,aF as h}from"./main-Cb6vMAZa.js";import{C as y}from"./Card-WqeqJCTJ.js";function p({checked:s,onChange:t,disabled:e,ariaLabel:i}){return a.jsx("button",{type:"button",role:"switch","aria-checked":s,"aria-label":i,onClick:t,disabled:e,className:x("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-oai-brand-500 disabled:opacity-50 disabled:cursor-not-allowed",s?"bg-oai-brand-500":"bg-oai-gray-300 dark:bg-oai-gray-700"),children:a.jsx("span",{className:x("inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",s?"translate-x-[18px]":"translate-x-[3px]")})})}function j({label:s,hint:t,control:e}){return a.jsxs("div",{className:"flex items-center justify-between gap-4 py-3",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("div",{className:"text-sm text-oai-gray-900 dark:text-oai-gray-200",children:s}),t?a.jsx("div",{className:"mt-0.5 text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),a.jsx("div",{className:"shrink-0",children:e})]})}function N({title:s,subtitle:t,action:e,children:i}){return a.jsxs(y,{children:[a.jsxs("div",{className:"mb-3 flex items-start justify-between gap-4",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("h2",{className:"text-sm font-medium text-oai-gray-500 dark:text-oai-gray-300 uppercase tracking-wide",children:s}),t?a.jsx("p",{className:"mt-1 truncate text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),e?a.jsx("div",{className:"shrink-0",children:e}):null]}),a.jsx("div",{className:"-mb-3 divide-y divide-oai-gray-200/60 dark:divide-oai-gray-800/60",children:i})]})}function w({options:s,value:t,onChange:e}){return a.jsx("div",{className:"inline-flex items-center rounded-lg border border-oai-gray-200 bg-oai-gray-50 p-0.5 dark:border-oai-gray-800 dark:bg-oai-gray-900",children:s.map(({value:i,label:d,Icon:l})=>{const r=t===i;return a.jsxs("button",{type:"button",onClick:()=>e(i),"aria-pressed":r,className:x("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors",r?"bg-white text-oai-black shadow-sm dark:bg-oai-gray-800 dark:text-white":"text-oai-gray-500 hover:text-oai-black dark:text-oai-gray-400 dark:hover:text-white"),children:[l?a.jsx(l,{className:"h-3.5 w-3.5","aria-hidden":!0}):null,a.jsx("span",{children:d})]},i)})})}function S(){const[s,t]=n.useState(null),e=g()&&m();n.useEffect(()=>{if(!e)return;const r=b(o=>t(o));return u(),r},[e]);const i=n.useCallback((r,o)=>{e&&(t(c=>c&&{...c,[r]:o}),f(r,o))},[e]),d=n.useCallback(r=>{e&&h(r)},[e]),l=n.useCallback(()=>{e&&u()},[e]);return{available:e,settings:s,setSetting:i,runAction:d,refresh:l}}export{N as S,p as T,j as a,w as b,S as u};
1
+ import{C as a,a7 as x,r as n,aH as g,aI as m,aJ as b,aK as u,aL as f,aF as h}from"./main-uaLO1Ae7.js";import{C as y}from"./Card-C22jvaH-.js";function p({checked:s,onChange:t,disabled:e,ariaLabel:i}){return a.jsx("button",{type:"button",role:"switch","aria-checked":s,"aria-label":i,onClick:t,disabled:e,className:x("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-oai-brand-500 disabled:opacity-50 disabled:cursor-not-allowed",s?"bg-oai-brand-500":"bg-oai-gray-300 dark:bg-oai-gray-700"),children:a.jsx("span",{className:x("inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",s?"translate-x-[18px]":"translate-x-[3px]")})})}function j({label:s,hint:t,control:e}){return a.jsxs("div",{className:"flex items-center justify-between gap-4 py-3",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("div",{className:"text-sm text-oai-gray-900 dark:text-oai-gray-200",children:s}),t?a.jsx("div",{className:"mt-0.5 text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),a.jsx("div",{className:"shrink-0",children:e})]})}function N({title:s,subtitle:t,action:e,children:i}){return a.jsxs(y,{children:[a.jsxs("div",{className:"mb-3 flex items-start justify-between gap-4",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("h2",{className:"text-sm font-medium text-oai-gray-500 dark:text-oai-gray-300 uppercase tracking-wide",children:s}),t?a.jsx("p",{className:"mt-1 truncate text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),e?a.jsx("div",{className:"shrink-0",children:e}):null]}),a.jsx("div",{className:"-mb-3 divide-y divide-oai-gray-200/60 dark:divide-oai-gray-800/60",children:i})]})}function w({options:s,value:t,onChange:e}){return a.jsx("div",{className:"inline-flex items-center rounded-lg border border-oai-gray-200 bg-oai-gray-50 p-0.5 dark:border-oai-gray-800 dark:bg-oai-gray-900",children:s.map(({value:i,label:d,Icon:l})=>{const r=t===i;return a.jsxs("button",{type:"button",onClick:()=>e(i),"aria-pressed":r,className:x("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors",r?"bg-white text-oai-black shadow-sm dark:bg-oai-gray-800 dark:text-white":"text-oai-gray-500 hover:text-oai-black dark:text-oai-gray-400 dark:hover:text-white"),children:[l?a.jsx(l,{className:"h-3.5 w-3.5","aria-hidden":!0}):null,a.jsx("span",{children:d})]},i)})})}function S(){const[s,t]=n.useState(null),e=g()&&m();n.useEffect(()=>{if(!e)return;const r=b(o=>t(o));return u(),r},[e]);const i=n.useCallback((r,o)=>{e&&(t(c=>c&&{...c,[r]:o}),f(r,o))},[e]),d=n.useCallback(r=>{e&&h(r)},[e]),l=n.useCallback(()=>{e&&u()},[e]);return{available:e,settings:s,setSetting:i,runAction:d,refresh:l}}export{N as S,p as T,j as a,w as b,S as u};
@@ -1 +1 @@
1
- import{aM as t,aN as o,r,aO as s}from"./main-Cb6vMAZa.js";function u(){!t.current&&o();const[e]=r.useState(s.current);return e}export{u};
1
+ import{aM as t,aN as o,r,aO as s}from"./main-uaLO1Ae7.js";function u(){!t.current&&o();const[e]=r.useState(s.current);return e}export{u};
@@ -1 +1 @@
1
- import{r,al as l}from"./main-Cb6vMAZa.js";function y(i){const[o,a]=r.useState(null),[u,s]=r.useState(null),[c,f]=r.useState(!0),n=!!i?.initialRefresh,g=r.useCallback(async()=>{try{const e=await l({refresh:!0});a(e&&typeof e=="object"?e:null),s(null)}catch(e){s(e?.message||String(e))}},[]);return r.useEffect(()=>{let e=!1;return(async()=>{try{const t=await l(n?{refresh:!0}:{});if(e)return;a(t&&typeof t=="object"?t:null),s(null)}catch(t){if(e)return;s(t?.message||String(t))}finally{e||f(!1)}})(),()=>{e=!0}},[n]),{data:o,error:u,isLoading:c,refresh:g}}export{y as u};
1
+ import{r,al as l}from"./main-uaLO1Ae7.js";function y(i){const[o,a]=r.useState(null),[u,s]=r.useState(null),[c,f]=r.useState(!0),n=!!i?.initialRefresh,g=r.useCallback(async()=>{try{const e=await l({refresh:!0});a(e&&typeof e=="object"?e:null),s(null)}catch(e){s(e?.message||String(e))}},[]);return r.useEffect(()=>{let e=!1;return(async()=>{try{const t=await l(n?{refresh:!0}:{});if(e)return;a(t&&typeof t=="object"?t:null),s(null)}catch(t){if(e)return;s(t?.message||String(t))}finally{e||f(!1)}})(),()=>{e=!0}},[n]),{data:o,error:u,isLoading:c,refresh:g}}export{y as u};
@@ -1 +1 @@
1
- import{r as e,aA as t,aB as r,I as c}from"./main-Cb6vMAZa.js";const a=Object.freeze({currency:c,rate:1,symbol:"$",rates:{...r},rateSource:"default",rateFetchedAt:null,setCurrency:()=>{}});function u(){return e.useContext(t)??a}export{u};
1
+ import{r as e,aA as t,aB as r,I as c}from"./main-uaLO1Ae7.js";const a=Object.freeze({currency:c,rate:1,symbol:"$",rates:{...r},rateSource:"default",rateFetchedAt:null,setCurrency:()=>{}});function u(){return e.useContext(t)??a}export{u};
@@ -210,8 +210,8 @@
210
210
  ]
211
211
  }
212
212
  </script>
213
- <script type="module" crossorigin src="/assets/main-Cb6vMAZa.js"></script>
214
- <link rel="stylesheet" crossorigin href="/assets/main-0bSXJNLn.css">
213
+ <script type="module" crossorigin src="/assets/main-uaLO1Ae7.js"></script>
214
+ <link rel="stylesheet" crossorigin href="/assets/main-JP_EYeq-.css">
215
215
  </head>
216
216
  <body>
217
217
  <main class="aeo-seed-content" aria-label="Token Tracker AI-readable summary">
@@ -51,8 +51,8 @@
51
51
  "description": "Shareable Token Tracker dashboard snapshot."
52
52
  }
53
53
  </script>
54
- <script type="module" crossorigin src="/assets/main-Cb6vMAZa.js"></script>
55
- <link rel="stylesheet" crossorigin href="/assets/main-0bSXJNLn.css">
54
+ <script type="module" crossorigin src="/assets/main-uaLO1Ae7.js"></script>
55
+ <link rel="stylesheet" crossorigin href="/assets/main-JP_EYeq-.css">
56
56
  </head>
57
57
  <body>
58
58
  <main class="aeo-seed-content" aria-label="Token Tracker share page summary">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokentracker-cli",
3
- "version": "0.23.2",
3
+ "version": "0.24.0",
4
4
  "description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Every Code, Hermes, GitHub Copilot, Kimi Code, CodeBuddy, Grok Build, oh-my-pi, pi, Craft Agents, Kilo CLI, Kilo Code, Roo Code, Zed Agent, Goose)",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -673,29 +673,18 @@ function json(res, data, status) {
673
673
  }
674
674
 
675
675
  // ---------------------------------------------------------------------------
676
- // IP check proxy (issue #81): ip.net.coffee/claude/ sets X-Frame-Options:
677
- // SAMEORIGIN so the dashboard iframe is blocked. We reverse-proxy through
678
- // /proxy/ipcheck/* so requests stay same-origin to 127.0.0.1, then strip the
679
- // embedding-hostile headers and rewrite root-relative URLs in HTML so the
680
- // page's own /api/* and /claude/* fetches route back through us.
676
+ // IP check API proxy: dashboard/src/pages/IpCheckPage.jsx is a native React
677
+ // page that calls ip.net.coffee's data endpoints (/api/iprisk, /api/geoip,
678
+ // /api/dns/result, /favicons, /claude/status.json). Browser-side fetch can't
679
+ // hit them cross-origin from the dashboard, so we reverse-proxy /proxy/ipcheck/*
680
+ // to https://ip.net.coffee/* and strip embedding-hostile headers.
681
+ // (Previously this proxy also served the upstream HTML page for an iframe;
682
+ // the iframe and its HTML-rewrite path have been removed.)
681
683
  // ---------------------------------------------------------------------------
682
684
 
683
685
  const IP_CHECK_PROXY_PREFIX = "/proxy/ipcheck";
684
686
  const IP_CHECK_TARGET = "https://ip.net.coffee";
685
687
 
686
- function rewriteIpCheckHtml(html) {
687
- const prefix = IP_CHECK_PROXY_PREFIX;
688
- return html
689
- .replace(
690
- /(\s(?:href|src|action)\s*=\s*["'])\/(?!\/|proxy\/ipcheck\/)/g,
691
- `$1${prefix}/`,
692
- )
693
- .replace(
694
- /(fetch\s*\(\s*["'`])\/(?!\/|proxy\/ipcheck\/)/g,
695
- `$1${prefix}/`,
696
- );
697
- }
698
-
699
688
  // ---------------------------------------------------------------------------
700
689
  // Main handler factory
701
690
  // ---------------------------------------------------------------------------
@@ -1015,34 +1004,49 @@ function createLocalApiHandler({ queuePath, allowedHosts = [] } = {}) {
1015
1004
  return true;
1016
1005
  }
1017
1006
 
1018
- // --- ip-check proxy: reverse-proxy ip.net.coffee/claude/ (issue #81) ---
1007
+ // --- ip-check proxy: reverse-proxy ip.net.coffee (issue #81) ---
1008
+ // Lock-down: GET/HEAD only, restricted path prefixes, do not forward
1009
+ // browser credentials or fingerprintable headers. Without these limits
1010
+ // /proxy/ipcheck is an open reverse-proxy any local process can abuse
1011
+ // (exfiltrate dashboard cookies, anonymously POST through user IP).
1019
1012
  if (p.startsWith(`${IP_CHECK_PROXY_PREFIX}/`) || p === IP_CHECK_PROXY_PREFIX) {
1013
+ const method = String(req.method || "GET").toUpperCase();
1014
+ if (method !== "GET" && method !== "HEAD") {
1015
+ json(res, { error: "Method Not Allowed" }, 405);
1016
+ return true;
1017
+ }
1020
1018
  const targetPath = p === IP_CHECK_PROXY_PREFIX
1021
1019
  ? "/"
1022
1020
  : p.slice(IP_CHECK_PROXY_PREFIX.length) || "/";
1021
+ const ALLOWED_PREFIXES = [
1022
+ "/api/geoip/",
1023
+ "/api/geoip-batch",
1024
+ "/api/iprisk/",
1025
+ "/api/dns/result/",
1026
+ "/claude/status.json",
1027
+ "/favicons/",
1028
+ "/ip/",
1029
+ ];
1030
+ if (!ALLOWED_PREFIXES.some((prefix) => targetPath.startsWith(prefix))) {
1031
+ json(res, { error: "Path not allowed" }, 403);
1032
+ return true;
1033
+ }
1023
1034
  const targetUrl = `${IP_CHECK_TARGET}${targetPath}${url.search || ""}`;
1024
1035
  try {
1025
- const proxyHeaders = {};
1026
- for (const [key, value] of Object.entries(req.headers)) {
1027
- const lk = key.toLowerCase();
1028
- if (["host", "connection", "referer", "origin"].includes(lk)) continue;
1029
- proxyHeaders[key] = value;
1030
- }
1031
- proxyHeaders["host"] = "ip.net.coffee";
1032
- proxyHeaders["referer"] = `${IP_CHECK_TARGET}${targetPath}`;
1033
-
1034
- const method = String(req.method || "GET").toUpperCase();
1035
- let body;
1036
- if (method !== "GET" && method !== "HEAD") {
1037
- const chunks = [];
1038
- for await (const chunk of req) chunks.push(chunk);
1039
- if (chunks.length > 0) body = Buffer.concat(chunks);
1040
- }
1036
+ // Whitelist forwarded headers — no cookies, no auth, no fingerprintable
1037
+ // identity. Only what the upstream needs to negotiate content.
1038
+ const proxyHeaders = {
1039
+ host: "ip.net.coffee",
1040
+ accept: req.headers["accept"] || "*/*",
1041
+ "accept-language": req.headers["accept-language"] || "en",
1042
+ "accept-encoding": req.headers["accept-encoding"] || "gzip",
1043
+ "user-agent": "TokenTracker/IPCheck (https://www.tokentracker.cc)",
1044
+ referer: `${IP_CHECK_TARGET}${targetPath}`,
1045
+ };
1041
1046
 
1042
1047
  const proxyRes = await fetch(targetUrl, {
1043
1048
  method,
1044
1049
  headers: proxyHeaders,
1045
- body,
1046
1050
  redirect: "manual",
1047
1051
  });
1048
1052
 
@@ -1061,12 +1065,7 @@ function createLocalApiHandler({ queuePath, allowedHosts = [] } = {}) {
1061
1065
  ([k]) => !stripped.has(k.toLowerCase()),
1062
1066
  );
1063
1067
 
1064
- const contentType = proxyRes.headers.get("content-type") || "";
1065
- let resBody = Buffer.from(await proxyRes.arrayBuffer());
1066
- if (contentType.toLowerCase().includes("text/html")) {
1067
- resBody = Buffer.from(rewriteIpCheckHtml(resBody.toString("utf8")), "utf8");
1068
- }
1069
-
1068
+ const resBody = Buffer.from(await proxyRes.arrayBuffer());
1070
1069
  res.writeHead(proxyRes.status, Object.fromEntries(responseHeaders));
1071
1070
  res.end(resBody);
1072
1071
  } catch (e) {