@vladar107/claudescope 0.3.0 → 0.4.1

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
@@ -7,14 +7,27 @@
7
7
 
8
8
  *A scope for your AI coding-agent sessions.*
9
9
 
10
- A local, **read-only**, multi-agent viewer to browse, read, search, and analyze
11
- your AI coding-agent transcripts in one place — both
12
- [Claude Code](https://claude.com/claude-code) (`~/.claude/projects/**/*.jsonl`)
13
- and [OpenAI Codex](https://openai.com/codex) (`~/.codex/sessions/**/rollout-*.jsonl`).
10
+ Claudescope is a **local, read-only** viewer that brings every AI coding-agent
11
+ transcript on your machine into one place — to browse, read, search, and analyze.
14
12
  Sessions from every agent that worked in a directory are **merged under one
15
- project**, each tagged with the agent that produced it.
13
+ project**, each tagged with the agent that produced it. It runs entirely on your
14
+ machine and only ever reads your transcripts.
16
15
 
17
- - **Multi-agent** — Claude Code and Codex sessions side by side, each labeled with an **agent badge**. A project that several agents touched shows one card with all its agent tags; drill in and **filter the session list by agent**.
16
+ ## Supported agents
17
+
18
+ | Agent | Transcripts read from |
19
+ | ------------------------------------------------- | --------------------------------------------- |
20
+ | [Claude Code](https://claude.com/claude-code) | `~/.claude/projects/**/*.jsonl` |
21
+ | [OpenAI Codex](https://openai.com/codex) | `~/.codex/sessions/**/rollout-*.jsonl` |
22
+ | [JetBrains Junie](https://www.jetbrains.com/junie/) | `~/.junie/sessions/session-*/events.jsonl` |
23
+
24
+ Each source is optional — a directory that doesn't exist is simply skipped, so
25
+ Claudescope works whether you use one agent or all three. Adding another is just
26
+ [adding another connector](#how-it-works).
27
+
28
+ ## What it can do
29
+
30
+ - **Multi-agent** — Claude Code, Codex, and Junie sessions side by side, each labeled with an **agent badge**. A project that several agents touched shows one card with all its agent tags; drill in and **filter the session list by agent**.
18
31
  - **Browse** every session grouped by project — titles, dates, message/tool counts, token totals, cost, git branch, PR links.
19
32
  - **Read** a session as a clean threaded conversation: markdown, syntax-highlighted code, collapsible thinking, paired tool calls + results, **syntax-highlighted red/green diffs** for edits, attachments, and sidechain/subagent turns. A built-in **find-in-session** bar (⌘/Ctrl+F) searches the whole transcript — including collapsed thinking, tool, and subagent content — auto-expanding and highlighting matches, with a user/assistant filter.
20
33
  - **Review changes** via a **Files changed** tab that aggregates every edit/write in the session by file, with per-file diffs and +/− counts (diffs load lazily per file).
@@ -24,7 +37,7 @@ project**, each tagged with the agent that produced it.
24
37
  - **Light & dark themes** — follows your system appearance, with a manual toggle.
25
38
 
26
39
  > **Privacy:** Everything runs locally on `127.0.0.1`. The app **never** writes to
27
- > `~/.claude` or `~/.codex` — both are read-only sources. Its only persistent
40
+ > `~/.claude`, `~/.codex`, or `~/.junie` — all are read-only sources. Its only persistent
28
41
  > state lives in `~/.claudescope/` — a DuckDB index and a copy of the pricing
29
42
  > file, both safe to delete anytime. The sole outbound request is an optional
30
43
  > daily check for a newer published version (`claudescope update`); nothing about
@@ -101,6 +114,21 @@ Try it without installing:
101
114
  npx @vladar107/claudescope
102
115
  ```
103
116
 
117
+ ### Other install methods
118
+
119
+ ```bash
120
+ # Homebrew (macOS / Linux)
121
+ brew tap vladar107/tap
122
+ brew install claudescope
123
+
124
+ # Nix (any platform) — run without installing, or add to a profile
125
+ nix run github:vladar107/claudescope
126
+ nix profile install github:vladar107/claudescope
127
+ ```
128
+
129
+ All channels wrap the same package; `claudescope update` detects how you
130
+ installed it and points you at the right upgrade command.
131
+
104
132
  ### Commands
105
133
 
106
134
  ```bash
@@ -141,11 +169,12 @@ All optional — set via environment variables.
141
169
  | `PORT` | `4317` | Port the app listens on (or `--port <n>`). |
142
170
  | `CLAUDE_PROJECTS_DIR` | `~/.claude/projects` | Where to read Claude Code transcripts from. A leading `~` is expanded. |
143
171
  | `CODEX_SESSIONS_DIR` | `~/.codex/sessions` | Where to read OpenAI Codex transcripts from. A leading `~` is expanded.|
172
+ | `JUNIE_SESSIONS_DIR` | `~/.junie/sessions` | Where to read JetBrains Junie transcripts from. A leading `~` is expanded.|
144
173
  | `CLAUDESCOPE_HOME` | `~/.claudescope` | Where the app keeps its own state (index, pricing copy, logs, PID). |
145
174
  | `REINDEX_INTERVAL_MS` | `15000` | How often to auto-pick-up new/updated sessions. Set `0` to disable. |
146
175
 
147
176
  Each agent source is optional — if a directory doesn't exist it's simply skipped,
148
- so the app works whether you use one agent or both.
177
+ so the app works whether you use one agent or all three.
149
178
 
150
179
  Examples:
151
180
 
@@ -153,6 +182,7 @@ Examples:
153
182
  claudescope --port 8080 # custom port
154
183
  CLAUDE_PROJECTS_DIR=/path/to/exported/projects claudescope # view someone else's transcripts
155
184
  CODEX_SESSIONS_DIR=/path/to/codex/sessions claudescope # point at Codex sessions elsewhere
185
+ JUNIE_SESSIONS_DIR=/path/to/junie/sessions claudescope # point at Junie sessions elsewhere
156
186
  claudescope --no-open # don't pop a browser tab
157
187
  ```
158
188
 
@@ -231,6 +261,11 @@ served from cache (legitimately high for Claude Code, which re-reads cached cont
231
261
  explicitly. (Not a bug.)
232
262
  - **Codex sessions have no stored title**, so the title falls back to the first
233
263
  user message.
264
+ - **Junie sessions render differently.** Junie records an event-sourced UI stream
265
+ rather than a chat log, so a session reads as tool / terminal / file blocks plus
266
+ a final result — there's no assistant prose or thinking to show. Pasted
267
+ screenshots are surfaced inline. Older Junie sessions don't record a working
268
+ directory and group under an **"(unknown — Junie)"** project.
234
269
 
235
270
  ---
236
271
 
@@ -250,10 +285,10 @@ session. The index is a derived cache — if it's ever corrupted (e.g. the proce
250
285
  is killed mid-write) the app discards and rebuilds it automatically.
251
286
 
252
287
  Each agent is a **connector** (`packages/server/src/connectors/`). Claude Code
253
- JSONL is projected per-row; Codex spreads a session across record types, so its
254
- connector normalizes a rollout to canonical NDJSON first after that the
255
- indexing, search, cost, and threading paths are shared. Adding another agent is
256
- adding another connector.
288
+ JSONL is projected per-row; Codex spreads a session across record types and Junie
289
+ records an event-sourced UI stream, so those connectors normalize a session to
290
+ canonical NDJSON first — after that the indexing, search, cost, and threading
291
+ paths are shared. Adding another agent is adding another connector.
257
292
 
258
293
  ---
259
294
 
@@ -296,8 +331,8 @@ bundles, and publishes. Auth uses npm **Trusted Publishing** (OIDC) — no
296
331
 
297
332
  ## Security & privacy
298
333
 
299
- Claudescope runs entirely on your machine. It treats `~/.claude` and `~/.codex`
300
- as **read-only**, **binds to `127.0.0.1` only**, sends **no telemetry**, and its sole outbound
334
+ 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
301
336
  request is a cached npm-registry version check for the update notice. See
302
337
  [`SECURITY.md`](./SECURITY.md) for the full breakdown of filesystem, network,
303
338
  shell, and self-update behavior — and how to report a vulnerability.
@@ -306,10 +341,10 @@ shell, and self-update behavior — and how to report a vulnerability.
306
341
 
307
342
  ## Troubleshooting
308
343
 
309
- - **App is empty / "sessions directory not found"** — `CLAUDE_PROJECTS_DIR`
310
- (and/or `CODEX_SESSIONS_DIR`) doesn't point at real transcripts. Check the
311
- banner and set it correctly. Either source can be absent; only the present one
312
- is indexed.
344
+ - **App is empty / "sessions directory not found"** — none of `CLAUDE_PROJECTS_DIR`,
345
+ `CODEX_SESSIONS_DIR`, or `JUNIE_SESSIONS_DIR` points at real transcripts. Check
346
+ the banner and set them correctly. Any source can be absent; only the present
347
+ ones are indexed.
313
348
  - **`Error: listen EADDRINUSE :4317`** — the port is taken; run `claudescope --port <n>`.
314
349
  - **Node version errors** — you need Node ≥ 22 (`node -v`).
315
350
  - **Stale or wrong data** — delete `~/.claudescope/index.duckdb*` and
package/cli.js CHANGED
@@ -1,10 +1,10 @@
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 N,spawnSync as y}from"node:child_process";import{existsSync as O,mkdirSync as $,openSync as F,readFileSync as C,rmSync as b,writeFileSync as x}from"node:fs";import{dirname as M,join as g}from"node:path";import{createInterface as V}from"node:readline/promises";import{parseArgs as J}from"node:util";import{fileURLToPath as G}from"node:url";import{copyFileSync as re,existsSync as j,mkdirSync as se}from"node:fs";import{homedir as p}from"node:os";import{dirname as H,join as s}from"node:path";import{fileURLToPath as B}from"node:url";var v=H(B(import.meta.url));function I(e,t){return e.find(o=>j(o))??t}var S=Number(process.env.PORT??4317),f=s(v,"..");function E(e){return e==="~"?p():e.startsWith("~/")?s(p(),e.slice(2)):e}var _=E(process.env.CLAUDE_PROJECTS_DIR??s(p(),".claude","projects")),le=E(process.env.CODEX_SESSIONS_DIR??s(p(),".codex","sessions")),pe=process.env.OPEN_BROWSER==="1",ue=Number(process.env.REINDEX_INTERVAL_MS??15e3),a=E(process.env.CLAUDESCOPE_HOME??s(p(),".claudescope")),de=process.env.DUCKDB_PATH??s(a,"index.duckdb"),fe=I([s(v,"pricing.default.json"),s(f,"pricing.json")],s(f,"pricing.json")),me=process.env.PRICING_PATH??s(a,"pricing.json"),ge=process.env.WEB_DIST_DIR??I([s(v,"web"),s(f,"..","web","dist")],s(f,"..","web","dist")),i="0.3.0";var W=M(G(import.meta.url)),K=g(W,"server.js"),c=g(a,"daemon.json"),m=g(a,"daemon.log"),w=g(a,"update-check.json"),u="@vladar107/claudescope",X=24*60*60*1e3;function h(){if(!O(c))return null;try{return JSON.parse(C(c,"utf8"))}catch{return null}}function d(e){try{return process.kill(e,0),!0}catch{return!1}}async function D(e){try{return(await fetch(`http://127.0.0.1:${e}/api/health`,{signal:AbortSignal.timeout(1500)})).ok}catch{return!1}}async function q(e,t){let o=Date.now()+t;for(;Date.now()<o;){if(await D(e))return!0;process.stdout.write("."),await new Promise(r=>setTimeout(r,500))}return!1}function P(e){let t=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";try{N(t,[e],{stdio:"ignore",detached:!0,shell:process.platform==="win32"}).unref()}catch{}}async function A(e,t){$(a,{recursive:!0});let o=h();if(o&&d(o.pid)&&await D(o.port)){console.log(`\u2713 claudescope is already running \u2192 ${o.url}`),t&&P(o.url);return}o&&!d(o.pid)&&b(c,{force:!0});let r=`http://localhost:${e}`,n=F(m,"a"),l=N(process.execPath,[K],{detached:!0,stdio:["ignore",n,n],env:{...process.env,PORT:String(e),OPEN_BROWSER:"0"}});if(l.unref(),x(c,JSON.stringify({pid:l.pid,port:e,url:r,version:i,startedAt:new Date().toISOString()},null,2)),process.stdout.write("\u203A Starting claudescope"),!await q(e,2e4)){console.error(`
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(`
5
5
  \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: ${_} (read-only)`),t&&P(r),await L(!1)}function R(){let e=h();if(!e||!d(e.pid)){console.log("claudescope is not running."),b(c,{force:!0});return}try{process.kill(e.pid,"SIGTERM")}catch{}b(c,{force:!0}),console.log(`\u2713 Stopped claudescope (pid ${e.pid}).`)}async function Y(){let e=h();e&&d(e.pid)&&await D(e.port)?console.log(`\u25CF running ${e.url} (pid ${e.pid}, v${e.version})`):console.log(`\u25CB stopped (installed v${i})`),await L(!0)}function z(){let e=h();e&&d(e.pid)?P(e.url):console.log("claudescope is not running. Start it with: claudescope start")}function Q(e){if(!O(m)){console.log("No logs yet.");return}e&&process.platform!=="win32"?y("tail",["-f",m],{stdio:"inherit"}):process.stdout.write(C(m,"utf8"))}async function Z(e,t){if(!process.stdin.isTTY)return t;let o=V({input:process.stdin,output:process.stdout});try{let r=(await o.question(`${e} ${t?"[Y/n]":"[y/N]"} `)).trim().toLowerCase();return r?r==="y"||r==="yes":t}finally{o.close()}}async function ee(e){let t=await U(!0);if(t&&!k(t,i)){console.log(`\u2713 Already on the latest version (v${i}).`);return}t||console.log("\u26A0 Could not reach the npm registry to confirm the latest version.");let o=t?`v${i} \u2192 v${t}`:`v${i} \u2192 latest`;if(console.log(`\u203A Will run: npm install -g ${u}@latest (${o})`),!e&&!await Z("Proceed?",!0)){console.log("Aborted.");return}console.log(`\u203A Updating ${u}\u2026`);let r=process.platform==="win32"?"npm.cmd":"npm";if(y(r,["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}R(),console.log("\u2713 Updated. Restarting\u2026"),y("claudescope",["start"],{stdio:"inherit",shell:process.platform==="win32"})}function k(e,t){let o=e.split(".").map(n=>Number.parseInt(n,10)||0),r=t.split(".").map(n=>Number.parseInt(n,10)||0);for(let n=0;n<3;n++){if((o[n]??0)>(r[n]??0))return!0;if((o[n]??0)<(r[n]??0))return!1}return!1}async function U(e){let t=Date.now();if(!e&&O(w))try{let l=JSON.parse(C(w,"utf8"));if(t-l.lastCheck<X)return l.latest}catch{}let o=`https://registry.npmjs.org/${u.replace("/","%2f")}/latest`,r=await fetch(o,{signal:AbortSignal.timeout(2500)});if(!r.ok)return null;let n=await r.json();return n.version?($(a,{recursive:!0}),x(w,JSON.stringify({lastCheck:t,latest:n.version})),n.version):null}async function L(e){try{let t=await U(e);t&&k(t,i)&&console.log(`
7
- \u2B06 Update available: v${i} \u2192 v${t}. Run: claudescope update`)}catch{}}function T(){console.log(`claudescope v${i} \u2014 local viewer for Claude Code transcripts
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
8
8
 
9
9
  Usage: claudescope [command] [options]
10
10
 
@@ -20,11 +20,11 @@ Commands:
20
20
  version Print the installed version
21
21
 
22
22
  Options:
23
- --port <n> Port to listen on (default ${S}, or $PORT)
23
+ --port <n> Port to listen on (default ${E}, or $PORT)
24
24
  --no-open Don't open the browser on start
25
25
  -y, --yes Skip the confirmation prompt (for \`update\`)
26
26
 
27
27
  State (index, pricing, logs, PID) lives in ${a}
28
28
  (override with $CLAUDESCOPE_HOME). Sessions are read from
29
- ${_} (override with $CLAUDE_PROJECTS_DIR).`)}async function te(){let{values:e,positionals:t}=J({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"}}}),o=e.port?Number(e.port):S,r=!e["no-open"],n=t[0];switch(n||(n=e.help?"help":e.version?"version":"start"),n){case"start":await A(o,r);break;case"stop":R();break;case"restart":R(),await A(o,r);break;case"status":await Y();break;case"open":z();break;case"logs":Q(!!e.follow);break;case"update":await ee(!!e.yes);break;case"version":console.log(i);break;case"help":T();break;default:console.error(`Unknown command: ${n}
30
- `),T(),process.exitCode=1}}te().catch(e=>{console.error(e),process.exit(1)});
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)});
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@vladar107/claudescope",
3
- "version": "0.3.0",
4
- "description": "Local, read-only web app to browse, read, search, and analyze your AI coding-agent transcripts — Claude Code and OpenAI Codex.",
3
+ "version": "0.4.1",
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",
7
7
  "claude-code",
8
8
  "anthropic",
9
9
  "codex",
10
10
  "openai",
11
+ "junie",
12
+ "jetbrains",
11
13
  "transcripts",
12
14
  "sessions",
13
15
  "viewer",