@vladar107/claudescope 0.4.1 → 0.5.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.
package/README.md CHANGED
@@ -38,10 +38,11 @@ Claudescope works whether you use one agent or all three. Adding another is just
38
38
 
39
39
  > **Privacy:** Everything runs locally on `127.0.0.1`. The app **never** writes to
40
40
  > `~/.claude`, `~/.codex`, or `~/.junie` — all are read-only sources. Its only persistent
41
- > state lives in `~/.claudescope/` — a DuckDB index and a copy of the pricing
42
- > file, both safe to delete anytime. The sole outbound request is an optional
43
- > daily check for a newer published version (`claudescope update`); nothing about
44
- > your transcripts ever leaves your machine.
41
+ > state lives in `~/.claudescope/` — a DuckDB index, a copy of the pricing file, and
42
+ > a cached pricing snapshot (`pricing.fetched.json`), all safe to delete anytime. The
43
+ > sole outbound requests are an optional daily check for a newer published version
44
+ > (`claudescope update`) and an optional daily pricing refresh (`claudescope pricing
45
+ > update`); nothing about your transcripts ever leaves your machine.
45
46
 
46
47
  ---
47
48
 
@@ -139,8 +140,9 @@ claudescope restart # restart it
139
140
  claudescope status # is it running? is an update available?
140
141
  claudescope open # open the running app in your browser
141
142
  claudescope logs -f # tail the server log
142
- claudescope update # upgrade to the latest published version and restart
143
- claudescope help # full usage
143
+ claudescope update # upgrade to the latest published version and restart
144
+ claudescope pricing update # fetch current model prices (LiteLLM) into the local rate table
145
+ claudescope help # full usage
144
146
 
145
147
  # options: --port <n> (default 4317, or $PORT)
146
148
  # --no-open (don't open the browser on start)
@@ -205,13 +207,24 @@ cost = ( input_tokens × input_rate
205
207
  The per-event cost is computed once at index time and stored, so analytics is
206
208
  just a `SUM` over events; a project/session total is the sum of its events.
207
209
 
208
- Rates live in `~/.claudescope/pricing.json` (seeded on first run from the copy
209
- shipped with the package; when running from source, `packages/server/pricing.json`).
210
- A model id resolves in this order:
211
- exact `models` entry **family** match (`opus` / `sonnet` / `haiku` / `gpt`
212
- substring) `default`. The family step means version- or date-suffixed ids (e.g.
213
- `claude-haiku-4-5-20251001`, or a Codex `gpt-5.x-codex` id) still price correctly.
214
- Shipped rates (USD per 1M tokens, from Anthropic and OpenAI published API pricing):
210
+ Rates are resolved in a layered lookup:
211
+
212
+ 1. **Fetched exact id** `~/.claudescope/pricing.fetched.json` (auto-refreshed
213
+ daily from [LiteLLM's community price table](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json),
214
+ covering Anthropic, OpenAI, Gemini, xAI, Mistral, and DeepSeek models).
215
+ 2. **Local exact id** — `~/.claudescope/pricing.json` (seeded on first run from
216
+ the shipped default; user-editable; takes precedence over the fetched snapshot
217
+ for any id it defines explicitly).
218
+ 3. **Family match** — `opus` / `sonnet` / `haiku` / `gemini` / `gpt` substring in
219
+ the model id → the matching family rate from `pricing.json`.
220
+ 4. **Default** — the `default` entry in `pricing.json`.
221
+
222
+ The family step means version- or date-suffixed ids (e.g. `claude-haiku-4-5-20251001`,
223
+ `gpt-5.x-codex`, `gemini-2.5-flash`) still price correctly. `pricing.json` is the
224
+ user-editable fallback and override layer for families and the default rate; the
225
+ fetched snapshot provides exact per-model rates for all known models.
226
+
227
+ Shipped fallback rates (USD per 1M tokens):
215
228
 
216
229
  | family / model | input | output | cache write (5m) | cache read |
217
230
  | ------------------- | ----- | ------ | ---------------- | ---------- |
@@ -219,20 +232,21 @@ Shipped rates (USD per 1M tokens, from Anthropic and OpenAI published API pricin
219
232
  | Opus 4.1 / 4 | $15 | $75 | $18.75 | $1.50 |
220
233
  | Sonnet 4.x | $3 | $15 | $3.75 | $0.30 |
221
234
  | Haiku 4.5 | $1 | $5 | $1.25 | $0.10 |
235
+ | Gemini 2.5 Pro-class| $1.25 | $10 | — | $0.31 |
222
236
  | GPT-5 | $0.63 | $5 | — | $0.13 |
223
237
  | GPT-5.4 | $2.50 | $15 | — | $0.50 |
224
238
  | GPT-5.5 | $5 | $30 | — | $0.50 |
225
239
  | `<synthetic>` | $0 | $0 | $0 | $0 |
226
240
 
227
- - Edit `~/.claudescope/pricing.json` to update prices or add models, then re-index
228
- (`POST /api/reindex` or `claudescope restart`) to recompute.
229
- - Or run **`npm run update-pricing`** to refresh `pricing.json` from Anthropic's
230
- published pricing page. There's no official pricing *API*, so this is a
231
- best-effort scrape (it validates what it parses and won't write garbage) —
232
- review the diff afterwards. Use `--dry-run` to preview without writing.
233
- - The `opus`/`sonnet`/`haiku`/`gpt` family rules use **current** pricing; the
234
- deprecated Opus 4 / 4.1 ($15/$75) and specific GPT-5 versions are pinned via
235
- exact `models` entries. Add an exact entry to override any specific model.
241
+ - Rates **auto-refresh daily** in the background while the server runs. Run
242
+ `claudescope pricing update` to force a refresh at any time. New rates apply
243
+ to newly indexed events; existing indexed costs are unchanged.
244
+ - Edit `~/.claudescope/pricing.json` to override families, the default rate, or
245
+ pin specific model prices. Re-index (`POST /api/reindex` or `claudescope
246
+ restart`) to recompute stored costs at the new rates.
247
+ - The `opus`/`sonnet`/`haiku`/`gemini`/`gpt` family rules use **current**
248
+ pricing; the deprecated Opus 4 / 4.1 ($15/$75) and specific GPT-5 versions are
249
+ pinned via exact `models` entries. Add an exact entry to override any model.
236
250
 
237
251
  > **Caveat:** these are **list-price estimates** — they ignore any discounts,
238
252
  > service tier, or batch pricing, and the cache-write rate assumes the 5-minute
@@ -332,8 +346,10 @@ bundles, and publishes. Auth uses npm **Trusted Publishing** (OIDC) — no
332
346
  ## Security & privacy
333
347
 
334
348
  Claudescope runs entirely on your machine. It treats `~/.claude`, `~/.codex`, and
335
- `~/.junie` as **read-only**, **binds to `127.0.0.1` only**, sends **no telemetry**, and its sole outbound
336
- request is a cached npm-registry version check for the update notice. See
349
+ `~/.junie` as **read-only**, **binds to `127.0.0.1` only**, and sends **no telemetry**. Its only
350
+ outbound requests are a cached npm-registry version check for the update notice
351
+ and a daily fetch of public model pricing rates from LiteLLM (disable with
352
+ `PRICING_REFRESH_INTERVAL_MS=0`). See
337
353
  [`SECURITY.md`](./SECURITY.md) for the full breakdown of filesystem, network,
338
354
  shell, and self-update behavior — and how to report a vulnerability.
339
355
 
package/cli.js CHANGED
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __cr } from 'node:module';
3
3
  const require = __cr(import.meta.url);
4
- import{spawn as T,spawnSync as b}from"node:child_process";import{existsSync as D,mkdirSync as $,openSync as F,readFileSync as I,realpathSync as J,rmSync as y,writeFileSync as U}from"node:fs";import{dirname as V,join as h}from"node:path";import{createInterface as G}from"node:readline/promises";import{parseArgs as W}from"node:util";import{fileURLToPath as K}from"node:url";import{copyFileSync as ie,existsSync as H,mkdirSync as ae}from"node:fs";import{homedir as l}from"node:os";import{dirname as M,join as s}from"node:path";import{fileURLToPath as B}from"node:url";var v=M(B(import.meta.url));function x(e,o){return e.find(t=>H(t))??o}var E=Number(process.env.PORT??4317),f=s(v,"..");function m(e){return e==="~"?l():e.startsWith("~/")?s(l(),e.slice(2)):e}var w=m(process.env.CLAUDE_PROJECTS_DIR??s(l(),".claude","projects")),ue=m(process.env.CODEX_SESSIONS_DIR??s(l(),".codex","sessions")),de=m(process.env.JUNIE_SESSIONS_DIR??s(l(),".junie","sessions")),fe=process.env.OPEN_BROWSER==="1",me=Number(process.env.REINDEX_INTERVAL_MS??15e3),a=m(process.env.CLAUDESCOPE_HOME??s(l(),".claudescope")),ge=process.env.DUCKDB_PATH??s(a,"index.duckdb"),he=x([s(v,"pricing.default.json"),s(f,"pricing.json")],s(f,"pricing.json")),Se=process.env.PRICING_PATH??s(a,"pricing.json"),ve=process.env.WEB_DIST_DIR??x([s(v,"web"),s(f,"..","web","dist")],s(f,"..","web","dist")),i="0.4.1";var P=V(K(import.meta.url)),X=h(P,"server.js"),p=h(a,"daemon.json"),g=h(a,"daemon.log"),_=h(a,"update-check.json"),u="@vladar107/claudescope",q=24*60*60*1e3;function S(){if(!D(p))return null;try{return JSON.parse(I(p,"utf8"))}catch{return null}}function d(e){try{return process.kill(e,0),!0}catch{return!1}}async function C(e){try{return(await fetch(`http://127.0.0.1:${e}/api/health`,{signal:AbortSignal.timeout(1500)})).ok}catch{return!1}}async function Y(e,o){let t=Date.now()+o;for(;Date.now()<t;){if(await C(e))return!0;process.stdout.write("."),await new Promise(r=>setTimeout(r,500))}return!1}function R(e){let o=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";try{T(o,[e],{stdio:"ignore",detached:!0,shell:process.platform==="win32"}).unref()}catch{}}async function N(e,o){$(a,{recursive:!0});let t=S();if(t&&d(t.pid)&&await C(t.port)){console.log(`\u2713 claudescope is already running \u2192 ${t.url}`),o&&R(t.url);return}t&&!d(t.pid)&&y(p,{force:!0});let r=`http://localhost:${e}`,n=F(g,"a"),c=T(process.execPath,[X],{detached:!0,stdio:["ignore",n,n],env:{...process.env,PORT:String(e),OPEN_BROWSER:"0"}});if(c.unref(),U(p,JSON.stringify({pid:c.pid,port:e,url:r,version:i,startedAt:new Date().toISOString()},null,2)),process.stdout.write("\u203A Starting claudescope"),!await Y(e,2e4)){console.error(`
4
+ import{spawn as G,spawnSync as I}from"node:child_process";import{existsSync as D,mkdirSync as B,openSync as ue,readFileSync as N,realpathSync as de,rmSync as C,writeFileSync as J}from"node:fs";import{dirname as fe,join as E}from"node:path";import{createInterface as me}from"node:readline/promises";import{parseArgs as ge}from"node:util";import{fileURLToPath as he}from"node:url";import{copyFileSync as ke,existsSync as X,mkdirSync as De}from"node:fs";import{homedir as l}from"node:os";import{dirname as Y,join as i}from"node:path";import{fileURLToPath as q}from"node:url";var R=Y(q(import.meta.url));function T(e,t){return e.find(n=>X(n))??t}var v=Number(process.env.PORT??4317),h=i(R,"..");function _(e){return e==="~"?l():e.startsWith("~/")?i(l(),e.slice(2)):e}var b=_(process.env.CLAUDE_PROJECTS_DIR??i(l(),".claude","projects")),Te=_(process.env.CODEX_SESSIONS_DIR??i(l(),".codex","sessions")),$e=_(process.env.JUNIE_SESSIONS_DIR??i(l(),".junie","sessions")),Me=process.env.OPEN_BROWSER==="1",Ue=Number(process.env.REINDEX_INTERVAL_MS??15e3),a=_(process.env.CLAUDESCOPE_HOME??i(l(),".claudescope")),Fe=process.env.DUCKDB_PATH??i(a,"index.duckdb"),He=T([i(R,"pricing.default.json"),i(h,"pricing.json")],i(h,"pricing.json")),je=process.env.PRICING_PATH??i(a,"pricing.json"),p=process.env.FETCHED_PRICING_PATH??i(a,"pricing.fetched.json"),$=process.env.LITELLM_PRICING_URL??"https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",Ge=Number(process.env.PRICING_REFRESH_INTERVAL_MS??24*60*60*1e3),Be=process.env.WEB_DIST_DIR??T([i(R,"web"),i(h,"..","web","dist")],i(h,"..","web","dist")),c="0.5.0";import{mkdirSync as z,readFileSync as Q,renameSync as Z,writeFileSync as ee}from"node:fs";import{dirname as M,join as te}from"node:path";var oe=new Set(["anthropic","openai","gemini","xai","mistral","deepseek"]),ne=new Set(["chat","responses"]),re=1e6,se=1e4,ie=3e4;function y(e){if(typeof e!="number"||!Number.isFinite(e)||e<0)return null;let t=e*re;return t<=se?t:null}function U(e){return e==null?0:y(e)}function ce(e){let t={};if(typeof e!="object"||e===null)return t;for(let[n,r]of Object.entries(e)){if(n==="sample_spec"||n.includes("/")||typeof r!="object"||r===null)continue;let o=r;if(typeof o.litellm_provider!="string"||!oe.has(o.litellm_provider)||typeof o.mode!="string"||!ne.has(o.mode))continue;let s=y(o.input_cost_per_token),d=y(o.output_cost_per_token),f=U(o.cache_creation_input_token_cost),A=U(o.cache_read_input_token_cost);s===null||d===null||f===null||A===null||(t[n]={input:s,output:d,cacheWrite:f,cacheRead:A})}return t}function ae(e){let t=Object.keys(e),n=t.some(o=>o.startsWith("claude")),r=t.some(o=>/^(gpt|o\d|chatgpt|codex)/.test(o));if(!n||!r){let o=[!n&&"anthropic",!r&&"openai"].filter(Boolean).join(", ");throw new Error(`Fetched pricing failed validation: no ${o} model survived mapping (${t.length} model(s) total). LiteLLM schema may have drifted.`)}}function le(e){try{return JSON.parse(Q(e,"utf8"))?.models??{}}catch{return{}}}function pe(e,t){let n=0;for(let[r,o]of Object.entries(e)){let s=t[r];(!s||s.input!==o.input||s.output!==o.output||s.cacheWrite!==o.cacheWrite||s.cacheRead!==o.cacheRead)&&(n+=1)}return n}async function F(){let e=await fetch($,{signal:AbortSignal.timeout(ie)});if(!e.ok)throw new Error(`Pricing fetch failed: ${e.status} ${e.statusText}`);let t=await e.json(),n=ce(t);ae(n);let r=le(p),o=pe(n,r),s=new Date().toISOString(),d={fetchedAt:s,models:n};z(M(p),{recursive:!0});let f=te(M(p),`.pricing.fetched.${process.pid}.tmp`);return ee(f,`${JSON.stringify(d,null,2)}
5
+ `),Z(f,p),{fetchedAt:s,modelCount:Object.keys(n).length,changed:o,path:p}}var O=fe(he(import.meta.url)),_e=E(O,"server.js"),u=E(a,"daemon.json"),w=E(a,"daemon.log"),P=E(a,"update-check.json"),m="@vladar107/claudescope",we=24*60*60*1e3;function S(){if(!D(u))return null;try{return JSON.parse(N(u,"utf8"))}catch{return null}}function g(e){try{return process.kill(e,0),!0}catch{return!1}}async function x(e){try{return(await fetch(`http://127.0.0.1:${e}/api/health`,{signal:AbortSignal.timeout(1500)})).ok}catch{return!1}}async function Ee(e,t){let n=Date.now()+t;for(;Date.now()<n;){if(await x(e))return!0;process.stdout.write("."),await new Promise(r=>setTimeout(r,500))}return!1}function L(e){let t=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";try{G(t,[e],{stdio:"ignore",detached:!0,shell:process.platform==="win32"}).unref()}catch{}}async function H(e,t){B(a,{recursive:!0});let n=S();if(n&&g(n.pid)&&await x(n.port)){console.log(`\u2713 claudescope is already running \u2192 ${n.url}`),t&&L(n.url);return}n&&!g(n.pid)&&C(u,{force:!0});let r=`http://localhost:${e}`,o=ue(w,"a"),s=G(process.execPath,[_e],{detached:!0,stdio:["ignore",o,o],env:{...process.env,PORT:String(e),OPEN_BROWSER:"0"}});if(s.unref(),J(u,JSON.stringify({pid:s.pid,port:e,url:r,version:c,startedAt:new Date().toISOString()},null,2)),process.stdout.write("\u203A Starting claudescope"),!await Ee(e,2e4)){console.error(`
5
6
  \u2717 Server did not become healthy in time. Inspect: claudescope logs`),process.exitCode=1;return}console.log(`
6
- \u2713 claudescope running \u2192 ${r}`),console.log(` Sessions: ${w} (read-only)`),o&&R(r),await j(!1)}function O(){let e=S();if(!e||!d(e.pid)){console.log("claudescope is not running."),y(p,{force:!0});return}try{process.kill(e.pid,"SIGTERM")}catch{}y(p,{force:!0}),console.log(`\u2713 Stopped claudescope (pid ${e.pid}).`)}async function z(){let e=S();e&&d(e.pid)&&await C(e.port)?console.log(`\u25CF running ${e.url} (pid ${e.pid}, v${e.version})`):console.log(`\u25CB stopped (installed v${i})`),await j(!0)}function Q(){let e=S();e&&d(e.pid)?R(e.url):console.log("claudescope is not running. Start it with: claudescope start")}function Z(e){if(!D(g)){console.log("No logs yet.");return}e&&process.platform!=="win32"?b("tail",["-f",g],{stdio:"inherit"}):process.stdout.write(I(g,"utf8"))}async function ee(e,o){if(!process.stdin.isTTY)return o;let t=G({input:process.stdin,output:process.stdout});try{let r=(await t.question(`${e} ${o?"[Y/n]":"[y/N]"} `)).trim().toLowerCase();return r?r==="y"||r==="yes":o}finally{t.close()}}function oe(){let e=P;try{e=J(P)}catch{}return e.includes("/nix/store/")?"nix":/[\\/]Cellar[\\/]claudescope[\\/]/.test(e)?"brew":"npm"}async function te(e){let o=await L(!0);if(o&&!k(o,i)){console.log(`\u2713 Already on the latest version (v${i}).`);return}let t=oe();if(t==="brew"){console.log("claudescope was installed via Homebrew."),console.log(" Run: brew upgrade vladar107/tap/claudescope");return}if(t==="nix"){console.log("claudescope was installed via Nix."),console.log(" Run: nix profile upgrade claudescope"),console.log(" (flake users: re-run `nix run --refresh github:vladar107/claudescope`)");return}o||console.log("\u26A0 Could not reach the npm registry to confirm the latest version.");let r=o?`v${i} \u2192 v${o}`:`v${i} \u2192 latest`;if(console.log(`\u203A Will run: npm install -g ${u}@latest (${r})`),!e&&!await ee("Proceed?",!0)){console.log("Aborted.");return}console.log(`\u203A Updating ${u}\u2026`);let n=process.platform==="win32"?"npm.cmd":"npm";if(b(n,["install","-g",`${u}@latest`],{stdio:"inherit"}).status!==0){console.error(`\u2717 Update failed. If you run via npx, just re-run \`npx ${u}\` to get the latest.`),process.exitCode=1;return}O(),console.log("\u2713 Updated. Restarting\u2026"),b("claudescope",["start"],{stdio:"inherit",shell:process.platform==="win32"})}function k(e,o){let t=e.split(".").map(n=>Number.parseInt(n,10)||0),r=o.split(".").map(n=>Number.parseInt(n,10)||0);for(let n=0;n<3;n++){if((t[n]??0)>(r[n]??0))return!0;if((t[n]??0)<(r[n]??0))return!1}return!1}async function L(e){let o=Date.now();if(!e&&D(_))try{let c=JSON.parse(I(_,"utf8"));if(o-c.lastCheck<q)return c.latest}catch{}let t=`https://registry.npmjs.org/${u.replace("/","%2f")}/latest`,r=await fetch(t,{signal:AbortSignal.timeout(2500)});if(!r.ok)return null;let n=await r.json();return n.version?($(a,{recursive:!0}),U(_,JSON.stringify({lastCheck:o,latest:n.version})),n.version):null}async function j(e){try{let o=await L(e);o&&k(o,i)&&console.log(`
7
- \u2B06 Update available: v${i} \u2192 v${o}. Run: claudescope update`)}catch{}}function A(){console.log(`claudescope v${i} \u2014 local viewer for Claude Code transcripts
7
+ \u2713 claudescope running \u2192 ${r}`),console.log(` Sessions: ${b} (read-only)`),t&&L(r),await K(!1)}function k(){let e=S();if(!e||!g(e.pid)){console.log("claudescope is not running."),C(u,{force:!0});return}try{process.kill(e.pid,"SIGTERM")}catch{}C(u,{force:!0}),console.log(`\u2713 Stopped claudescope (pid ${e.pid}).`)}async function Se(){let e=S();e&&g(e.pid)&&await x(e.port)?console.log(`\u25CF running ${e.url} (pid ${e.pid}, v${e.version})`):console.log(`\u25CB stopped (installed v${c})`),await K(!0)}function Re(){let e=S();e&&g(e.pid)?L(e.url):console.log("claudescope is not running. Start it with: claudescope start")}function ve(e){if(!D(w)){console.log("No logs yet.");return}e&&process.platform!=="win32"?I("tail",["-f",w],{stdio:"inherit"}):process.stdout.write(N(w,"utf8"))}async function be(e,t){if(!process.stdin.isTTY)return t;let n=me({input:process.stdin,output:process.stdout});try{let r=(await n.question(`${e} ${t?"[Y/n]":"[y/N]"} `)).trim().toLowerCase();return r?r==="y"||r==="yes":t}finally{n.close()}}function ye(){let e=O;try{e=de(O)}catch{}return e.includes("/nix/store/")?"nix":/[\\/]Cellar[\\/]claudescope[\\/]/.test(e)?"brew":"npm"}async function Pe(e){let t=await W(!0);if(t&&!V(t,c)){console.log(`\u2713 Already on the latest version (v${c}).`);return}let n=ye();if(n==="brew"){console.log("claudescope was installed via Homebrew."),console.log(" Run: brew upgrade vladar107/tap/claudescope");return}if(n==="nix"){console.log("claudescope was installed via Nix."),console.log(" Run: nix profile upgrade claudescope"),console.log(" (flake users: re-run `nix run --refresh github:vladar107/claudescope`)");return}t||console.log("\u26A0 Could not reach the npm registry to confirm the latest version.");let r=t?`v${c} \u2192 v${t}`:`v${c} \u2192 latest`;if(console.log(`\u203A Will run: npm install -g ${m}@latest (${r})`),!e&&!await be("Proceed?",!0)){console.log("Aborted.");return}console.log(`\u203A Updating ${m}\u2026`);let o=process.platform==="win32"?"npm.cmd":"npm";if(I(o,["install","-g",`${m}@latest`],{stdio:"inherit"}).status!==0){console.error(`\u2717 Update failed. If you run via npx, just re-run \`npx ${m}\` to get the latest.`),process.exitCode=1;return}k(),console.log("\u2713 Updated. Restarting\u2026"),I("claudescope",["start"],{stdio:"inherit",shell:process.platform==="win32"})}function V(e,t){let n=e.split(".").map(o=>Number.parseInt(o,10)||0),r=t.split(".").map(o=>Number.parseInt(o,10)||0);for(let o=0;o<3;o++){if((n[o]??0)>(r[o]??0))return!0;if((n[o]??0)<(r[o]??0))return!1}return!1}async function W(e){let t=Date.now();if(!e&&D(P))try{let s=JSON.parse(N(P,"utf8"));if(t-s.lastCheck<we)return s.latest}catch{}let n=`https://registry.npmjs.org/${m.replace("/","%2f")}/latest`,r=await fetch(n,{signal:AbortSignal.timeout(2500)});if(!r.ok)return null;let o=await r.json();return o.version?(B(a,{recursive:!0}),J(P,JSON.stringify({lastCheck:t,latest:o.version})),o.version):null}async function K(e){try{let t=await W(e);t&&V(t,c)&&console.log(`
8
+ \u2B06 Update available: v${c} \u2192 v${t}. Run: claudescope update`)}catch{}}async function Ie(){try{let{modelCount:e,changed:t,path:n}=await F();console.log(`\u2713 Pricing updated: ${e} models (${t} changed) \u2192 ${n}`),console.log(" A running server picks up the new rates automatically (newly indexed events).")}catch(e){console.error(`\u2717 Pricing update failed: ${e instanceof Error?e.message:String(e)}`),console.error(" Existing rates are unchanged."),process.exitCode=1}}function Ce(){console.log(`Usage: claudescope pricing <subcommand>
9
+
10
+ Subcommands:
11
+ update Fetch current model prices (LiteLLM) into the local rate table`)}function j(){console.log(`claudescope v${c} \u2014 local viewer for Claude Code transcripts
8
12
 
9
13
  Usage: claudescope [command] [options]
10
14
 
@@ -16,15 +20,16 @@ Commands:
16
20
  open Open the running app in your browser
17
21
  logs [-f] Print the server log (-f / --follow to tail it)
18
22
  update [-y] Upgrade to the latest published version and restart
23
+ pricing update Fetch current model prices (LiteLLM) into the local rate table
19
24
  help Show this help
20
25
  version Print the installed version
21
26
 
22
27
  Options:
23
- --port <n> Port to listen on (default ${E}, or $PORT)
28
+ --port <n> Port to listen on (default ${v}, or $PORT)
24
29
  --no-open Don't open the browser on start
25
30
  -y, --yes Skip the confirmation prompt (for \`update\`)
26
31
 
27
32
  State (index, pricing, logs, PID) lives in ${a}
28
33
  (override with $CLAUDESCOPE_HOME). Sessions are read from
29
- ${w} (override with $CLAUDE_PROJECTS_DIR).`)}async function ne(){let{values:e,positionals:o}=W({allowPositionals:!0,options:{port:{type:"string"},follow:{type:"boolean",short:"f"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"},yes:{type:"boolean",short:"y"},"no-open":{type:"boolean"}}}),t=e.port?Number(e.port):E,r=!e["no-open"],n=o[0];switch(n||(n=e.help?"help":e.version?"version":"start"),n){case"start":await N(t,r);break;case"stop":O();break;case"restart":O(),await N(t,r);break;case"status":await z();break;case"open":Q();break;case"logs":Z(!!e.follow);break;case"update":await te(!!e.yes);break;case"version":console.log(i);break;case"help":A();break;default:console.error(`Unknown command: ${n}
30
- `),A(),process.exitCode=1}}ne().catch(e=>{console.error(e),process.exit(1)});
34
+ ${b} (override with $CLAUDE_PROJECTS_DIR).`)}async function Oe(){let{values:e,positionals:t}=ge({allowPositionals:!0,options:{port:{type:"string"},follow:{type:"boolean",short:"f"},help:{type:"boolean",short:"h"},version:{type:"boolean",short:"v"},yes:{type:"boolean",short:"y"},"no-open":{type:"boolean"}}}),n=e.port?Number(e.port):v,r=!e["no-open"],o=t[0];switch(o||(o=e.help?"help":e.version?"version":"start"),o){case"start":await H(n,r);break;case"stop":k();break;case"restart":k(),await H(n,r);break;case"status":await Se();break;case"open":Re();break;case"logs":ve(!!e.follow);break;case"update":await Pe(!!e.yes);break;case"pricing":{t[1]==="update"?await Ie():Ce();break}case"version":console.log(c);break;case"help":j();break;default:console.error(`Unknown command: ${o}
35
+ `),j(),process.exitCode=1}}Oe().catch(e=>{console.error(e),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vladar107/claudescope",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Local, read-only web app to browse, read, search, and analyze your AI coding-agent transcripts — Claude Code, OpenAI Codex, and JetBrains Junie.",
5
5
  "keywords": [
6
6
  "claude",
@@ -12,6 +12,7 @@
12
12
  "opus": { "input": 5, "output": 25, "cacheWrite": 6.25, "cacheRead": 0.5 },
13
13
  "sonnet": { "input": 3, "output": 15, "cacheWrite": 3.75, "cacheRead": 0.3 },
14
14
  "haiku": { "input": 1, "output": 5, "cacheWrite": 1.25, "cacheRead": 0.1 },
15
+ "gemini": { "input": 1.25, "output": 10, "cacheWrite": 0, "cacheRead": 0.31 },
15
16
  "gpt": { "input": 2.5, "output": 15, "cacheWrite": 0, "cacheRead": 0.5 }
16
17
  },
17
18
  "default": { "input": 3, "output": 15, "cacheWrite": 3.75, "cacheRead": 0.3 }