agent-log-viewer 0.1.1 → 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/README.md +171 -51
- package/bin/cli.mjs +89 -22
- package/bin/tailscale.mjs +101 -41
- package/dist/standalone/.next/BUILD_ID +1 -1
- package/dist/standalone/.next/app-path-routes-manifest.json +10 -5
- package/dist/standalone/.next/build-manifest.json +2 -2
- package/dist/standalone/.next/prerender-manifest.json +3 -3
- package/dist/standalone/.next/routes-manifest.json +36 -0
- package/dist/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/standalone/.next/server/app/_not-found.rsc +4 -4
- package/dist/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
- package/dist/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/dist/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/standalone/.next/server/app/api/access/route.js +1 -1
- package/dist/standalone/.next/server/app/api/answer/route.js +1 -1
- package/dist/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/standalone/.next/server/app/api/flows/[id]/route.js +1 -1
- package/dist/standalone/.next/server/app/api/flows/[id]/route.js.nft.json +1 -1
- package/dist/standalone/.next/server/app/api/flows/route.js +1 -1
- package/dist/standalone/.next/server/app/api/flows/route.js.nft.json +1 -1
- package/dist/standalone/.next/server/app/api/inbox/route.js +1 -1
- package/dist/standalone/.next/server/app/api/limits/route.js +1 -1
- package/dist/standalone/.next/server/app/api/log/route.js +1 -1
- package/dist/standalone/.next/server/app/api/proc/route.js +1 -1
- package/dist/standalone/.next/server/app/api/push/route.js +1 -1
- package/dist/standalone/.next/server/app/api/spawn/route.js +1 -1
- package/dist/standalone/.next/server/app/api/tasks/[id]/route.js +1 -0
- package/dist/standalone/.next/server/app/api/tasks/[id]/route.js.nft.json +1 -0
- package/dist/standalone/.next/server/app/api/tasks/[id]/route_client-reference-manifest.js +1 -0
- package/dist/standalone/.next/server/app/api/tasks/[id]/send/route.js +2 -0
- package/dist/standalone/.next/server/app/api/tasks/[id]/send/route.js.nft.json +1 -0
- package/dist/standalone/.next/server/app/api/tasks/[id]/send/route_client-reference-manifest.js +1 -0
- package/dist/standalone/.next/server/app/api/tasks/[id]/spawn/route.js +1 -0
- package/dist/standalone/.next/server/app/api/tasks/[id]/spawn/route.js.nft.json +1 -0
- package/dist/standalone/.next/server/app/api/tasks/[id]/spawn/route_client-reference-manifest.js +1 -0
- package/dist/standalone/.next/server/app/api/tasks/route.js +1 -0
- package/dist/standalone/.next/server/app/api/tasks/route.js.nft.json +1 -0
- package/dist/standalone/.next/server/app/api/tasks/route_client-reference-manifest.js +1 -0
- package/dist/standalone/.next/server/app/api/timeline/route.js +1 -1
- package/dist/standalone/.next/server/app/api/tmux/route.js +2 -2
- package/dist/standalone/.next/server/app/api/transcribe/route.js +1 -1
- package/dist/standalone/.next/server/app/api/transcribe/token/route.js +1 -0
- package/dist/standalone/.next/server/app/api/transcribe/token/route.js.nft.json +1 -0
- package/dist/standalone/.next/server/app/api/transcribe/token/route_client-reference-manifest.js +1 -0
- package/dist/standalone/.next/server/app/favicon.ico/route.js +1 -1
- package/dist/standalone/.next/server/app/index.html +1 -1
- package/dist/standalone/.next/server/app/index.rsc +5 -5
- package/dist/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/standalone/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/dist/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/dist/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/dist/standalone/.next/server/app/page.js +3 -6
- package/dist/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/standalone/.next/server/app-paths-manifest.json +10 -5
- package/dist/standalone/.next/server/chunks/289.js +2 -0
- package/dist/standalone/.next/server/chunks/871.js +2 -3
- package/dist/standalone/.next/server/functions-config-manifest.json +6 -1
- package/dist/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/standalone/.next/server/pages/404.html +1 -1
- package/dist/standalone/.next/server/pages/500.html +1 -1
- package/dist/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/standalone/.next/static/chunks/258-a0398b569708ffc3.js +1 -0
- package/dist/standalone/.next/static/chunks/app/_global-error/page-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/access/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/answer/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/files/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/flows/[id]/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/flows/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/inbox/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/limits/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/log/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/proc/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/push/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/spawn/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/tasks/[id]/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/tasks/[id]/send/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/tasks/[id]/spawn/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/tasks/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/timeline/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/tmux/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/transcribe/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/api/transcribe/token/route-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/app/page-cd0f70a472e144bd.js +2 -0
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f4e3cd62c5cbfd22.js +1 -0
- package/dist/standalone/.next/static/css/3f6c482d54e91e64.css +1 -0
- package/dist/standalone/.next/static/dxjlPz3fKtXT-7BfdVO_q/_buildManifest.js +1 -0
- package/dist/standalone/package.json +4 -2
- package/package.json +4 -2
- package/dist/standalone/.next/static/0YxRBiEgYCSV92_fZOmmC/_buildManifest.js +0 -1
- package/dist/standalone/.next/static/chunks/703-1f675e3bf3e228de.js +0 -1
- package/dist/standalone/.next/static/chunks/app/_global-error/page-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/access/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/answer/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/files/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/flows/[id]/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/flows/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/inbox/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/limits/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/log/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/proc/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/push/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/spawn/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/timeline/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/tmux/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/api/transcribe/route-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/app/page-169f116d08445993.js +0 -5
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/app-error-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/forbidden-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/not-found-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b58291c8001879c8.js +0 -1
- package/dist/standalone/.next/static/css/e3457e19c8be328e.css +0 -1
- /package/dist/standalone/.next/static/{0YxRBiEgYCSV92_fZOmmC → dxjlPz3fKtXT-7BfdVO_q}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
# Live Log Viewer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
live-updating chat feed. It discovers every session,
|
|
5
|
-
job and background shell task on your machine, links
|
|
6
|
-
tree, and tails the selected one in real time.
|
|
3
|
+
`agent-log-viewer` is a local web UI that turns raw Codex / Claude Code agent
|
|
4
|
+
logs into a readable, live-updating chat feed. It discovers every session,
|
|
5
|
+
subagent, Codex companion job and background shell task on your machine, links
|
|
6
|
+
them into a parent→child tree, and tails the selected one in real time.
|
|
7
|
+
|
|
8
|
+
Everything runs locally against files already on disk. There is no database and
|
|
9
|
+
no external service — the app reads `~/.claude` and `~/.codex` and renders what
|
|
10
|
+
it finds.
|
|
11
|
+
|
|
12
|
+

|
|
7
14
|
|
|
8
15
|
## What it shows
|
|
9
16
|
|
|
@@ -24,29 +31,42 @@ tree, and tails the selected one in real time.
|
|
|
24
31
|
- **Parentage tree**: session → subagents → codex jobs → rollouts → background
|
|
25
32
|
tasks, built server-side by scanning transcripts (append-only incremental,
|
|
26
33
|
cached — the warm `/api/files` poll stays around 100 ms).
|
|
27
|
-
- **
|
|
28
|
-
|
|
29
|
-
technical noise collected under a collapsed «⚙ Технічне» group.
|
|
30
|
-
- **Live activity**: content-based badges — a transcript is "працює" while
|
|
31
|
-
mid-turn and "закінчив" once the final assistant message lands.
|
|
34
|
+
- **Live activity**: content-based badges — a transcript reads *working* while
|
|
35
|
+
it is mid-turn and *done* once the final assistant message lands.
|
|
32
36
|
- **Deep links**: every selection is reflected in the URL (`#f=<path>`), so a
|
|
33
37
|
link opens that exact log.
|
|
34
|
-
-
|
|
35
|
-
|
|
38
|
+
- **Project scheme**: each project is a pannable, zoomable diagram — the root
|
|
39
|
+
conversation on top, spawned agents one generation below, arrows colored by
|
|
40
|
+
engine. Quiet branches and tasks collapse under their nearest ancestor.
|
|
41
|
+
- **English or Ukrainian UI**, model chips (`fable-5`, `gpt-5.5`, `sonnet`…),
|
|
42
|
+
collapsible tree with persisted state, follow-mode autoscroll, service-event
|
|
43
|
+
toggle, and a line filter.
|
|
36
44
|
|
|
37
|
-
##
|
|
45
|
+
## Screenshots
|
|
46
|
+
|
|
47
|
+
The session parentage tree, wiring root conversations to their spawned agents:
|
|
48
|
+
|
|
49
|
+

|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
| A Codex CLI session | The overview board |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
|  |  |
|
|
42
54
|
|
|
43
|
-
|
|
55
|
+
## Run
|
|
56
|
+
|
|
57
|
+
The package is published to npm, so the quickstart needs no clone:
|
|
44
58
|
|
|
45
59
|
```bash
|
|
46
60
|
bunx agent-log-viewer
|
|
61
|
+
# or
|
|
62
|
+
npx agent-log-viewer
|
|
47
63
|
```
|
|
48
64
|
|
|
49
|
-
|
|
65
|
+
This starts the server on `127.0.0.1:8898` and opens your browser.
|
|
66
|
+
|
|
67
|
+
### From a local clone
|
|
68
|
+
|
|
69
|
+
With bun:
|
|
50
70
|
|
|
51
71
|
```bash
|
|
52
72
|
bun install
|
|
@@ -55,20 +75,91 @@ bun start --port 8898 --hostname 127.0.0.1
|
|
|
55
75
|
# open http://127.0.0.1:8898/
|
|
56
76
|
```
|
|
57
77
|
|
|
58
|
-
|
|
78
|
+
With npm (note the `--` that forwards flags to the start script):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install
|
|
82
|
+
npm run build
|
|
83
|
+
npm start -- --port 8898 --hostname 127.0.0.1
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`start` serves the output of the last `build`, so run `build` first. For
|
|
87
|
+
development, `bun dev` runs the app with hot reload (it needs a high OS
|
|
88
|
+
file-watch limit for large home directories).
|
|
89
|
+
|
|
90
|
+
**Prerequisites:** Node ≥ 20.9, and bun or npm/pnpm. `tmux` is optional — see
|
|
91
|
+
[Platform support](#platform-support).
|
|
92
|
+
|
|
93
|
+
### CLI options
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
agent-log-viewer [options]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Option | Description |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `-p, --port <n>` | Port for the local server (default `8898`). |
|
|
102
|
+
| `-H, --hostname <h>` | Bind address (default `127.0.0.1`). |
|
|
103
|
+
| `--tailscale` | Expose the viewer inside your tailnet (see below). |
|
|
104
|
+
| `--new-token` | Generate a fresh access key and invalidate old cookies. |
|
|
105
|
+
| `--no-open` | Don't open the browser on start. |
|
|
106
|
+
| `-v, --version` | Print the version. |
|
|
107
|
+
| `-h, --help` | Show usage. |
|
|
59
108
|
|
|
60
109
|
## Platform support
|
|
61
110
|
|
|
62
|
-
Linux is the native target: process discovery reads `/proc` directly. macOS
|
|
63
|
-
|
|
111
|
+
Linux is the native target: process discovery reads `/proc` directly. macOS is
|
|
112
|
+
supported through a portable backend that shells out to `ps` and `lsof`
|
|
64
113
|
instead — same live-process detection, tmux composer targeting, agent
|
|
65
|
-
spawn/kill and background-task discovery, just a bit more subprocess
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
114
|
+
spawn/kill and background-task discovery, just a bit more subprocess overhead
|
|
115
|
+
per scan. The backend is chosen automatically by `process.platform` (see
|
|
116
|
+
`src/lib/proc/`); `VIEWER_PROC_BACKEND=portable` forces the portable path on
|
|
117
|
+
Linux too, for testing.
|
|
69
118
|
|
|
70
|
-
|
|
71
|
-
|
|
119
|
+
The package supports Linux and macOS. The package's `os` field blocks Windows
|
|
120
|
+
installs with `EBADPLATFORM`. WSL works as Linux.
|
|
121
|
+
|
|
122
|
+
`tmux` is optional. Without it, log viewing, the parentage tree, live activity
|
|
123
|
+
and deep links all work; the composer, agent spawn/kill and resume-into-pane
|
|
124
|
+
features need tmux (`brew install tmux` on macOS, or your distro's package on
|
|
125
|
+
Linux).
|
|
126
|
+
|
|
127
|
+
## Language
|
|
128
|
+
|
|
129
|
+
The UI defaults to English and shows a compact EN/UK toggle in the project
|
|
130
|
+
list header. The locale is resolved as `localStorage` key `llv_lang` first,
|
|
131
|
+
then the browser language (Ukrainian if the browser prefers it), then English.
|
|
132
|
+
|
|
133
|
+
CLI messages are English by default, and switch to Ukrainian with
|
|
134
|
+
`LLV_LANG=uk` or a `uk_*` value in `LANG`/`LC_ALL`.
|
|
135
|
+
|
|
136
|
+
## Dictation / voice input
|
|
137
|
+
|
|
138
|
+
Composers that talk to agents have a mic button for dictating messages. By
|
|
139
|
+
default transcription runs fully locally via faster-whisper — no audio leaves
|
|
140
|
+
the machine. Run `scripts/setup-whisper.sh` once to install the local engine.
|
|
141
|
+
|
|
142
|
+
Two cloud backends are available as an explicit per-machine opt-in (never a UI
|
|
143
|
+
toggle): ChatGPT (reuses your local Codex login) and ElevenLabs Scribe (the
|
|
144
|
+
only one with live, streaming transcription). Select with
|
|
145
|
+
`LLV_TRANSCRIBE_BACKEND=local|chatgpt|elevenlabs` or by writing the backend
|
|
146
|
+
name to `~/.config/agent-log-viewer/transcribe-backend`; local is the default.
|
|
147
|
+
|
|
148
|
+
See [docs/transcription.md](docs/transcription.md) for setup, key locations,
|
|
149
|
+
and troubleshooting.
|
|
150
|
+
|
|
151
|
+
## Review loops
|
|
152
|
+
|
|
153
|
+
The viewer orchestrates implement→review cycles: a long-lived implementer
|
|
154
|
+
agent in tmux, a fresh read-only reviewer per round over the full diff,
|
|
155
|
+
automatic relay of findings, and a verdict deck in the scheme view. Start one
|
|
156
|
+
from the **Flow** chip above a conversation pane; presets pair engines and
|
|
157
|
+
reasoning efforts per role (e.g. `Codex high → Fable`).
|
|
158
|
+
|
|
159
|
+
See [docs/review-loop.md](docs/review-loop.md) for the round protocol,
|
|
160
|
+
presets, the HTTP automation API, and troubleshooting. A Claude Code skill
|
|
161
|
+
for driving flows from an agent ships in `.claude/skills/review-loop/` —
|
|
162
|
+
agents working in a clone pick it up automatically.
|
|
72
163
|
|
|
73
164
|
## Tailscale access
|
|
74
165
|
|
|
@@ -76,29 +167,29 @@ and resume-into-pane features are unavailable.
|
|
|
76
167
|
bunx agent-log-viewer --tailscale
|
|
77
168
|
```
|
|
78
169
|
|
|
79
|
-
`--tailscale` starts the local server on `127.0.0.1` and exposes it inside
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
The CLI generates a 32-character access key, appends it to the tailnet URL
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
The terminal prints the tailnet URL along with a QR code to scan with a
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
170
|
+
`--tailscale` starts the local server on `127.0.0.1` and exposes it inside your
|
|
171
|
+
tailnet through a foreground `tailscale serve <port>` process. The public
|
|
172
|
+
internet (Funnel) is never used.
|
|
173
|
+
|
|
174
|
+
The CLI generates a 32-character access key, appends it to the tailnet URL as
|
|
175
|
+
`?k=...`, and after the first visit the server sets an `llv_auth` cookie for 30
|
|
176
|
+
days. `--new-token` generates a fresh key and immediately invalidates every old
|
|
177
|
+
cookie — each request compares the hash against the current token, so a cookie
|
|
178
|
+
minted with a previous key no longer passes.
|
|
179
|
+
|
|
180
|
+
The terminal prints the tailnet URL along with a QR code to scan with a phone.
|
|
181
|
+
The same QR is available inside the web UI: the QR-icon button in the project
|
|
182
|
+
list header opens a popover with the code and the link as text (with a copy
|
|
183
|
+
button). The QR is rendered entirely client-side (the `qrcode` package, no
|
|
184
|
+
external requests) and is served only to already-authorized clients — the same
|
|
185
|
+
token gate from `src/proxy.ts` also protects `/api/access`. When the server
|
|
186
|
+
runs without `--tailscale`, the button shows a hint to start
|
|
187
|
+
`bunx agent-log-viewer --tailscale`.
|
|
97
188
|
|
|
98
189
|
Anyone with tailnet access to this URL can read all agent transcripts,
|
|
99
|
-
including any sensitive data that landed in a session, and can execute
|
|
100
|
-
|
|
101
|
-
|
|
190
|
+
including any sensitive data that landed in a session, and can execute commands
|
|
191
|
+
through `/api/tmux` and `/api/spawn`. Treat the tailnet URL as a secret — do
|
|
192
|
+
not forward it to anyone else.
|
|
102
193
|
|
|
103
194
|
## Security model
|
|
104
195
|
|
|
@@ -111,12 +202,41 @@ inside the tailnet via `tailscale serve` and guarded by the token gate in
|
|
|
111
202
|
`src/proxy.ts`. Non-loopback binds also force token mode. Treat any URL
|
|
112
203
|
containing `?k=` as a credential.
|
|
113
204
|
|
|
205
|
+
## Environment variables
|
|
206
|
+
|
|
207
|
+
All optional. Transcription variables are documented in full in
|
|
208
|
+
[docs/transcription.md](docs/transcription.md).
|
|
209
|
+
|
|
210
|
+
| Variable | Effect |
|
|
211
|
+
| --- | --- |
|
|
212
|
+
| `VIEWER_PROC_BACKEND` | `portable` or `linux` — force the process-discovery backend (auto-selected by default). |
|
|
213
|
+
| `LLV_LANG` | `uk` or `en` — force the CLI message language. |
|
|
214
|
+
| `LLV_TRANSCRIBE_BACKEND` | `local`, `chatgpt`, or `elevenlabs` — pick the dictation backend (default `local`). |
|
|
215
|
+
| `LLV_WHISPER_MODEL` | faster-whisper model size (default `small`). |
|
|
216
|
+
| `LLV_WHISPER_DEVICE` | `cpu` (default) or `cuda`. |
|
|
217
|
+
| `LLV_WHISPER_VENV` | Path to the whisper virtualenv (default `~/.cache/agent-log-viewer/whisper-venv`). |
|
|
218
|
+
| `LLV_ELEVENLABS_STT_MODEL` | ElevenLabs batch model override. |
|
|
219
|
+
| `ELEVENLABS_API_KEY` | ElevenLabs API key for the ElevenLabs backend. |
|
|
220
|
+
|
|
221
|
+
## Config paths
|
|
222
|
+
|
|
223
|
+
The viewer keeps its state under the standard XDG directories, named after the
|
|
224
|
+
package:
|
|
225
|
+
|
|
226
|
+
- `~/.config/agent-log-viewer/` — access `token`, `transcribe-backend`, and
|
|
227
|
+
`elevenlabs-api-key`.
|
|
228
|
+
- `~/.cache/agent-log-viewer/whisper-venv` — the local transcription
|
|
229
|
+
virtualenv.
|
|
230
|
+
|
|
231
|
+
Legacy `live-log-viewer` paths are still honored as read-only fallbacks, so
|
|
232
|
+
existing setups keep working without changes.
|
|
233
|
+
|
|
114
234
|
## Architecture
|
|
115
235
|
|
|
116
|
-
See [ARCHITECTURE.md](ARCHITECTURE.md): route handlers under
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
`
|
|
236
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md): route handlers under `src/app/api/*`, a
|
|
237
|
+
pure scanner pipeline under `src/lib/scanner/*` (discover → describe → activity
|
|
238
|
+
→ model → links), React components under `src/components/*`. Caches live on
|
|
239
|
+
`globalThis` and survive dev hot-reload.
|
|
120
240
|
|
|
121
241
|
## License
|
|
122
242
|
|
package/bin/cli.mjs
CHANGED
|
@@ -16,8 +16,50 @@ const READINESS_INTERVAL_MS = 200;
|
|
|
16
16
|
const cliPath = fileURLToPath(import.meta.url);
|
|
17
17
|
const cliDir = dirname(cliPath);
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/* Dependency-free CLI localization: English by default, Ukrainian when
|
|
20
|
+
LLV_LANG=uk or the locale (LC_ALL/LANG) is a uk_* / uk.* variant. */
|
|
21
|
+
function detectLang() {
|
|
22
|
+
const explicit = (process.env.LLV_LANG || "").toLowerCase();
|
|
23
|
+
if (explicit === "uk" || explicit === "en") return explicit;
|
|
24
|
+
const loc = (process.env.LC_ALL || process.env.LANG || "").toLowerCase();
|
|
25
|
+
return loc === "uk" || loc.startsWith("uk_") || loc.startsWith("uk.") ? "uk" : "en";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const LANG = detectLang();
|
|
29
|
+
|
|
30
|
+
const MESSAGES = {
|
|
31
|
+
en: {
|
|
32
|
+
usage: () => `Usage: agent-log-viewer [options]
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
-p, --port <n> Port for the local server (default ${DEFAULT_PORT})
|
|
36
|
+
-H, --hostname <h> Bind address (default ${DEFAULT_HOSTNAME})
|
|
37
|
+
--tailscale Access over Tailscale
|
|
38
|
+
--no-open Don't open the browser
|
|
39
|
+
--new-token Create a new access key
|
|
40
|
+
-v, --version Show the version
|
|
41
|
+
-h, --help Show this help`,
|
|
42
|
+
badPort: (value) => `Invalid port: ${value}`,
|
|
43
|
+
flagNeedsValue: (flag) => `Option ${flag} requires a value.`,
|
|
44
|
+
hostnameNeedsValue: () => "Option --hostname requires a value.",
|
|
45
|
+
unknownOption: (arg) => `Unknown option: ${arg}`,
|
|
46
|
+
noPackageJson: () => "Couldn't find package.json for agent-log-viewer.",
|
|
47
|
+
readPackageJsonErr: (detail) => `Couldn't read package.json: ${detail}`,
|
|
48
|
+
readPackageJsonErrGeneric: () => "Couldn't read package.json.",
|
|
49
|
+
noServer: () => "No standalone server.js or local next found.",
|
|
50
|
+
portBusy: (port) => `Port ${port} is busy. Try: bunx agent-log-viewer --port ${port + 1}`,
|
|
51
|
+
serverStartFail: (detail) => `Couldn't start the server: ${detail}`,
|
|
52
|
+
serverTimeout: (seconds) => `The server didn't respond within ${seconds} seconds.`,
|
|
53
|
+
bannerOpened: (url) => ` Opened: ${url}`,
|
|
54
|
+
bannerReads: () => " Reads logs from ~/.claude/projects, ~/.codex/sessions.",
|
|
55
|
+
bannerStop: () => " Ctrl+C — stop. --tailscale — access from your phone.",
|
|
56
|
+
tsLinkWarn: () => " The link contains an access key — don't forward it to others.",
|
|
57
|
+
tsCookie: () => " After the first open the key is stored in a cookie for 30 days.",
|
|
58
|
+
nonLocalWarn: () => "Warning: a non-local address exposes the viewer to the network, so access-key mode is forced on.",
|
|
59
|
+
serverNotReady: () => "Server not ready.",
|
|
60
|
+
},
|
|
61
|
+
uk: {
|
|
62
|
+
usage: () => `Використання: agent-log-viewer [опції]
|
|
21
63
|
|
|
22
64
|
Опції:
|
|
23
65
|
-p, --port <n> Порт для локального сервера (типово ${DEFAULT_PORT})
|
|
@@ -26,7 +68,32 @@ function usage() {
|
|
|
26
68
|
--no-open Не відкривати браузер
|
|
27
69
|
--new-token Створити новий ключ доступу
|
|
28
70
|
-v, --version Показати версію
|
|
29
|
-
-h, --help Показати
|
|
71
|
+
-h, --help Показати довідку`,
|
|
72
|
+
badPort: (value) => `Некоректний порт: ${value}`,
|
|
73
|
+
flagNeedsValue: (flag) => `Опція ${flag} потребує значення.`,
|
|
74
|
+
hostnameNeedsValue: () => "Опція --hostname потребує значення.",
|
|
75
|
+
unknownOption: (arg) => `Невідома опція: ${arg}`,
|
|
76
|
+
noPackageJson: () => "Не вдалося знайти package.json для agent-log-viewer.",
|
|
77
|
+
readPackageJsonErr: (detail) => `Не вдалося прочитати package.json: ${detail}`,
|
|
78
|
+
readPackageJsonErrGeneric: () => "Не вдалося прочитати package.json.",
|
|
79
|
+
noServer: () => "Не знайдено standalone server.js або локальний next.",
|
|
80
|
+
portBusy: (port) => `Порт ${port} зайнятий. Спробуйте: bunx agent-log-viewer --port ${port + 1}`,
|
|
81
|
+
serverStartFail: (detail) => `Не вдалося запустити сервер: ${detail}`,
|
|
82
|
+
serverTimeout: (seconds) => `Сервер не відповів за ${seconds} секунд.`,
|
|
83
|
+
bannerOpened: (url) => ` Відкрито: ${url}`,
|
|
84
|
+
bannerReads: () => " Читає логи з ~/.claude/projects, ~/.codex/sessions.",
|
|
85
|
+
bannerStop: () => " Ctrl+C — зупинити. --tailscale — доступ з телефона.",
|
|
86
|
+
tsLinkWarn: () => " Посилання містить ключ доступу — не пересилайте його стороннім.",
|
|
87
|
+
tsCookie: () => " Після першого відкриття ключ зберігається у cookie на 30 днів.",
|
|
88
|
+
nonLocalWarn: () => "Увага: нелокальна адреса відкриває viewer для мережі, тому режим ключа доступу увімкнено примусово.",
|
|
89
|
+
serverNotReady: () => "Сервер не готовий.",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const m = MESSAGES[LANG];
|
|
94
|
+
|
|
95
|
+
function usage() {
|
|
96
|
+
return m.usage();
|
|
30
97
|
}
|
|
31
98
|
|
|
32
99
|
function fail(message) {
|
|
@@ -37,7 +104,7 @@ function fail(message) {
|
|
|
37
104
|
function parsePort(value) {
|
|
38
105
|
const port = Number(value);
|
|
39
106
|
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
40
|
-
fail(
|
|
107
|
+
fail(m.badPort(value));
|
|
41
108
|
}
|
|
42
109
|
return port;
|
|
43
110
|
}
|
|
@@ -45,7 +112,7 @@ function parsePort(value) {
|
|
|
45
112
|
function requireValue(args, index, flag) {
|
|
46
113
|
const value = args[index + 1];
|
|
47
114
|
if (!value || value.startsWith("-")) {
|
|
48
|
-
fail(
|
|
115
|
+
fail(m.flagNeedsValue(flag));
|
|
49
116
|
}
|
|
50
117
|
return value;
|
|
51
118
|
}
|
|
@@ -76,7 +143,7 @@ function parseArgs(args) {
|
|
|
76
143
|
} else if (arg.startsWith("--hostname=")) {
|
|
77
144
|
const value = arg.slice("--hostname=".length);
|
|
78
145
|
if (!value) {
|
|
79
|
-
fail(
|
|
146
|
+
fail(m.hostnameNeedsValue());
|
|
80
147
|
}
|
|
81
148
|
options.hostname = value;
|
|
82
149
|
} else if (arg === "--tailscale") {
|
|
@@ -90,7 +157,7 @@ function parseArgs(args) {
|
|
|
90
157
|
} else if (arg === "-h" || arg === "--help") {
|
|
91
158
|
options.help = true;
|
|
92
159
|
} else {
|
|
93
|
-
fail(
|
|
160
|
+
fail(m.unknownOption(arg));
|
|
94
161
|
}
|
|
95
162
|
}
|
|
96
163
|
|
|
@@ -112,7 +179,7 @@ function findPackageRoot(startDir) {
|
|
|
112
179
|
|
|
113
180
|
const parentDir = dirname(currentDir);
|
|
114
181
|
if (parentDir === currentDir) {
|
|
115
|
-
fail(
|
|
182
|
+
fail(m.noPackageJson());
|
|
116
183
|
}
|
|
117
184
|
currentDir = parentDir;
|
|
118
185
|
}
|
|
@@ -125,8 +192,8 @@ function readPackageJson(packageRoot) {
|
|
|
125
192
|
} catch (error) {
|
|
126
193
|
fail(
|
|
127
194
|
error instanceof Error
|
|
128
|
-
?
|
|
129
|
-
:
|
|
195
|
+
? m.readPackageJsonErr(error.message)
|
|
196
|
+
: m.readPackageJsonErrGeneric(),
|
|
130
197
|
);
|
|
131
198
|
}
|
|
132
199
|
}
|
|
@@ -154,7 +221,7 @@ function resolveServer(packageRoot) {
|
|
|
154
221
|
|
|
155
222
|
const nextBin = join(packageRoot, "node_modules", ".bin", "next");
|
|
156
223
|
if (!existsSync(nextBin)) {
|
|
157
|
-
fail(
|
|
224
|
+
fail(m.noServer());
|
|
158
225
|
}
|
|
159
226
|
|
|
160
227
|
return {
|
|
@@ -204,7 +271,7 @@ function startServer(server, options, runtime, tailscaleProcessRef) {
|
|
|
204
271
|
const text = chunk.toString("utf8");
|
|
205
272
|
if (text.includes("EADDRINUSE")) {
|
|
206
273
|
state.sawAddressInUse = true;
|
|
207
|
-
console.error(
|
|
274
|
+
console.error(m.portBusy(options.port));
|
|
208
275
|
if (!child.killed) {
|
|
209
276
|
child.kill("SIGTERM");
|
|
210
277
|
}
|
|
@@ -215,7 +282,7 @@ function startServer(server, options, runtime, tailscaleProcessRef) {
|
|
|
215
282
|
});
|
|
216
283
|
|
|
217
284
|
child.on("error", (error) => {
|
|
218
|
-
fail(
|
|
285
|
+
fail(m.serverStartFail(error.message));
|
|
219
286
|
});
|
|
220
287
|
|
|
221
288
|
child.on("exit", async (code, signal) => {
|
|
@@ -285,7 +352,7 @@ async function waitForReadiness(port) {
|
|
|
285
352
|
await wait(READINESS_INTERVAL_MS);
|
|
286
353
|
}
|
|
287
354
|
|
|
288
|
-
throw new Error(
|
|
355
|
+
throw new Error(m.serverTimeout(READINESS_TIMEOUT_MS / 1000));
|
|
289
356
|
}
|
|
290
357
|
|
|
291
358
|
function localUrl(options) {
|
|
@@ -295,9 +362,9 @@ function localUrl(options) {
|
|
|
295
362
|
|
|
296
363
|
function printBanner(version, options) {
|
|
297
364
|
console.log(` ✳ Live Log Viewer v${version}`);
|
|
298
|
-
console.log(
|
|
299
|
-
console.log(
|
|
300
|
-
console.log(
|
|
365
|
+
console.log(m.bannerOpened(localUrl(options)));
|
|
366
|
+
console.log(m.bannerReads());
|
|
367
|
+
console.log(m.bannerStop());
|
|
301
368
|
}
|
|
302
369
|
|
|
303
370
|
async function printTailscaleBanner(runtime) {
|
|
@@ -314,8 +381,8 @@ async function printTailscaleBanner(runtime) {
|
|
|
314
381
|
resolve();
|
|
315
382
|
});
|
|
316
383
|
});
|
|
317
|
-
console.log(
|
|
318
|
-
console.log(
|
|
384
|
+
console.log(m.tsLinkWarn());
|
|
385
|
+
console.log(m.tsCookie());
|
|
319
386
|
}
|
|
320
387
|
|
|
321
388
|
function openBrowser(url) {
|
|
@@ -386,7 +453,7 @@ async function prepareRuntime(options) {
|
|
|
386
453
|
|
|
387
454
|
const nonLoopbackBind = !isLoopbackHostname(options.hostname);
|
|
388
455
|
if (nonLoopbackBind) {
|
|
389
|
-
console.error(
|
|
456
|
+
console.error(m.nonLocalWarn());
|
|
390
457
|
}
|
|
391
458
|
|
|
392
459
|
if (options.tailscale) {
|
|
@@ -436,7 +503,7 @@ async function main() {
|
|
|
436
503
|
}
|
|
437
504
|
|
|
438
505
|
if (await portAlreadyResponds(options.port)) {
|
|
439
|
-
console.error(
|
|
506
|
+
console.error(m.portBusy(options.port));
|
|
440
507
|
process.exit(1);
|
|
441
508
|
}
|
|
442
509
|
|
|
@@ -453,7 +520,7 @@ async function main() {
|
|
|
453
520
|
await waitForReadiness(options.port);
|
|
454
521
|
} catch (error) {
|
|
455
522
|
await stopAll(serverProcess, tailscaleProcessRef.current);
|
|
456
|
-
fail(error instanceof Error ? error.message :
|
|
523
|
+
fail(error instanceof Error ? error.message : m.serverNotReady());
|
|
457
524
|
}
|
|
458
525
|
|
|
459
526
|
if (
|