claude-code-watch 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +110 -0
- package/README.zh-CN.md +30 -0
- package/bin/claude-watch.js +187 -0
- package/package.json +38 -0
- package/public/index.html +1206 -0
- package/src/parser/parser.js +528 -0
- package/src/server/server.js +375 -0
- package/src/watcher/watcher.js +1130 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 phiat (https://github.com/phiat/claude-esp)
|
|
4
|
+
Copyright (c) 2026 shuxuecode
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# claude-watch
|
|
2
|
+
|
|
3
|
+
> Stream Claude Code's hidden output (thinking, tool calls, subagents) to a web browser in real-time.
|
|
4
|
+
|
|
5
|
+
Claude Code writes detailed JSONL logs under `~/.claude/projects/` as it works — including thinking blocks, tool inputs/outputs, subagent activity, and token usage. `claude-watch` tails those logs and streams everything to a local web dashboard, so you can see exactly what Claude Code is doing under the hood.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Real-time streaming** — thinking, tool calls, tool results, and text responses appear as they happen
|
|
12
|
+
- **Multi-session** — watch all active Claude Code sessions simultaneously in a tree view
|
|
13
|
+
- **Subagent tracking** — see subagent activity nested under their parent session
|
|
14
|
+
- **Token & cost visibility** — tracks input/output/cache tokens per agent, with context window utilization
|
|
15
|
+
- **Filter controls** — toggle thinking, tool input, tool output, and text visibility independently
|
|
16
|
+
- **Auto-discovery** — automatically picks up new sessions as they start (toggleable)
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx claude-watch
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This starts the dashboard at `http://localhost:23000` and opens it in your browser.
|
|
25
|
+
|
|
26
|
+
It will auto-discover active Claude Code sessions from `~/.claude/projects/` and start streaming immediately.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g claude-watch
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then run:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
claude-watch
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
claude-watch [OPTIONS]
|
|
44
|
+
|
|
45
|
+
OPTIONS:
|
|
46
|
+
-p, --port <port> HTTP port (default: 23000)
|
|
47
|
+
-h, --host <host> Bind host (default: 127.0.0.1)
|
|
48
|
+
-s <ID> Watch a specific session by ID
|
|
49
|
+
-n Start from newest (skip history, live only)
|
|
50
|
+
-l [N] List recent sessions (default 10) and exit
|
|
51
|
+
-a [N] List active sessions (default all) and exit
|
|
52
|
+
-w <dur> Active window duration (default 5m, e.g. 30s, 2m, 10m)
|
|
53
|
+
-m <N> Max sessions to show in tree (default 0=unlimited)
|
|
54
|
+
-c <dur> Auto-collapse sessions inactive for this duration (e.g. 2m)
|
|
55
|
+
-D Debug: show raw type:subtype for every JSONL line we'd drop
|
|
56
|
+
--poll <ms> Polling interval in milliseconds (default: 500)
|
|
57
|
+
-v Show version
|
|
58
|
+
--help Show this help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Examples
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# List recent sessions
|
|
65
|
+
claude-watch -l
|
|
66
|
+
|
|
67
|
+
# List active sessions from last 10 minutes
|
|
68
|
+
claude-watch -a -w 10m
|
|
69
|
+
|
|
70
|
+
# Watch a specific session
|
|
71
|
+
claude-watch -s abc123-def456
|
|
72
|
+
|
|
73
|
+
# Live-only mode (don't replay history)
|
|
74
|
+
claude-watch -n
|
|
75
|
+
|
|
76
|
+
# Custom port and host
|
|
77
|
+
claude-watch -p 8080 -h 0.0.0.0
|
|
78
|
+
|
|
79
|
+
# Limit tree to 5 most recent sessions, auto-collapse after 2m of inactivity
|
|
80
|
+
claude-watch -m 5 -c 2m
|
|
81
|
+
|
|
82
|
+
# Debug mode: see every unknown JSONL line type
|
|
83
|
+
claude-watch -D
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## How It Works
|
|
87
|
+
|
|
88
|
+
`claude-watch` monitors the Claude Code project directory (`~/.claude/projects/`) for JSONL log files. Each Claude Code session writes structured JSON lines containing:
|
|
89
|
+
|
|
90
|
+
- `assistant` messages — thinking blocks, text responses, and tool use requests
|
|
91
|
+
- `user` messages — tool results and user prompts
|
|
92
|
+
- `system` messages — turn duration markers, compaction boundaries
|
|
93
|
+
- `attachment` messages — hook outputs and diagnostics
|
|
94
|
+
- Agent metadata — session titles, subagent type info
|
|
95
|
+
|
|
96
|
+
The watcher tails these files (via chokidar fsnotify events, with polling fallback), parses each line into structured stream items, and pushes them to the browser over WebSocket. The browser renders them in a terminal-style dashboard with filtering, tree navigation, and token tracking.
|
|
97
|
+
|
|
98
|
+
## Environment
|
|
99
|
+
|
|
100
|
+
| Variable | Description |
|
|
101
|
+
|----------|-------------|
|
|
102
|
+
| `CLAUDE_HOME` | Override Claude config directory (default: `~/.claude`) |
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
107
|
+
|
|
108
|
+
## Acknowledgments
|
|
109
|
+
|
|
110
|
+
This project was inspired by and developed based on [claude-esp](https://github.com/phiat/claude-esp) by phiat.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# claude-watch
|
|
2
|
+
|
|
3
|
+
claude-watch — 一个 Claude Code 的实时 Web 监控仪表盘。
|
|
4
|
+
|
|
5
|
+
## 核心作用
|
|
6
|
+
|
|
7
|
+
Claude Code 在运行时会将详细的 JSONL 日志写入 `~/.claude/projects/` 目录,包括思考内容、工具调用、子代理活动、token 使用量等。这些信息在 Claude Code 的正常界面中并不全部可见。`claude-watch` 的作用就是**读取这些隐藏日志,实时流式传输到本地 Web 仪表盘**,让你能看到 Claude Code 的"幕后"工作细节。
|
|
8
|
+
|
|
9
|
+
## 架构组成
|
|
10
|
+
|
|
11
|
+
项目由三个核心模块构成:
|
|
12
|
+
|
|
13
|
+
1. **`src/parser/parser.js`** — JSONL 日志解析器。将 Claude Code 的 JSONL 行解析为结构化的流项目(thinking、tool_input、tool_output、text、turn_marker、hook_output 等),并提取 token 使用量和模型信息。
|
|
14
|
+
|
|
15
|
+
2. **`src/watcher/watcher.js`** — 文件监视器。使用 chokidar 监听 `~/.claude/projects/` 下的 JSONL 文件变化(带轮询 fallback),管理多会话和子代理的发现与跟踪,增量读取文件新增内容并触发解析。
|
|
16
|
+
|
|
17
|
+
3. **`src/server/server.js`** — HTTP + WebSocket 服务器。提供静态页面服务、REST API(会话列表、状态、上下文信息)和 WebSocket 实时推送,将解析后的内容广播到浏览器客户端。启动时自动打开浏览器。
|
|
18
|
+
|
|
19
|
+
## 主要功能
|
|
20
|
+
|
|
21
|
+
- **实时流式传输** — 思考过程、工具调用/结果、文本响应实时呈现
|
|
22
|
+
- **多会话监视** — 同时查看所有活跃的 Claude Code 会话
|
|
23
|
+
- **子代理追踪** — 在父会话下嵌套显示子代理活动
|
|
24
|
+
- **Token/成本追踪** — 每个代理的输入/输出/缓存 token 及上下文窗口利用率
|
|
25
|
+
- **过滤控制** — 独立切换 thinking、工具输入/输出、文本的可见性
|
|
26
|
+
- **自动发现** — 新会话启动时自动纳入监控
|
|
27
|
+
|
|
28
|
+
## 致谢
|
|
29
|
+
|
|
30
|
+
本项目基于 [phiat](https://github.com/phiat) 的 [claude-esp](https://github.com/phiat/claude-esp) 项目提供思路并开发。
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { startServer } = require('../src/server/server');
|
|
6
|
+
const { listSessions, listActiveSessions } = require('../src/watcher/watcher');
|
|
7
|
+
|
|
8
|
+
const VERSION = '0.0.1';
|
|
9
|
+
|
|
10
|
+
function printHelp() {
|
|
11
|
+
console.log(`claude-watch v${VERSION}
|
|
12
|
+
|
|
13
|
+
Stream Claude Code's hidden output (thinking, tool calls, subagents)
|
|
14
|
+
to a web browser.
|
|
15
|
+
|
|
16
|
+
USAGE:
|
|
17
|
+
claude-watch [OPTIONS]
|
|
18
|
+
|
|
19
|
+
OPTIONS:
|
|
20
|
+
-p, --port <port> HTTP port (default: 23000)
|
|
21
|
+
-h, --host <host> Bind host (default: 127.0.0.1)
|
|
22
|
+
-s <ID> Watch a specific session by ID
|
|
23
|
+
-n Start from newest (skip history, live only)
|
|
24
|
+
-l [N] List recent sessions (default 10) and exit
|
|
25
|
+
-a [N] List active sessions (default all) and exit
|
|
26
|
+
-w <dur> Active window duration (default 5m, e.g. 30s, 2m, 10m)
|
|
27
|
+
-m <N> Max sessions to show in tree (default 0=unlimited)
|
|
28
|
+
-c <dur> Auto-collapse sessions inactive for this duration (e.g. 2m)
|
|
29
|
+
-D Debug: show raw type:subtype for every JSONL line we'd drop
|
|
30
|
+
--poll <ms> Polling interval in milliseconds (default: 500)
|
|
31
|
+
-v Show version
|
|
32
|
+
--help Show this help
|
|
33
|
+
|
|
34
|
+
ENVIRONMENT:
|
|
35
|
+
CLAUDE_HOME Override Claude config directory (default: ~/.claude)
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseDuration(s) {
|
|
40
|
+
const match = s.match(/^(\d+)(ms|s|m|h)$/);
|
|
41
|
+
if (!match) throw new Error(`Invalid duration: ${s}`);
|
|
42
|
+
const val = parseInt(match[1], 10);
|
|
43
|
+
switch (match[2]) {
|
|
44
|
+
case 'ms': return val;
|
|
45
|
+
case 's': return val * 1000;
|
|
46
|
+
case 'm': return val * 60 * 1000;
|
|
47
|
+
case 'h': return val * 3600 * 1000;
|
|
48
|
+
default: throw new Error(`Invalid duration unit: ${match[2]}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
|
|
55
|
+
const options = {
|
|
56
|
+
port: 23000,
|
|
57
|
+
host: '127.0.0.1',
|
|
58
|
+
sessionID: '',
|
|
59
|
+
skipHistory: false,
|
|
60
|
+
pollMs: 500,
|
|
61
|
+
activeWindow: 5 * 60 * 1000,
|
|
62
|
+
maxSessions: 0,
|
|
63
|
+
collapseAfter: 0,
|
|
64
|
+
debugAll: false,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// First pass: collect all option values
|
|
68
|
+
for (let i = 0; i < args.length; i++) {
|
|
69
|
+
switch (args[i]) {
|
|
70
|
+
case '-s':
|
|
71
|
+
options.sessionID = args[++i] || '';
|
|
72
|
+
break;
|
|
73
|
+
case '-n':
|
|
74
|
+
options.skipHistory = true;
|
|
75
|
+
break;
|
|
76
|
+
case '-p':
|
|
77
|
+
case '--port':
|
|
78
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
79
|
+
console.error(`Error: ${args[i]} requires a port number`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
const pv = parseInt(args[++i], 10);
|
|
83
|
+
if (isNaN(pv)) {
|
|
84
|
+
console.error(`Error: ${args[i - 1]} requires a numeric port, got '${args[i]}'`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
options.port = pv;
|
|
88
|
+
break;
|
|
89
|
+
case '-h':
|
|
90
|
+
case '--host':
|
|
91
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('-')) {
|
|
92
|
+
console.error(`Error: ${args[i]} requires a host address`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
options.host = args[++i];
|
|
96
|
+
break;
|
|
97
|
+
case '-w':
|
|
98
|
+
try {
|
|
99
|
+
options.activeWindow = parseDuration(args[++i] || '5m');
|
|
100
|
+
} catch {
|
|
101
|
+
options.activeWindow = 5 * 60 * 1000;
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case '-c':
|
|
105
|
+
try {
|
|
106
|
+
options.collapseAfter = parseDuration(args[++i] || '5m');
|
|
107
|
+
} catch {
|
|
108
|
+
options.collapseAfter = 5 * 60 * 1000;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case '-m':
|
|
112
|
+
options.maxSessions = parseInt(args[++i], 10) || 0;
|
|
113
|
+
break;
|
|
114
|
+
case '-D':
|
|
115
|
+
options.debugAll = true;
|
|
116
|
+
break;
|
|
117
|
+
case '--poll':
|
|
118
|
+
options.pollMs = parseInt(args[++i], 10) || 500;
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Second pass: execute action flags with fully resolved options
|
|
126
|
+
for (let i = 0; i < args.length; i++) {
|
|
127
|
+
switch (args[i]) {
|
|
128
|
+
case '-l': {
|
|
129
|
+
const v = parseInt(args[i + 1]);
|
|
130
|
+
const limit = !isNaN(v) ? v : 10;
|
|
131
|
+
if (!isNaN(v)) i++;
|
|
132
|
+
const sessions = await listSessions(limit);
|
|
133
|
+
if (sessions.length === 0) {
|
|
134
|
+
console.log('No sessions found.');
|
|
135
|
+
} else {
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
for (const s of sessions) {
|
|
138
|
+
const age = Math.round((now - new Date(s.modified).getTime()) / 1000);
|
|
139
|
+
const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
|
|
140
|
+
const active = s.isActive ? '●' : '○';
|
|
141
|
+
const id = s.id.length > 40 ? s.id.slice(0, 37) + '...' : s.id;
|
|
142
|
+
console.log(`${active} ${id} ${s.projectPath || '?'} ${ageStr}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
case '-a': {
|
|
148
|
+
const v = parseInt(args[i + 1]);
|
|
149
|
+
const limit = !isNaN(v) ? v : 0;
|
|
150
|
+
if (!isNaN(v)) i++;
|
|
151
|
+
const sessions = await listActiveSessions(options.activeWindow);
|
|
152
|
+
const result = limit > 0 ? sessions.slice(0, limit) : sessions;
|
|
153
|
+
if (result.length === 0) {
|
|
154
|
+
console.log('No active sessions found.');
|
|
155
|
+
} else {
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
for (const s of result) {
|
|
158
|
+
const age = Math.round((now - new Date(s.modified).getTime()) / 1000);
|
|
159
|
+
const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
|
|
160
|
+
const id = s.id.length > 40 ? s.id.slice(0, 37) + '...' : s.id;
|
|
161
|
+
console.log(`● ${id} ${s.projectPath || '?'} ${ageStr}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
case '-v':
|
|
167
|
+
console.log(`claude-watch v${VERSION}`);
|
|
168
|
+
return;
|
|
169
|
+
case '--help':
|
|
170
|
+
printHelp();
|
|
171
|
+
return;
|
|
172
|
+
default:
|
|
173
|
+
if (args[i].startsWith('-')) {
|
|
174
|
+
console.error(`Unknown option: ${args[i]}`);
|
|
175
|
+
printHelp();
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
startServer(options);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
main().catch(err => {
|
|
185
|
+
console.error(`Fatal error: ${err.message}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code-watch",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Web-based real-time monitor for Claude Code.",
|
|
5
|
+
"main": "./src/server/server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-watch": "./bin/claude-watch.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/claude-watch.js",
|
|
11
|
+
"dev": "node --watch bin/claude-watch.js",
|
|
12
|
+
"test": "node --test tests/all.test.js tests/watcher.test.js tests/server.test.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"src",
|
|
17
|
+
"public"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"claude",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"watcher",
|
|
23
|
+
"web",
|
|
24
|
+
"dashboard"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/shuxuecode/claude-watch"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"chokidar": "^4.0.3",
|
|
36
|
+
"ws": "^8.20.0"
|
|
37
|
+
}
|
|
38
|
+
}
|