@vale-dinap/lucidly 0.1.0 → 0.3.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/CHANGELOG.md ADDED
@@ -0,0 +1,59 @@
1
+ # Changelog
2
+
3
+ All notable changes to Lucidly are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow [Semantic Versioning](https://semver.org/).
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.3.0] - 2026-11-06
8
+
9
+ ### Added
10
+
11
+ - **Glance, a view of the run at a glance**, now the default view (next to Simple, which you can still pick; Lucidly remembers your choice), with the token heat button: a large orb for the agent's state (working, your turn, asked you something, in trouble, quiet), and the tasks as a vertical wheel: the one in the spotlight unfolds into its steps as large shapes, the two above and below stay compressed and fade out. Turn the wheel (mouse wheel, `↑` / `↓`, or a click) to look at other tasks; it comes back to the live one by itself. Almost no text: hover for names, click for the side panel, double-click for the Simple view.
12
+ - **A clear sign when the Lucidly server stops.** The status pill turns red ("Server stopped") and a bar under the top bar says the page no longer updates and since when. Live timers and animations stop, and keep-awake "only while working" lets go. Start `lucidly` again and the page reconnects by itself, and the warning goes.
13
+ - **The session list refreshes itself while it's open**, so a session started in the meantime shows up straight away. Only the transcripts that changed are read again.
14
+ - **All projects, grouped by folder.** In the session list's *All projects*, sessions sit in a folding group per project folder, with its name, session count and latest change. This project comes first and open, as do projects with a live session; *Expand all* / *Collapse all* at the top.
15
+ - **A light / dark theme switch**, at the right end of the top bar: System (the default, following the operating system), Light or Dark. The choice is remembered.
16
+ - **Long shell commands are shown in parts**: their shell lines, the script they run (`node - <<'EOF'`, `python -c "…"`), highlighted as JavaScript or Python, and the content they write to a file (`cat > a.py <<'EOF'`), labelled with the file. *Show the whole command* shows it as recorded. For commands that change files, long lines that wrap continue indented, and dense JavaScript can be laid out for reading with *Reformat for reading* (display only; *Show as written* goes back).
17
+ - **A task continued by your reply shows where each round ended.** When you answer after the agent finished ("yes, go with the first"), the chain no longer files your reply inside an earlier step and loses the first result: the end node stays as an intermediate goal (a smaller diamond: done, asked, or interrupted, with that round's result), your reply follows it, then the new steps and the final goal. The Detailed view shows the same.
18
+ - **Every message of yours has its own place.** One sent while the agent worked was filed inside a step; it is now a node in the Simple view and a row in the Detailed view, between the steps before and after it. In the details panel your messages are outlined in the accent colour.
19
+ - **Copy buttons on every card of the side panel**: messages, the agent's text and results (as Markdown), plans, thinking, commands and their parts, inputs, outputs, errors and diffs (as a unified diff). Long blocks are copied whole, even when only their first lines are shown.
20
+ - **A task's request, messages and round ends open its whole conversation.** Click one (in the Simple or Detailed view) and the panel shows the task's conversation, scrolled to that point, which glows a soft yellow.
21
+
22
+ ### Fixed
23
+
24
+ - **A finished turn shows as finished straight away.** Lucidly waited for a record Claude Code doesn't always write (not after a turn started by a background task's notification), so a session could look busy for up to five minutes after it was done. The agent's final message now ends the turn.
25
+ - **No more steps that never existed.** A bulleted list whose items all open with a bold label ("**Rows:** …") describes things; it was taken for a plan, adding steps that never ran (shown as to come, then as done with the others).
26
+ - **Files changed by shell commands are now edits**, not plain commands: `sed -i`, redirections and heredocs, `tee`, `cp`/`mv`/`rm`, PowerShell's `Set-Content` and the like, and scripts that write files. They read "Edit app.js, README.md (by command)" (or Delete, Rename, Move), count with the edits, appear in the Files view and keep their command line, which shows where a diff would be.
27
+ - **A message after `/compact` is no longer mistaken for one sent mid-turn**, which used to fold it into the previous task.
28
+
29
+ ### Changed
30
+
31
+ - **Questions show a speech balloon instead of a question mark**: Q&A exchanges, questions the agent asks you, and the rounds that ended with one.
32
+ - **Token heat shows better on the light theme.** Glows are light, and pale green and yellow washed out on white: on the light theme the heat colours are now a little darker, more saturated and stronger, and the page background a touch darker.
33
+ - **The Files view's History lists the newest change first.**
34
+ - **The Files view no longer flags recovered failures in red.** A file only shows the failed edits nothing has made up for. Recovered ones are left to their own rows, where they read "failed → recovered" in grey, here and in the other views.
35
+ - **`lucidly` no longer guesses which session to open.** It opens the folder's session when there's no doubt (the only one, or the only one in progress) and otherwise starts on the session list.
36
+
37
+ ## [0.2.0] - 2026-11-05
38
+
39
+ ### Added
40
+
41
+ - **Zoom for the Simple view**, from 50% to 200%: a stepped slider in the toolbar, Ctrl+scroll over the view, or `-` and `=` (`0`, or a click on the percentage, resets it). Bigger for comfortable reading and high-resolution screens; smaller for an overview of more tasks and longer chains, with step labels hidden below 75% and task titles kept readable. The setting is remembered.
42
+
43
+ ### Fixed
44
+
45
+ - **Expand all / Collapse all now work with every filter.** Choosing a filter no longer opens all of its tasks: it narrows the list and keeps what is open or closed. The button's label follows what is on screen.
46
+ - **The session list no longer hangs on "Loading…"** when the Lucidly server is slow, stopped or returns an error: it says what happened and offers a Retry.
47
+
48
+ ### Changed
49
+
50
+ - A slightly larger keep-awake icon.
51
+ - The README starts with a quick start: `npx @vale-dinap/lucidly`.
52
+
53
+ ## [0.1.0] - 2026-11-05
54
+
55
+ First public release.
56
+
57
+ [0.3.0]: https://github.com/vale-dinap/lucidly/compare/v0.2.0...v0.3.0
58
+ [0.2.0]: https://github.com/vale-dinap/lucidly/compare/v0.1.0...v0.2.0
59
+ [0.1.0]: https://github.com/vale-dinap/lucidly/releases/tag/v0.1.0
package/README.md CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  *See what your AI agent is doing, at a glance.*
6
6
 
7
+ https://github.com/user-attachments/assets/ff9ff898-4317-4e78-99e4-caac09b620ca
8
+
9
+ ## Quick start
10
+
11
+ From a project folder where you use Claude Code (Node.js 22.18 or newer):
12
+
13
+ ```sh
14
+ npx @vale-dinap/lucidly
15
+ ```
16
+
17
+ It opens in your browser on that folder's session, or lets you pick one if there are several, and follows it live while you keep working. To keep the `lucidly` command around, install it: see [Getting started](#getting-started).
18
+
7
19
  ## Purpose
8
20
 
9
21
  AI agents are increasingly capable of working on long, multi-step tasks on their own. While they work, most of them report their progress as a continuous stream of text: plans, tool calls, file reads, command outputs, reasoning, errors and retries. That stream is complete, but it is hard to follow. To find out what the agent is doing right now, what it has already finished, or where it got stuck, the user has to read through a fast-moving wall of text and often scroll back through a long history to find the relevant part.
@@ -36,6 +48,38 @@ Every item in the view, whether a planned step, a subtask or a blocker, is linke
36
48
 
37
49
  **Agent-agnostic design.** Lucidly works by reading agent output, not by depending on a specific agent's internals. Support for different agents and frameworks is meant to be added through lightweight adapters that teach Lucidly how to recognize each one's output.
38
50
 
51
+ ## See it in action
52
+
53
+ All of these show a made-up demo session (`scripts/demo-session.mjs`).
54
+
55
+ **Drill down.** Open a step in place to see each of its actions, hover one for what it did, and click it for its full output.
56
+
57
+ https://github.com/user-attachments/assets/a8d20d0e-41c5-4ada-a9d5-b9cf6541714f
58
+
59
+ **Where did the tokens go?** Token heat makes every task and step glow from green to red by the tokens it used, relative to the rest of the run.
60
+
61
+ https://github.com/user-attachments/assets/df84be05-f78d-45f5-8feb-2f0439094efd
62
+
63
+ **What changed.** Every file the agent edited, with the diff of each change, by file or in time order.
64
+
65
+ https://github.com/user-attachments/assets/08240389-cf8b-4216-ac5e-d06cd6bc79a4
66
+
67
+ **Every action, filterable.** The full history of commands, edits, reads and subagent calls: filter by kind, failures or task, search, and open any of them.
68
+
69
+ https://github.com/user-attachments/assets/a11f0f13-b1f4-42bd-920c-c0b9cd8ca7e4
70
+
71
+ **The detailed view**, with a task open: its steps, the models that worked on them and their tokens, the files and commands involved, and a subagent's work.
72
+
73
+ ![The detailed view, with a task open: steps, models and tokens, files and commands, a subagent and the latest actions](docs/media/img_01_detailed-view-with-the-audit-task-open.png)
74
+
75
+ **Token usage**, from the meter in the top bar: input, output, cache reads and the current context, per model, and tokens per minute over the last hour.
76
+
77
+ ![The token usage breakdown: totals, a table per model and tokens per minute](docs/media/img_02_the-token-popover-from-the-top-bar.png)
78
+
79
+ **Light or dark**, following the system, or chosen in the top bar.
80
+
81
+ ![The simple view in the light theme](docs/media/img_03_light-theme.png)
82
+
39
83
  ## Intended use
40
84
 
41
85
  Lucidly is aimed primarily at developers who work with AI agents, whether to get tasks done or to build and debug agents themselves. For them it offers a faster way to follow a run, spot where something went wrong, and jump straight to the relevant output.
@@ -52,8 +96,6 @@ Requires Node.js 22.18 or newer. Install the `lucidly` command from npm:
52
96
  npm install -g @vale-dinap/lucidly
53
97
  ```
54
98
 
55
- or try it without installing: `npx @vale-dinap/lucidly`.
56
-
57
99
  To run it from a clone instead (Node runs the TypeScript sources directly, no build needed):
58
100
 
59
101
  ```sh
@@ -62,10 +104,10 @@ npm start # same as: node src/cli.ts
62
104
  npm link # optional: puts a `lucidly` command on your PATH
63
105
  ```
64
106
 
65
- Run `lucidly` (or `npm start`) from a project folder. It opens the latest Claude Code session of that folder in your browser and follows it live while you keep working in Claude Code as usual.
107
+ Run `lucidly` (or `npm start`) from a project folder. It opens in your browser and follows a Claude Code session live while you keep working in Claude Code as usual. If the folder has a single session, or a single one in progress, that one opens straight away; otherwise the session list opens for you to choose. The list keeps itself up to date, so a session you start in the meantime shows up in it.
66
108
 
67
109
  ```sh
68
- lucidly # latest session of the current folder
110
+ lucidly # this folder's session (or choose one)
69
111
  lucidly 89a0789f # a specific session (id or id prefix, or a .jsonl path)
70
112
  lucidly list [--all] # recent sessions (this folder, or all projects)
71
113
  lucidly outline <session> # text outline in the terminal
@@ -82,16 +124,17 @@ In the browser:
82
124
  - The **token meter** in the top bar is always visible: input and output tokens so far, the main agent's current context size, and the live rate per minute. Click it (or press `T`) for the breakdown: fresh input, cache writes and reads, output, a table per model, and tokens per minute over the last hour. Counts come from the transcripts, subagents included.
83
125
  - The **overview** counts tasks, steps, actions and *failed* actions (with how many failures were recovered), and draws the whole run as a strip of thin bars, one per action, in time order. File edits are amber and a little taller, with a pointed top when zoomed in enough to see it (*file edit* in the legend). Shell commands are green, and reads, searches and the rest are short teal bars. Violet marks a subagent's launch. Blue is running, pale red is a failure that was recovered, and solid red is one that is still failed. While the agent is working, the overview card and the current task's card glow softly, and a pulsing blue mark holds the live end of the strip. The current task's steps that haven't started yet follow in grey. Click a bar to jump to that action. Subagents get their own track under the main one, like layers in a video editor. Each subagent is one box from its launch to its last action, on the same time-ordered axis, so you can see what ran in parallel with what. Overlapping subagents stack on separate rows (up to four for the part in view; a busier burst shares the last row and shows a *+N* count), a running one pulses with a dashed border, and one with failures has a red border. The main track leaves gaps where only subagents were working. Zoom in to widen the boxes and read their names. Select a subagent (its violet bar on the strip, its tile in the path, or anything it did) and its own **timeline** opens below the strip, showing its model, actions, duration, tokens and failures, with one bar per action. Its span is marked on the main strip. Click a bar to open that action. The timeline closes as soon as you select something outside the subagent, or with ×, which goes back to the step that called it. On long runs, zoom the strip like a video editor's timeline: scroll over it to zoom around the cursor (Shift+scroll pans), or use the navigator below it, a mini map of the whole run, where you drag the window to pan, drag its edges to zoom, click to jump, and double-click to fit. The `−` `+` *Fit* buttons do the same.
84
126
  - A failure counts as **recovered** when a later action in the same step or subtask, or in a fix nested inside it, succeeds. Otherwise it counts as **failed**: the agent moved on or the session ended without getting past it. This is a heuristic based on the order of actions, not on what the error was.
85
- - **Simple** (the default view) keeps only the essentials. Tasks sit on a vertical line, oldest at the top, each a round node with its status (done, running, interrupted) and one line of facts. A Q&A exchange is a small speech-bubble node with the start of the answer. Click a task to unfold its steps to the right as a chain, from your request to the goal. Each step is a node whose shape says what it mostly did: a pointed tile for file edits, a rounded rectangle for commands, a circle for reads and searches, a hexagon for subagents, a speech bubble for a question to you. Its colour matches the timeline once done; the running step glows blue, a failed one is red, steps not started yet are dashed. Only the running task is open by default, and it folds closed when the next one starts. A long chain scrolls sideways with the request pinned on the left, and follows the running step by itself until you scroll it yourself (scroll back to the end to resume). New nodes pop in and finished steps flash once. Click any node for its details on the right; a selected step also opens in place into a capsule of small marks, one per action in the timeline's colours (failed red, recovered ringed, running blue), up to 12 and then *+N*. Hover a mark for what it did, click it to open that action. It folds back when you select something else. Search dims what doesn't match, and *Hide Q&A* hides the questions answered without any work, as in the detailed view.
127
+ - **Glance** (the default view) shows the run at a glance, with almost no text: is it going fine, and does it need you? A large orb says what the agent is doing: breathing blue while it works (with an icon for what it's doing now: editing, running commands, reading), a warm amber pulse with a speech balloon when it asked you something (its question tool, or its last words a question), with the question itself quoted in a balloon beside the orb until you answer, a green pulse with a check when it finished (steady green once that's a while ago), steady red when failures in the current step aren't fixed yet, and still grey when it's quiet, interrupted or the server has stopped. Under it, the time the current turn has been running (or since it stopped), and the title of the task in the spotlight. Below, the tasks form a vertical wheel: two tasks above and two below are compressed to their node and fade out, magnified towards the spotlight (the outer ones at their size, the spotlight 70% larger), and the one in the spotlight unfolds sideways into its steps as large shapes (the same as in Simple, without labels) once the wheel comes to rest. Its steps sit in one row, with one node in the middle (on the live task the running step or the latest one; on a past task your request, and if the side panel is open it shows that task's conversation; or the step you click) and the request and the goal always at the ends; the steps between fade with distance, and those that don't fit leave a dotted gap. The spotlight stays in place and the tasks slide through it: turn the mouse wheel (one task per notch; spun fast, it speeds up), press `↑` / `↓`, or click a compressed task; `←` / `→` move along the steps. With a node selected, the arrows move the selection too, so the side panel follows at once, before the task has unfolded. A faint line under the wheel recalls the keys. The spotlight follows the live task; on another one, the orb dims slightly and a click on it (or `F`) brings you back, as does half a minute without a touch. Hover anything for its name, click a step for the side panel, double-click a task or step to open it in Simple. The overview and the toolbar step aside, leaving only the view switch and the token heat button. Switching into or out of Glance is animated: the timeline folds away (or comes back), the view switch and the flame button slide to their new places, and the rest cross-fades. With heat on, the tasks and steps glow here too, and the task in the spotlight, unfolded into its steps, keeps its own glow as a soft disc on the spine, behind the step in the middle. `Enter` or `Space` selects the step in the middle (or clears it when it's already selected), for moving through Glance with the keyboard only.
128
+ - **Simple** keeps only the essentials. Tasks sit on a vertical line, oldest at the top, each a round node with its status (done, running, interrupted) and one line of facts. A Q&A exchange is a small speech-bubble node with the start of the answer. Click a task to unfold its steps to the right as a chain, from your request to the goal. Each step is a node whose shape says what it mostly did: a pointed tile for file edits, a rounded rectangle for commands, a circle for reads and searches, a hexagon for subagents, a speech bubble for a question to you. Its colour matches the timeline once done; the running step glows blue, a failed one is red, steps not started yet are dashed. When you reply after the agent finished and the task goes on ("yes, go ahead"), its end node stays in the chain as an intermediate goal, a smaller diamond (a check when done, *?* when it ended with a question), followed by your reply and the new steps. A message you send while the agent works is a node of its own too (*Message*), between the steps before and after it. The detailed view lists your messages between the steps in the same way, and the details panel outlines them in the accent colour, so your words stand out from the agent's. Clicking the request, one of your messages, or the end of a round (in either view) opens the whole conversation of the task in the panel and scrolls to that point, which glows a soft yellow: your message, or that round's result. Only the running task is open by default, and it folds closed when the next one starts. A long chain scrolls sideways with the request pinned on the left, and follows the running step by itself until you scroll it yourself (scroll back to the end to resume). New nodes pop in and finished steps flash once. Click any node for its details on the right; a selected step also opens in place into a capsule of small marks, one per action in the timeline's colours (failed red, recovered ringed, running blue), up to 12 and then *+N*. Hover a mark for what it did, click it to open that action. It folds back when you select something else. The **zoom** slider in the toolbar (also Ctrl+scroll over the view, or `-` and `=`; click the percentage or press `0` to reset) scales this view from 50% to 200%: bigger for comfortable reading or a high-resolution screen, smaller for an overview of more tasks and longer chains at once (below 75% step labels are hidden; hover a node for its name). Search dims what doesn't match, and *Hide Q&A* hides the questions answered without any work, as in the detailed view.
86
129
  - **Detailed** lists each task as a card with a progress bar (one segment per step). Steps are expandable: folded, a step is one line with its flags (errors, subtasks, subagents, *Blocked*); open, it shows the models that worked on it and their tokens, the files and commands involved, its subtasks and blockers, and its latest actions. Subagents show their model too (for example *Haiku 5.5*). Each task ends at its goal while in progress, and at its result once done. The current step opens by itself. Filter by *Failed* (failures nothing has succeeded after yet, and open blockers), *Recovered* (trouble along the way that got fixed, flagged ↺) or *Subagents*, hide Q&A exchanges, search steps, files and commands (`/`), or *Expand all* (`E`).
87
130
  - Click any item to see **its own output** on the right. Narration reads as formatted text (markdown: headings, tables, nested and task lists, quotes, links, and code blocks with highlighting), and every action is a row you can expand to see its input and result. File edits show as a diff, like on GitHub: hunks with old and new line numbers, context lines, and the changed words highlighted. Shell commands are syntax-highlighted, including Python run inline (`python -c`, `python - <<EOF`), as are Python and shell files in diffs and writes.
88
- - **Files** (a view of its own, next to *Simple* and *Detailed*) lists every file the agent changed, with the number of edits, lines removed and added, when it was last touched, and a sparkline of where in the run its edits happened. Sort by recency, by number of edits, or by path. Open a file for its history, oldest edit first, with the task and step behind each change. Click an edit for its diff in the side panel, or use *Show all diffs* to read the whole history inline. Files outside the project (scratch files, notes) are folded away at the bottom. Switch from *By file* to *History* for every edit in time order, oldest first, so the same file appears once per change. Dividers show the task each group of edits belonged to, and *Show all diffs* reads the whole history inline. Only changes made through the agent's edit and write tools are listed: files changed by shell commands (`sed -i`, scripts, redirections) are not.
131
+ - **Files** (a view of its own, next to *Simple* and *Detailed*) lists every file the agent changed, with the number of edits, lines removed and added, when it was last touched, and a sparkline of where in the run its edits happened. Sort by recency, by number of edits, or by path. Open a file for its history, oldest edit first, with the task and step behind each change. Click an edit for its diff in the side panel, or use *Show all diffs* to read the whole history inline. Files outside the project (scratch files, notes) are folded away at the bottom. Switch from *By file* to *History* for every edit in time order, newest first, so the same file appears once per change. Dividers show the task each group of edits belonged to, and *Show all diffs* reads the whole history inline. Changes made through shell commands are listed too, marked *(by command)*: `sed -i`, redirections and heredocs, `tee`, `cp`, `mv`, `rm`, PowerShell's `Set-Content` and the like, and scripts (`node -e`, `python - <<EOF`) that write files. The transcript holds the command but not the change, so those show the command instead of a diff.
89
132
  - The overview also shows the **tokens** used so far (fresh input plus output) and the current rate. Click it for the breakdown.
90
133
  - If Lucidly's own code changed after its server started, a **Restart Lucidly to update** notice appears in the top bar, because the page may expect data the running server doesn't produce yet.
91
- - Switch views with the buttons at the left of the toolbar under the timeline (it stays on screen while you scroll), or `V` to cycle through *Simple*, *Detailed*, *Files* and **Actions**. In Simple and Detailed, the **filters** next to them narrow the list: *All*, *Failed*, *Recovered*, *Subagents* and *Questions* (the exchanges answered without any work), each with its count. When the toolbar is too narrow (for example with the side panel open), they fold into one menu button showing the current filter, with a red dot if anything is still failed. *Hide Q&A* applies to *All* only, and greys out with the other filters. Search is a magnifier until you click it or press `/`. The flame button (or `H`) turns on **token heat**: tasks and steps glow from green to red by the tokens they used (fresh input, cache writes and output, the way the token meter counts them), relative to the rest of the run, on a log scale so only the real outliers turn red. Tasks and questions are ranked together, steps among steps. It is off by default; hover a glowing node or dot for its tokens. Actions is a flat, chronological list of everything the agent did: commands, edits, reads, searches, subagent calls. Filter by kind, show only *Failed* actions or only *Recovered* ones (each failure is tagged *failed* or *recovered*), search (`/`), or limit to one task. Each failure shows its actual error line. Select a row to see its full input and output, and *Show in path* to jump back to the step it belongs to. *Copy commands* copies the listed shell commands, with failed ones marked.
92
- - The **pin** in the overview's top-right corner (or `P`) keeps the overview on screen too while you scroll. It is pinned by default: scrolled down, it tightens into a compact card (about half the height) that still shows everything: the stats on one line, the timeline, the subagents on a single row (parallel ones are counted on a *+N* marker), the navigator and the legend. It unfolds again back at the top, and what you were reading stays put during the transition. Unpin it to let it scroll away; Lucidly remembers the choice. The arrows button at the right of the toolbar (or `E`) expands or collapses everything.
93
- - **Keep awake** (the cup next to the status in the top bar) stops the device from sleeping, like a no-sleep page. Turn it on, and optionally choose *Only while the agent is working*: the device stays awake while a turn runs and may sleep once the work is done and the session is idle (or has had no news for 30 minutes). The cup steams while it is holding the device awake. It uses the browser's Screen Wake Lock, so it works while the Lucidly tab is visible: a hidden or minimised tab lets the device sleep, and it takes effect again when you come back.
94
- - A session opens at the top, on the overview and its timeline, with the side panel closed until you select something. Whenever you're not at the end of the list, a round **arrow button** floats at the bottom right, with a count of actions that arrived since you scrolled away. Click it (or press `F` or `End`) to jump to the end and follow the agent, keeping the current step in view. If the side panel is closed it stays closed: the list just scrolls along. Scrolling or clicking pauses following. **Open session** (the folder button on the left, the run's title, or `O`) lists the Claude Code sessions of this project or of all projects.
134
+ - Switch views with the buttons at the left of the toolbar under the timeline (it stays on screen while you scroll), or `V` to cycle through *Glance*, *Simple*, *Detailed*, *Files* and **Actions**. In Simple and Detailed, the **filters** next to them narrow the list: *All*, *Failed*, *Recovered*, *Subagents* and *Questions* (the exchanges answered without any work), each with its count. When the toolbar is too narrow (for example with the side panel open), they fold into one menu button showing the current filter, with a red dot if anything is still failed. *Hide Q&A* applies to *All* only, and greys out with the other filters. Search is a magnifier until you click it or press `/`. The flame button (or `H`) turns on **token heat**: tasks and steps glow from green to red by the tokens they used (fresh input, cache writes and output, the way the token meter counts them), relative to the rest of the run, on a log scale so only the real outliers turn red. Tasks and questions are ranked together, steps among steps. It is off by default; hover a glowing node or dot for its tokens. Actions is a flat, chronological list of everything the agent did: commands, edits, reads, searches, subagent calls. Filter by kind, show only *Failed* actions or only *Recovered* ones (each failure is tagged *failed*, or *failed → recovered* in grey), search (`/`), or limit to one task. Each failure shows its actual error line. Select a row to see its full input and output, and *Show in path* to jump back to the step it belongs to. A long shell command is shown in parts: its shell lines, the script it runs (`node - <<'EOF'`, `python -c "…"`, highlighted as JavaScript or Python), and the content it writes to a file (`cat > a.py <<'EOF'`), with *Show the whole command* for the command as recorded. For commands that change files, long lines that wrap continue indented so each real line stands out, and dense JavaScript has *Reformat for reading*: line breaks and indentation for display only, with *Show as written* to go back. *Copy commands* copies the listed shell commands, with failed ones marked. In the side panel, every card has its own **copy button**: your messages, the agent's text, plans and results (as Markdown), thinking, commands and each of their parts (without the `$` prompt; a script as written, even when shown reformatted), inputs, outputs and errors (whole, even when only the first lines are shown), and diffs (as a unified diff). A tick confirms the copy.
135
+ - The **pin** in the overview's top-right corner (or `P`) keeps the overview on screen too while you scroll. It is pinned by default: scrolled down, it tightens into a compact card (about half the height) that still shows everything: the stats on one line, the timeline, the subagents on a single row (parallel ones are counted on a *+N* marker), the navigator and the legend. It unfolds again back at the top, and what you were reading stays put during the transition. Unpin it to let it scroll away; Lucidly remembers the choice. The arrows button at the right of the toolbar (or `E`) expands or collapses everything.
136
+ - **Keep awake** (the cup next to the status in the top bar) stops the device from sleeping, like a no-sleep page. Turn it on, and optionally choose *Only while the agent is working*: the device stays awake while a turn runs and may sleep once the work is done and the session is idle (or has had no news for 30 minutes). The cup steams while it is holding the device awake. It uses the browser's Screen Wake Lock, so it works while the Lucidly tab is visible: a hidden or minimised tab lets the device sleep, and it takes effect again when you come back.
137
+ - A session opens at the top, on the overview and its timeline, with the side panel closed until you select something. Whenever you're not at the end of the list, a round **arrow button** floats at the bottom right, with a count of actions that arrived since you scrolled away. Click it (or press `F` or `End`) to jump to the end and follow the agent, keeping the current step in view. If the side panel is closed it stays closed: the list just scrolls along. Scrolling or clicking pauses following. **Open session** (the folder button on the left, the run's title, or `O`) lists the Claude Code sessions of this project or of all projects. All projects are grouped by project folder, each group headed by the folder's name, its session count and when it last changed (a green dot if a session in it is live). This project comes first; groups fold, and the ones open by default are this project's and any with a live session (or all of them, when there are only a few). *Expand all* / *Collapse all* sits at the top, and what you open or close stays that way.
95
138
  - The download button, next to *Open session* on the left, saves a **snapshot** of the run as a `.lucidly.json` file, and *Open a saved snapshot…* in the session list reopens one. You don't need this for Claude Code sessions, which reopen straight from their transcripts. It's for keeping a run after its transcript is cleaned up, sharing it with someone who doesn't have the transcript, and runs streamed from other agents through stdin, which have no transcript to reopen.
96
139
 
97
140
  Lucidly only reads. It binds to `127.0.0.1` and never sends anything to the agent.
@@ -104,12 +147,13 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for how it works inside.
104
147
  npm test # unit + integration tests (node:test)
105
148
  npm run typecheck # tsc --noEmit
106
149
  npm run build # compiles src/ to dist/ (runs by itself on npm pack / npm publish)
150
+ node scripts/demo-session.mjs [dir] [--play] # a made-up session for screenshots and demos (see the script)
107
151
  ```
108
152
 
109
153
  ## What Lucidly is not
110
154
 
111
155
  Lucidly is a viewer, not a controller. It does not send commands to the agent, approve or block actions, or change how the agent behaves. All interaction with the agent continues to happen through whatever application or harness the user already uses. This keeps Lucidly simple, safe to run alongside any agent, and focused on one job: making the agent's work easy to see and understand.
112
-
113
- ## License
114
-
115
- Copyright 2026 Valerio Di Napoli. Lucidly is licensed under the [Apache License 2.0](LICENSE); see also [NOTICE](NOTICE).
156
+
157
+ ## License
158
+
159
+ Copyright 2026 Valerio Di Napoli. Lucidly is licensed under the [Apache License 2.0](LICENSE); see also [NOTICE](NOTICE).
@@ -1,4 +1,5 @@
1
- import { PLANNING_TOOLS, summarizeTool } from "./claude-tools.js";
1
+ import { KnownFiles, PLANNING_TOOLS, commandEdit, summarizeTool } from "./claude-tools.js";
2
+ import { commandParts } from "./shell-writes.js";
2
3
  const REJECTED = /doesn't want to (?:proceed|take this action)|user rejected|tool use was rejected|permission to use .* (?:was )?denied/i;
3
4
  function blockText(content) {
4
5
  if (typeof content === 'string')
@@ -91,6 +92,11 @@ export class ClaudeCodeAdapter {
91
92
  counted = new Set();
92
93
  /** Usage of the latest message, held until its content blocks have been emitted. */
93
94
  pendingUsage = null;
95
+ /** The working folder as of the latest record, and the files seen so far: for shell edits. */
96
+ cwd;
97
+ known = new KnownFiles();
98
+ /** A turn.end was emitted and nothing has happened since: a later turn_duration adds nothing. */
99
+ turnEnded = false;
94
100
  /** @param scope set for subagent transcripts: the callId of the spawning Agent call. */
95
101
  scope;
96
102
  constructor(scope) {
@@ -109,6 +115,8 @@ export class ClaudeCodeAdapter {
109
115
  if (this.pendingUsage && (rec.type !== 'assistant' || rec.message?.id !== this.pendingUsage.id))
110
116
  out.push(...this.flush());
111
117
  const ts = rec.timestamp;
118
+ if (typeof rec.cwd === 'string' && rec.cwd)
119
+ this.cwd = rec.cwd;
112
120
  const emit = (e) => {
113
121
  if (this.scope)
114
122
  e.scope = this.scope;
@@ -134,8 +142,10 @@ export class ClaudeCodeAdapter {
134
142
  emit({ type: 'run.meta', title: rec.aiTitle ?? rec.summary });
135
143
  break;
136
144
  case 'user':
137
- if (!rec.isSidechain || this.scope)
145
+ if (!rec.isSidechain || this.scope) {
146
+ this.turnEnded = false;
138
147
  this.onUser(rec, emit);
148
+ }
139
149
  break;
140
150
  case 'queue-operation':
141
151
  // Background work finishing is queued for the agent as a task notification.
@@ -146,6 +156,18 @@ export class ClaudeCodeAdapter {
146
156
  if (!rec.isSidechain || this.scope) {
147
157
  this.onAssistant(rec, emit);
148
158
  this.noteUsage(rec);
159
+ // The agent's final words end its turn, without waiting for turn_duration: Claude Code
160
+ // doesn't write one for every turn (not after a background task's notification). A
161
+ // final message comes in several records (thinking, then text) that all say end_turn:
162
+ // the turn ends with the one holding its text.
163
+ const m = rec.message;
164
+ if (!this.scope && m?.stop_reason === 'end_turn' && Array.isArray(m.content) && m.content.some((b) => b.type === 'text')) {
165
+ out.push(...this.flush());
166
+ emit({ type: 'turn.end' });
167
+ this.turnEnded = true;
168
+ }
169
+ else
170
+ this.turnEnded = false;
149
171
  }
150
172
  break;
151
173
  case 'system':
@@ -155,7 +177,9 @@ export class ClaudeCodeAdapter {
155
177
  // stream-json end of a run
156
178
  if (rec.is_error)
157
179
  emit({ type: 'notice', level: 'error', text: `Run ended with an error (${rec.subtype ?? 'error'})`, raw: rec.result });
158
- emit({ type: 'turn.end' });
180
+ if (!this.turnEnded)
181
+ emit({ type: 'turn.end' });
182
+ this.turnEnded = true;
159
183
  break;
160
184
  }
161
185
  return out;
@@ -328,7 +352,12 @@ export class ClaudeCodeAdapter {
328
352
  }
329
353
  return;
330
354
  }
331
- const s = summarizeTool(name, input);
355
+ let s = summarizeTool(name, input);
356
+ if (s.kind === 'command')
357
+ s = commandEdit(name, input, s, this.cwd, this.known) ?? s;
358
+ const parts = name === 'Bash' && typeof input.command === 'string' ? commandParts(input.command) : undefined;
359
+ for (const f of s.files ?? [])
360
+ this.known.add(f);
332
361
  emit({
333
362
  type: 'tool.start',
334
363
  callId: b.id,
@@ -339,6 +368,8 @@ export class ClaudeCodeAdapter {
339
368
  command: s.command,
340
369
  files: s.files,
341
370
  spawnsAgent: s.spawnsAgent,
371
+ ...(s.via ? { via: s.via } : {}),
372
+ ...(parts ? { parts } : {}),
342
373
  raw: s.raw,
343
374
  });
344
375
  }
@@ -347,7 +378,9 @@ export class ClaudeCodeAdapter {
347
378
  const content = typeof rec.content === 'string' ? rec.content : '';
348
379
  // (stream-json's `init` record is covered by the generic first-record run.meta in push())
349
380
  if (sub === 'turn_duration') {
350
- emit({ type: 'turn.end' });
381
+ if (!this.turnEnded)
382
+ emit({ type: 'turn.end' });
383
+ this.turnEnded = true;
351
384
  }
352
385
  else if (sub === 'compact_boundary') {
353
386
  emit({ type: 'notice', level: 'info', text: 'Context compacted' });
@@ -1,4 +1,6 @@
1
+ import { posix, win32 } from 'node:path';
1
2
  import { firstLine, truncate } from "../core/text.js";
3
+ import { shellWrites } from "./shell-writes.js";
2
4
  const str = (v) => (typeof v === 'string' ? v : v == null ? '' : JSON.stringify(v));
3
5
  export function baseName(p) {
4
6
  const parts = p.split(/[\\/]/).filter(Boolean);
@@ -121,3 +123,74 @@ export function summarizeTool(name, input) {
121
123
  }
122
124
  /** Tools that express a plan rather than an action. */
123
125
  export const PLANNING_TOOLS = new Set(['TodoWrite', 'TaskCreate', 'TaskUpdate', 'ExitPlanMode']);
126
+ // ───────────────────────────── files changed through the shell
127
+ /**
128
+ * Files the session has dealt with (read, edited, written), by normalized absolute path: a path
129
+ * found inside a script counts as a written file only if it is one of these.
130
+ */
131
+ export class KnownFiles {
132
+ byKey = new Map();
133
+ add(path) {
134
+ if (path)
135
+ this.byKey.set(fileKey(path), path);
136
+ }
137
+ /** The known file a path names: the same file, or else the only one it is the tail of. */
138
+ find(path, resolved) {
139
+ const exact = this.byKey.get(fileKey(resolved));
140
+ if (exact)
141
+ return exact;
142
+ const tail = '/' + fileKey(path).replace(/^(?:\.\/)+/, '');
143
+ const hits = [...this.byKey].filter(([k]) => k.endsWith(tail));
144
+ return hits.length === 1 ? hits[0][1] : undefined;
145
+ }
146
+ }
147
+ const fileKey = (p) => {
148
+ const s = p.replace(/\\/g, '/');
149
+ return /^[a-z]:\//i.test(s) ? s.toLowerCase() : s;
150
+ };
151
+ /** A path from a command, made absolute from the folder it ran in (Git Bash's /c/... included). */
152
+ export function resolveFrom(base, p) {
153
+ if (p.startsWith('~'))
154
+ return p;
155
+ const win = base ? /^[a-z]:[\\/]/i.test(base) : /^[a-z]:[\\/]/i.test(p);
156
+ const fix = (x) => (win ? x.replace(/^\/([a-z])(?=\/|$)/i, (_, d) => `${d.toUpperCase()}:`) : x);
157
+ const path = win ? win32 : posix;
158
+ const q = fix(p);
159
+ if (path.isAbsolute(q) || !base)
160
+ return win ? path.normalize(q) : q;
161
+ return path.resolve(fix(base), q);
162
+ }
163
+ /**
164
+ * A shell command that changes files, as an edit made by a command: the files it writes or deletes
165
+ * (resolved from `cwd`), labelled "Edit a.ts, b.ts (by command)". Undefined when it changes none.
166
+ */
167
+ export function commandEdit(name, input, s, cwd, known) {
168
+ const files = [];
169
+ const ops = [];
170
+ for (const w of shellWrites(str(input.command), { powershell: name === 'PowerShell' })) {
171
+ const dir = w.dir ? resolveFrom(cwd, w.dir) : cwd;
172
+ const abs = resolveFrom(dir, w.path);
173
+ const file = w.guessed ? known.find(w.path, abs) : abs;
174
+ if (!file || files.some((f) => fileKey(f) === fileKey(file)))
175
+ continue;
176
+ files.push(file);
177
+ ops.push(w.op);
178
+ }
179
+ if (!files.length)
180
+ return undefined;
181
+ const names = files.map(baseName);
182
+ const list = (n) => (n.length > 3 ? `${n.slice(0, 3).join(', ')} and ${n.length - 3} more` : n.join(', '));
183
+ let what;
184
+ if (ops.every((o) => o === 'delete'))
185
+ what = `Delete ${list(names)}`;
186
+ else if (ops.every((o) => o === 'from' || o === 'to')) {
187
+ // Moves and renames only: "Rename a.md → b.md", or how many files moved.
188
+ const from = files.filter((_, i) => ops[i] === 'from');
189
+ const to = files.filter((_, i) => ops[i] === 'to');
190
+ const verb = new Set(files.map((f) => fileKey(f).replace(/[^/]*$/, ''))).size === 1 ? 'Rename' : 'Move';
191
+ what = from.length === 1 && to.length === 1 ? `${verb} ${baseName(from[0])} → ${baseName(to[0])}` : `${verb} ${list(from.map(baseName))}`;
192
+ }
193
+ else
194
+ what = `Edit ${list(names)}`;
195
+ return { ...s, kind: 'edit', via: 'command', label: `${what} (by command)`, files };
196
+ }