@vale-dinap/lucidly 0.2.0 → 0.4.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 CHANGED
@@ -1,26 +1,74 @@
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
- ## [0.2.0] - 2026-11-05
6
-
7
- ### Added
8
-
9
- - **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.
10
-
11
- ### Fixed
12
-
13
- - **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.
14
- - **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.
15
-
16
- ### Changed
17
-
18
- - A slightly larger keep-awake icon.
19
- - The README starts with a quick start: `npx @vale-dinap/lucidly`.
20
-
21
- ## [0.1.0] - 2026-11-05
22
-
23
- First public release.
24
-
25
- [0.2.0]: https://github.com/vale-dinap/lucidly/compare/v0.1.0...v0.2.0
26
- [0.1.0]: https://github.com/vale-dinap/lucidly/releases/tag/v0.1.0
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.4.0] - 2026-11-11
8
+
9
+ ### Added
10
+
11
+ - **Session tabs: several sessions open at once.** Picking a session in the list still replaces the one on screen; to keep both, open it in a new tab with `Ctrl`/`⌘`-click, a middle click, the row's **New tab** button, or the **+** at the end of the tab strip. The strip shows from two tabs on, at the very top of the page, above the top bar. Each tab has a dot in the orb's colours (blue working, amber your turn, green finished, red in trouble, grey quiet), and a count of running subagents. When a tab you're not looking at asks you something, finishes or runs into trouble, it flashes a few times, then glows until you open it, and the page title counts those tabs, like "(2) Lucidly". A session already in a tab is never opened twice: picking it switches to its tab. Each tab keeps your place (view, selection, Glance spotlight, scroll) for when you come back. `[` / `]` go to the previous / next tab, and a middle click on a tab closes it. Tabs survive a reload and a server restart, which reopens their sessions.
12
+ - **Glance names the step too, and says how both stand.** Under the orb, the task in the spotlight and, on a second line, the node in the middle of its chain (the step under way by default, "Step 3 of 5"; `←` / `→` move it, and the line follows: your request, a reply, a round's end, the result). Each line has a status mark: a spinning ring while it runs, a tick when done, a cross when in trouble, a balloon when it asked you, a pause when interrupted, an empty ring for what hasn't started.
13
+ - **Running subagents show in Glance.** While any runs, including in the background after the agent's turn ended, a tag beside the orb names the latest one, with "+N" for the others. Click it for the list of all of them (how long each has run, and the task and step that launched it); pick one and the wheel brings that step into the spotlight, and the side panel, if open, shows the subagent.
14
+
15
+ ### Changed
16
+
17
+ - **Lucidly stops following a session nobody has open.** Closing its tab (or replacing it with another session) stops it at once, unless another page has it too; a session no page has shown for 10 minutes stops by itself. Until now, every session opened kept being followed until the server stopped. A saved snapshot you loaded is kept.
18
+ - **An earlier task in the Glance spotlight centres on its goal**, where it ended up, instead of your request; with the side panel open, the goal is the one selected, and the panel shows the task with its result highlighted.
19
+ - **The System theme has a clearer icon**: the sun and the moon, cut along a diagonal, instead of a monitor. The Dark icon's moon now faces the same way.
20
+
21
+ ## [0.3.0] - 2026-11-06
22
+
23
+ ### Added
24
+
25
+ - **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.
26
+ - **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.
27
+ - **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.
28
+ - **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.
29
+ - **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.
30
+ - **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).
31
+ - **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.
32
+ - **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.
33
+ - **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.
34
+ - **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.
35
+
36
+ ### Fixed
37
+
38
+ - **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.
39
+ - **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).
40
+ - **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.
41
+ - **A message after `/compact` is no longer mistaken for one sent mid-turn**, which used to fold it into the previous task.
42
+
43
+ ### Changed
44
+
45
+ - **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.
46
+ - **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.
47
+ - **The Files view's History lists the newest change first.**
48
+ - **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.
49
+ - **`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.
50
+
51
+ ## [0.2.0] - 2026-11-05
52
+
53
+ ### Added
54
+
55
+ - **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.
56
+
57
+ ### Fixed
58
+
59
+ - **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.
60
+ - **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.
61
+
62
+ ### Changed
63
+
64
+ - A slightly larger keep-awake icon.
65
+ - The README starts with a quick start: `npx @vale-dinap/lucidly`.
66
+
67
+ ## [0.1.0] - 2026-11-05
68
+
69
+ First public release.
70
+
71
+ [0.4.0]: https://github.com/vale-dinap/lucidly/compare/v0.3.0...v0.4.0
72
+ [0.3.0]: https://github.com/vale-dinap/lucidly/compare/v0.2.0...v0.3.0
73
+ [0.2.0]: https://github.com/vale-dinap/lucidly/compare/v0.1.0...v0.2.0
74
+ [0.1.0]: https://github.com/vale-dinap/lucidly/releases/tag/v0.1.0
package/README.md CHANGED
@@ -2,8 +2,12 @@
2
2
 
3
3
  [![CI](https://github.com/vale-dinap/lucidly/actions/workflows/ci.yml/badge.svg)](https://github.com/vale-dinap/lucidly/actions/workflows/ci.yml)
4
4
 
5
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/vale-dinap/lucidly)
6
+
5
7
  *See what your AI agent is doing, at a glance.*
6
8
 
9
+ https://github.com/user-attachments/assets/6ceb52e7-eb58-4223-adba-d6a04d61b263
10
+
7
11
  ## Quick start
8
12
 
9
13
  From a project folder where you use Claude Code (Node.js 22.18 or newer):
@@ -12,7 +16,7 @@ From a project folder where you use Claude Code (Node.js 22.18 or newer):
12
16
  npx @vale-dinap/lucidly
13
17
  ```
14
18
 
15
- It opens that folder's latest session in your browser and follows it live while you keep working. To keep the `lucidly` command around, install it: see [Getting started](#getting-started).
19
+ 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).
16
20
 
17
21
  ## Purpose
18
22
 
@@ -46,6 +50,46 @@ Every item in the view, whether a planned step, a subtask or a blocker, is linke
46
50
 
47
51
  **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.
48
52
 
53
+ ## See it in action
54
+
55
+ All of these show a made-up demo session (`scripts/demo-session.mjs`).
56
+
57
+ **Glance**, the default view: how the agent is doing, at a glance. The orb says what it is doing now, and the task under way unfolds into its steps, from your request to the goal, with the steps still to come.
58
+
59
+ ![The Glance view while the agent works: the orb running a command, and the current task unfolded into its steps](docs/media/img_04_glance-view-while-the-agent-works.png)
60
+
61
+ **Glance and the side panel.** Pick any step, here a subagent's, and the panel shows its files, commands and transcript, while Glance stays where it was.
62
+
63
+ ![The Glance view in the light theme, with a subagent step open in the side panel](docs/media/img_05_glance-with-a-subagent-step-open-light-theme.png)
64
+
65
+ **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.
66
+
67
+ https://github.com/user-attachments/assets/a8d20d0e-41c5-4ada-a9d5-b9cf6541714f
68
+
69
+ **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.
70
+
71
+ https://github.com/user-attachments/assets/df84be05-f78d-45f5-8feb-2f0439094efd
72
+
73
+ **What changed.** Every file the agent edited, with the diff of each change, by file or in time order.
74
+
75
+ https://github.com/user-attachments/assets/08240389-cf8b-4216-ac5e-d06cd6bc79a4
76
+
77
+ **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.
78
+
79
+ https://github.com/user-attachments/assets/a11f0f13-b1f4-42bd-920c-c0b9cd8ca7e4
80
+
81
+ **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.
82
+
83
+ ![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)
84
+
85
+ **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.
86
+
87
+ ![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)
88
+
89
+ **Light or dark**, following the system, or chosen in the top bar.
90
+
91
+ ![The simple view in the light theme](docs/media/img_03_light-theme.png)
92
+
49
93
  ## Intended use
50
94
 
51
95
  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.
@@ -70,10 +114,10 @@ npm start # same as: node src/cli.ts
70
114
  npm link # optional: puts a `lucidly` command on your PATH
71
115
  ```
72
116
 
73
- 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.
117
+ 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.
74
118
 
75
119
  ```sh
76
- lucidly # latest session of the current folder
120
+ lucidly # this folder's session (or choose one)
77
121
  lucidly 89a0789f # a specific session (id or id prefix, or a .jsonl path)
78
122
  lucidly list [--all] # recent sessions (this folder, or all projects)
79
123
  lucidly outline <session> # text outline in the terminal
@@ -90,16 +134,18 @@ In the browser:
90
134
  - 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.
91
135
  - 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.
92
136
  - 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.
93
- - **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. 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.
137
+ - **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.
138
+ - **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.
94
139
  - **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`).
95
140
  - 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.
96
- - **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.
141
+ - **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.
97
142
  - The overview also shows the **tokens** used so far (fresh input plus output) and the current rate. Click it for the breakdown.
98
143
  - 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.
99
- - 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.
100
- - 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.
101
- - **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.
102
- - 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.
144
+ - 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.
145
+ - 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.
146
+ - **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.
147
+ - 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.
148
+ - **Tabs** let you keep several sessions open. Picking a session replaces the one on screen; `Ctrl`/`⌘`-click it, middle-click it or use its **New tab** button to open it in a new tab instead (the **+** at the end of the tab strip does the same). From two tabs on, a strip at the very top of the page, above the top bar, shows them, each with a dot in the orb's colours and a count of running subagents. A tab you're not looking at that asks you something, finishes or runs into trouble flashes, then glows until you open it, and the page title shows how many do. Picking a session that's already in a tab switches to it. Each tab keeps your place (view, selection, spotlight, scroll). `[` / `]` move between tabs, a middle click closes one, and they come back after a reload. Closing a tab stops following its session; one no page has shown for 10 minutes stops by itself.
103
149
  - 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.
104
150
 
105
151
  Lucidly only reads. It binds to `127.0.0.1` and never sends anything to the agent.
@@ -112,12 +158,13 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for how it works inside.
112
158
  npm test # unit + integration tests (node:test)
113
159
  npm run typecheck # tsc --noEmit
114
160
  npm run build # compiles src/ to dist/ (runs by itself on npm pack / npm publish)
161
+ node scripts/demo-session.mjs [dir] [--play] # a made-up session for screenshots and demos (see the script)
115
162
  ```
116
163
 
117
164
  ## What Lucidly is not
118
165
 
119
166
  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.
120
-
121
- ## License
122
-
123
- Copyright 2026 Valerio Di Napoli. Lucidly is licensed under the [Apache License 2.0](LICENSE); see also [NOTICE](NOTICE).
167
+
168
+ ## License
169
+
170
+ 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
+ }