claude-code-monitor 1.0.4 → 1.1.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/CHANGELOG.md +28 -0
- package/README.md +98 -103
- package/dist/bin/ccm.js +38 -16
- package/dist/components/Dashboard.d.ts +6 -1
- package/dist/components/Dashboard.d.ts.map +1 -1
- package/dist/components/Dashboard.js +39 -5
- package/dist/components/SessionCard.d.ts.map +1 -1
- package/dist/components/SessionCard.js +2 -4
- package/dist/components/Spinner.js +1 -1
- package/dist/constants.d.ts +5 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +5 -2
- package/dist/hook/handler.d.ts.map +1 -1
- package/dist/hook/handler.js +16 -15
- package/dist/hooks/useServer.d.ts +10 -0
- package/dist/hooks/useServer.d.ts.map +1 -0
- package/dist/hooks/useServer.js +39 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +309 -0
- package/dist/store/file-store.d.ts +5 -0
- package/dist/store/file-store.d.ts.map +1 -1
- package/dist/store/file-store.js +40 -7
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/applescript.d.ts +7 -0
- package/dist/utils/applescript.d.ts.map +1 -0
- package/dist/utils/applescript.js +18 -0
- package/dist/utils/focus.d.ts +0 -1
- package/dist/utils/focus.d.ts.map +1 -1
- package/dist/utils/focus.js +16 -22
- package/dist/utils/send-text.d.ts +40 -0
- package/dist/utils/send-text.d.ts.map +1 -0
- package/dist/utils/send-text.js +324 -0
- package/dist/utils/status.js +2 -2
- package/dist/utils/stdin.d.ts +6 -0
- package/dist/utils/stdin.d.ts.map +1 -0
- package/dist/utils/stdin.js +12 -0
- package/dist/utils/terminal-strategy.d.ts +18 -0
- package/dist/utils/terminal-strategy.d.ts.map +1 -0
- package/dist/utils/terminal-strategy.js +15 -0
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +1 -3
- package/dist/utils/transcript.d.ts +10 -0
- package/dist/utils/transcript.d.ts.map +1 -0
- package/dist/utils/transcript.js +45 -0
- package/dist/utils/tty-cache.d.ts +0 -5
- package/dist/utils/tty-cache.d.ts.map +1 -1
- package/dist/utils/tty-cache.js +0 -7
- package/package.json +6 -2
- package/public/index.html +1219 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.0] - 2026-01-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Mobile Web Interface** - Monitor and control sessions from your smartphone
|
|
13
|
+
- Real-time session status via WebSocket
|
|
14
|
+
- View latest Claude messages with markdown rendering
|
|
15
|
+
- Focus terminal sessions remotely
|
|
16
|
+
- Send text messages to terminal (multi-line supported)
|
|
17
|
+
- Permission prompt responses (y/n/a) and Ctrl+C support
|
|
18
|
+
- Bottom sheet modal with swipe-to-close gesture
|
|
19
|
+
- New command: `ccm serve` - Start mobile web server only
|
|
20
|
+
- QR code display in terminal UI (press `h` to toggle)
|
|
21
|
+
- Token-based authentication for mobile access
|
|
22
|
+
- Auto-select available port when default port (3456) is in use
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Redesigned README with demo GIFs for both Terminal UI and Mobile Web
|
|
27
|
+
- Consolidated terminal fallback strategy for better code maintainability
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
|
|
31
|
+
- Mobile Web requires same Wi-Fi network (local network only)
|
|
32
|
+
- Unique token generated per session for authentication
|
|
33
|
+
- Warning messages about not sharing the access URL
|
|
34
|
+
- Dangerous command detection in mobile input
|
|
35
|
+
|
|
8
36
|
## [1.0.4] - 2026-01-18
|
|
9
37
|
|
|
10
38
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,167 +1,144 @@
|
|
|
1
|
-
# Claude Code Monitor
|
|
1
|
+
# Claude Code Monitor
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/claude-code-monitor)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://www.apple.com/macos/)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Monitor multiple Claude Code sessions in real-time from your terminal or smartphone.**
|
|
8
|
+
|
|
9
|
+
### Terminal UI
|
|
10
|
+
Monitor sessions with keyboard navigation
|
|
8
11
|
|
|
9
12
|
<p align="center">
|
|
10
|
-
<img src="https://raw.githubusercontent.com/onikan27/claude-code-monitor/main/docs/ccm-demo.gif" alt="
|
|
13
|
+
<img src="https://raw.githubusercontent.com/onikan27/claude-code-monitor/main/docs/ccm-demo.gif" alt="Terminal UI Demo" width="800">
|
|
11
14
|
</p>
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
### Mobile Web
|
|
17
|
+
Control from your phone (same Wi-Fi required)
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [📋 Requirements](#-requirements)
|
|
19
|
-
- [🚀 Installation](#-installation)
|
|
20
|
-
- [⚡ Quick Start](#-quick-start)
|
|
21
|
-
- [📖 Commands](#-commands)
|
|
22
|
-
- [⌨️ Keybindings](#️-keybindings-watch-mode)
|
|
23
|
-
- [🎨 Status Icons](#-status-icons)
|
|
24
|
-
- [🖥️ Supported Terminals](#️-supported-terminals)
|
|
25
|
-
- [💾 Data Storage](#-data-storage)
|
|
26
|
-
- [📦 Programmatic Usage](#-programmatic-usage)
|
|
27
|
-
- [🔧 Troubleshooting](#-troubleshooting)
|
|
28
|
-
- [🔒 Security](#-security)
|
|
29
|
-
- [⚠️ Disclaimer](#️-disclaimer)
|
|
30
|
-
- [📝 Changelog](#-changelog)
|
|
31
|
-
- [📄 License](#-license)
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="https://raw.githubusercontent.com/onikan27/claude-code-monitor/main/docs/mobile-web-demo.gif" alt="Mobile Web Demo" width="800">
|
|
21
|
+
</p>
|
|
32
22
|
|
|
33
23
|
---
|
|
34
24
|
|
|
35
25
|
## ✨ Features
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
27
|
+
| Terminal (TUI) | Mobile Web |
|
|
28
|
+
|----------------|------------|
|
|
29
|
+
| Real-time session monitoring | Monitor from your smartphone |
|
|
30
|
+
| Quick tab focus with keyboard | Remote terminal focus |
|
|
31
|
+
| Vim-style navigation | Send messages to terminal |
|
|
32
|
+
| Simple status display | Real-time sync via WebSocket |
|
|
33
|
+
|
|
34
|
+
- 🔌 **Serverless** - File-based state management, no API server required
|
|
41
35
|
- ⚡ **Easy Setup** - One command `ccm` for automatic setup and launch
|
|
36
|
+
- 🔒 **Secure** - No external data transmission, token-based mobile auth
|
|
42
37
|
|
|
43
38
|
---
|
|
44
39
|
|
|
45
40
|
## 📋 Requirements
|
|
46
41
|
|
|
47
|
-
> **Note**: This tool is **macOS only** due to its use of AppleScript for terminal
|
|
42
|
+
> **Note**: This tool is **macOS only** due to its use of AppleScript for terminal control.
|
|
48
43
|
|
|
49
|
-
- **macOS**
|
|
44
|
+
- **macOS**
|
|
50
45
|
- **Node.js** >= 18.0.0
|
|
51
46
|
- **Claude Code** installed
|
|
52
47
|
|
|
53
48
|
---
|
|
54
49
|
|
|
55
|
-
## 🚀
|
|
50
|
+
## 🚀 Quick Start
|
|
56
51
|
|
|
57
|
-
###
|
|
52
|
+
### Install
|
|
58
53
|
|
|
59
54
|
```bash
|
|
60
55
|
npm install -g claude-code-monitor
|
|
61
56
|
```
|
|
62
57
|
|
|
63
|
-
### Run
|
|
58
|
+
### Run
|
|
64
59
|
|
|
65
60
|
```bash
|
|
66
|
-
|
|
61
|
+
ccm
|
|
67
62
|
```
|
|
68
63
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
---
|
|
64
|
+
On first run, it automatically sets up hooks and launches the monitor.
|
|
72
65
|
|
|
73
|
-
|
|
66
|
+
### Mobile Access
|
|
74
67
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```
|
|
68
|
+
1. Press `h` to show QR code (default port: 3456)
|
|
69
|
+
2. Scan with your smartphone (same Wi-Fi required)
|
|
78
70
|
|
|
79
|
-
|
|
71
|
+
> If port 3456 is in use, an available port is automatically selected.
|
|
80
72
|
|
|
81
73
|
---
|
|
82
74
|
|
|
83
|
-
## 📖
|
|
75
|
+
## 📖 Usage
|
|
76
|
+
|
|
77
|
+
### Commands
|
|
84
78
|
|
|
85
79
|
| Command | Alias | Description |
|
|
86
80
|
|---------|-------|-------------|
|
|
87
|
-
| `ccm` | - | Launch monitor
|
|
88
|
-
| `ccm watch` | `ccm w` | Launch monitor
|
|
81
|
+
| `ccm` | - | Launch monitor (auto-setup if needed) |
|
|
82
|
+
| `ccm watch` | `ccm w` | Launch monitor |
|
|
83
|
+
| `ccm serve` | `ccm s` | Start mobile web server only |
|
|
89
84
|
| `ccm setup` | - | Configure Claude Code hooks |
|
|
90
85
|
| `ccm list` | `ccm ls` | List sessions |
|
|
91
86
|
| `ccm clear` | - | Clear all sessions |
|
|
92
|
-
| `ccm --version` | `ccm -V` | Show version |
|
|
93
|
-
| `ccm --help` | `ccm -h` | Show help |
|
|
94
87
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
## ⌨️ Keybindings (watch mode)
|
|
88
|
+
### Keybindings
|
|
98
89
|
|
|
99
90
|
| Key | Action |
|
|
100
91
|
|-----|--------|
|
|
101
92
|
| `↑` / `k` | Move up |
|
|
102
93
|
| `↓` / `j` | Move down |
|
|
103
94
|
| `Enter` / `f` | Focus selected session |
|
|
104
|
-
| `1-9` | Quick select & focus
|
|
95
|
+
| `1-9` | Quick select & focus |
|
|
96
|
+
| `h` | Show/Hide QR code |
|
|
105
97
|
| `c` | Clear all sessions |
|
|
106
98
|
| `q` / `Esc` | Quit |
|
|
107
99
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
## 🎨 Status Icons
|
|
100
|
+
### Status Icons
|
|
111
101
|
|
|
112
102
|
| Icon | Status | Description |
|
|
113
103
|
|------|--------|-------------|
|
|
114
104
|
| `●` | Running | Claude Code is processing |
|
|
115
|
-
| `◐` | Waiting | Waiting for user input
|
|
105
|
+
| `◐` | Waiting | Waiting for user input |
|
|
116
106
|
| `✓` | Done | Session ended |
|
|
117
107
|
|
|
118
108
|
---
|
|
119
109
|
|
|
120
|
-
##
|
|
121
|
-
|
|
122
|
-
Focus feature works with the following terminals:
|
|
123
|
-
|
|
124
|
-
| Terminal | Focus Support | Notes |
|
|
125
|
-
|----------|--------------|-------|
|
|
126
|
-
| iTerm2 | ✅ Full | TTY-based window/tab targeting |
|
|
127
|
-
| Terminal.app | ✅ Full | TTY-based window/tab targeting |
|
|
128
|
-
| Ghostty | ⚠️ Limited | Activates app only (cannot target specific window/tab) |
|
|
110
|
+
## 📱 Mobile Web Interface
|
|
129
111
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
---
|
|
112
|
+
Monitor and control Claude Code sessions from your smartphone.
|
|
133
113
|
|
|
134
|
-
|
|
114
|
+
### Features
|
|
135
115
|
|
|
136
|
-
|
|
116
|
+
- Real-time session status via WebSocket
|
|
117
|
+
- View latest Claude messages
|
|
118
|
+
- Focus terminal sessions remotely
|
|
119
|
+
- Send text messages to terminal (multi-line supported)
|
|
120
|
+
- Swipe-to-close gesture on modal
|
|
121
|
+
- Warning display for dangerous commands
|
|
137
122
|
|
|
138
|
-
###
|
|
123
|
+
### Security
|
|
139
124
|
|
|
140
|
-
|
|
141
|
-
|-------|-------------|
|
|
142
|
-
| `session_id` | Claude Code session identifier |
|
|
143
|
-
| `cwd` | Working directory path |
|
|
144
|
-
| `tty` | Terminal device path (e.g., `/dev/ttys001`) |
|
|
145
|
-
| `status` | Session status (running/waiting_input/stopped) |
|
|
146
|
-
| `updated_at` | Last update timestamp |
|
|
125
|
+
> **Important**: Your smartphone and Mac must be on the **same Wi-Fi network**.
|
|
147
126
|
|
|
148
|
-
|
|
127
|
+
- **Token Authentication** - A unique token is generated for authentication
|
|
128
|
+
- **Local Network Only** - Not accessible from the internet
|
|
129
|
+
- **Do not share the URL** - Treat it like a password
|
|
149
130
|
|
|
150
131
|
---
|
|
151
132
|
|
|
152
|
-
##
|
|
153
|
-
|
|
154
|
-
Can also be used as a library:
|
|
133
|
+
## 🖥️ Supported Terminals
|
|
155
134
|
|
|
156
|
-
|
|
157
|
-
|
|
135
|
+
| Terminal | Focus Support | Notes |
|
|
136
|
+
|----------|--------------|-------|
|
|
137
|
+
| iTerm2 | ✅ Full | TTY-based targeting |
|
|
138
|
+
| Terminal.app | ✅ Full | TTY-based targeting |
|
|
139
|
+
| Ghostty | ⚠️ Limited | App activation only |
|
|
158
140
|
|
|
159
|
-
|
|
160
|
-
for (const session of sessions) {
|
|
161
|
-
const { symbol, label } = getStatusDisplay(session.status);
|
|
162
|
-
console.log(`${symbol} ${label}: ${session.cwd}`);
|
|
163
|
-
}
|
|
164
|
-
```
|
|
141
|
+
> Other terminals can use monitoring, but focus feature is not supported.
|
|
165
142
|
|
|
166
143
|
---
|
|
167
144
|
|
|
@@ -170,48 +147,66 @@ for (const session of sessions) {
|
|
|
170
147
|
### Sessions not showing
|
|
171
148
|
|
|
172
149
|
1. Run `ccm setup` to verify hook configuration
|
|
173
|
-
2. Check
|
|
150
|
+
2. Check `~/.claude/settings.json` for hook settings
|
|
174
151
|
3. Restart Claude Code
|
|
175
152
|
|
|
176
|
-
```bash
|
|
177
|
-
# Check configuration
|
|
178
|
-
cat ~/.claude/settings.json | grep ccm
|
|
179
|
-
```
|
|
180
|
-
|
|
181
153
|
### Focus not working
|
|
182
154
|
|
|
183
|
-
1. Verify you're using
|
|
184
|
-
2.
|
|
185
|
-
3. Check System Preferences > Privacy & Security > Accessibility permissions
|
|
155
|
+
1. Verify you're using a supported terminal
|
|
156
|
+
2. Check System Preferences > Privacy & Security > Accessibility
|
|
186
157
|
|
|
187
|
-
### Reset
|
|
158
|
+
### Reset data
|
|
188
159
|
|
|
189
160
|
```bash
|
|
190
161
|
ccm clear
|
|
191
|
-
# or
|
|
192
|
-
rm ~/.claude-monitor/sessions.json
|
|
193
162
|
```
|
|
194
163
|
|
|
195
164
|
---
|
|
196
165
|
|
|
197
166
|
## 🔒 Security
|
|
198
167
|
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
168
|
+
- **No data sent to external servers** - All data stays on your machine
|
|
169
|
+
- Hook registration modifies `~/.claude/settings.json`
|
|
170
|
+
- Focus feature uses AppleScript for terminal control
|
|
171
|
+
- Mobile Web uses token authentication on local network only
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 📦 Programmatic Usage
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { getSessions, focusSession } from 'claude-code-monitor';
|
|
179
|
+
|
|
180
|
+
const sessions = getSessions();
|
|
181
|
+
if (sessions[0]?.tty) {
|
|
182
|
+
focusSession(sessions[0].tty);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
202
185
|
|
|
203
186
|
---
|
|
204
187
|
|
|
205
188
|
## ⚠️ Disclaimer
|
|
206
189
|
|
|
207
|
-
This is an unofficial community tool and is not affiliated with
|
|
190
|
+
This is an unofficial community tool and is not affiliated with Anthropic.
|
|
208
191
|
"Claude" and "Claude Code" are trademarks of Anthropic.
|
|
209
192
|
|
|
210
193
|
---
|
|
211
194
|
|
|
195
|
+
## 🐛 Issues
|
|
196
|
+
|
|
197
|
+
Found a bug? [Open an issue](https://github.com/onikan27/claude-code-monitor/issues)
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 🤝 Contributing
|
|
202
|
+
|
|
203
|
+
Contributions are welcome! Please open an issue or submit a PR.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
212
207
|
## 📝 Changelog
|
|
213
208
|
|
|
214
|
-
See [CHANGELOG.md](./CHANGELOG.md) for
|
|
209
|
+
See [CHANGELOG.md](./CHANGELOG.md) for details.
|
|
215
210
|
|
|
216
211
|
---
|
|
217
212
|
|
package/dist/bin/ccm.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Command } from 'commander';
|
|
|
6
6
|
import { render } from 'ink';
|
|
7
7
|
import { Dashboard } from '../components/Dashboard.js';
|
|
8
8
|
import { handleHookEvent } from '../hook/handler.js';
|
|
9
|
+
import { startServer } from '../server/index.js';
|
|
9
10
|
import { isHooksConfigured, setupHooks } from '../setup/index.js';
|
|
10
11
|
import { clearSessions, getSessions } from '../store/file-store.js';
|
|
11
12
|
import { getStatusDisplay } from '../utils/status.js';
|
|
@@ -47,13 +48,24 @@ function getTtyFromAncestors() {
|
|
|
47
48
|
/**
|
|
48
49
|
* Run TUI with alternate screen buffer
|
|
49
50
|
*/
|
|
50
|
-
async function runWithAltScreen(
|
|
51
|
+
async function runWithAltScreen(options = {}) {
|
|
51
52
|
process.stdout.write(ENTER_ALT_SCREEN);
|
|
52
|
-
|
|
53
|
+
// カーソルを非表示にして、より安定したレンダリングを行う
|
|
54
|
+
process.stdout.write('\x1b[?25l');
|
|
55
|
+
const instance = render(_jsx(Dashboard, { initialShowQr: options.qr }), { patchConsole: false });
|
|
56
|
+
// リサイズ時にInkの描画をクリアして再描画
|
|
57
|
+
const handleResize = () => {
|
|
58
|
+
instance.clear();
|
|
59
|
+
instance.rerender(_jsx(Dashboard, { initialShowQr: options.qr }));
|
|
60
|
+
};
|
|
61
|
+
process.stdout.on('resize', handleResize);
|
|
53
62
|
try {
|
|
54
|
-
await waitUntilExit();
|
|
63
|
+
await instance.waitUntilExit();
|
|
55
64
|
}
|
|
56
65
|
finally {
|
|
66
|
+
process.stdout.off('resize', handleResize);
|
|
67
|
+
// カーソルを再表示
|
|
68
|
+
process.stdout.write('\x1b[?25h');
|
|
57
69
|
process.stdout.write(EXIT_ALT_SCREEN);
|
|
58
70
|
}
|
|
59
71
|
}
|
|
@@ -61,13 +73,15 @@ const program = new Command();
|
|
|
61
73
|
program
|
|
62
74
|
.name('ccm')
|
|
63
75
|
.description('Claude Code Monitor - CLI-based session monitoring')
|
|
64
|
-
.version(pkg.version)
|
|
76
|
+
.version(pkg.version)
|
|
77
|
+
.option('--qr', 'Show QR code for mobile access');
|
|
65
78
|
program
|
|
66
79
|
.command('watch')
|
|
67
80
|
.alias('w')
|
|
68
81
|
.description('Start the monitoring TUI')
|
|
69
|
-
.
|
|
70
|
-
|
|
82
|
+
.option('--qr', 'Show QR code for mobile access')
|
|
83
|
+
.action(async (options) => {
|
|
84
|
+
await runWithAltScreen({ qr: options.qr });
|
|
71
85
|
});
|
|
72
86
|
program
|
|
73
87
|
.command('hook <event>')
|
|
@@ -111,12 +125,21 @@ program
|
|
|
111
125
|
.action(async () => {
|
|
112
126
|
await setupHooks();
|
|
113
127
|
});
|
|
128
|
+
program
|
|
129
|
+
.command('serve')
|
|
130
|
+
.alias('s')
|
|
131
|
+
.description('Start web server for mobile monitoring')
|
|
132
|
+
.option('-p, --port <port>', 'Port number', '3456')
|
|
133
|
+
.action(async (options) => {
|
|
134
|
+
const port = parseInt(options.port, 10);
|
|
135
|
+
await startServer(port);
|
|
136
|
+
});
|
|
114
137
|
/**
|
|
115
|
-
* Default action (when launched without arguments)
|
|
138
|
+
* Default action (when launched without arguments or with --qr only)
|
|
116
139
|
* - Run setup if not configured
|
|
117
140
|
* - Launch monitor if already configured
|
|
118
141
|
*/
|
|
119
|
-
async function defaultAction() {
|
|
142
|
+
async function defaultAction(options = {}) {
|
|
120
143
|
if (!isHooksConfigured()) {
|
|
121
144
|
console.log('Initial setup required.\n');
|
|
122
145
|
await setupHooks();
|
|
@@ -128,12 +151,11 @@ async function defaultAction() {
|
|
|
128
151
|
console.log('');
|
|
129
152
|
}
|
|
130
153
|
// Launch monitor
|
|
131
|
-
await runWithAltScreen(
|
|
132
|
-
}
|
|
133
|
-
// Default action when executed without commands
|
|
134
|
-
if (process.argv.length === 2) {
|
|
135
|
-
defaultAction().catch(console.error);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
program.parse();
|
|
154
|
+
await runWithAltScreen(options);
|
|
139
155
|
}
|
|
156
|
+
// Handle default action (no subcommand)
|
|
157
|
+
program.action(async () => {
|
|
158
|
+
const options = program.opts();
|
|
159
|
+
await defaultAction({ qr: options.qr });
|
|
160
|
+
});
|
|
161
|
+
program.parse();
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
|
|
2
|
+
interface DashboardProps {
|
|
3
|
+
/** Override default QR code visibility (e.g., from --qr CLI flag) */
|
|
4
|
+
initialShowQr?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function Dashboard({ initialShowQr }: DashboardProps): React.ReactElement;
|
|
7
|
+
export {};
|
|
3
8
|
//# sourceMappingURL=Dashboard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dashboard.d.ts","sourceRoot":"","sources":["../../src/components/Dashboard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"Dashboard.d.ts","sourceRoot":"","sources":["../../src/components/Dashboard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAW/B,UAAU,cAAc;IACtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,SAAS,CAAC,EAAE,aAAa,EAAE,EAAE,cAAc,GAAG,KAAK,CAAC,YAAY,CA8L/E"}
|
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
|
-
import { useMemo, useState } from 'react';
|
|
2
|
+
import { Box, Text, useApp, useInput, useStdout } from 'ink';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { MIN_TERMINAL_HEIGHT_FOR_QR } from '../constants.js';
|
|
5
|
+
import { useServer } from '../hooks/useServer.js';
|
|
4
6
|
import { useSessions } from '../hooks/useSessions.js';
|
|
5
|
-
import { clearSessions } from '../store/file-store.js';
|
|
7
|
+
import { clearSessions, readSettings, writeSettings } from '../store/file-store.js';
|
|
6
8
|
import { focusSession } from '../utils/focus.js';
|
|
7
9
|
import { SessionCard } from './SessionCard.js';
|
|
8
10
|
const QUICK_SELECT_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
9
|
-
export function Dashboard() {
|
|
11
|
+
export function Dashboard({ initialShowQr }) {
|
|
10
12
|
const { sessions, loading, error } = useSessions();
|
|
13
|
+
const { url, qrCode, loading: serverLoading } = useServer();
|
|
11
14
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
12
15
|
const { exit } = useApp();
|
|
16
|
+
const { stdout } = useStdout();
|
|
17
|
+
// QRコード表示状態: --qrフラグが指定された場合はそれを優先、なければ設定を読み込む
|
|
18
|
+
const [qrCodeUserPref, setQrCodeUserPref] = useState(() => initialShowQr ?? readSettings().qrCodeVisible);
|
|
19
|
+
const [terminalHeight, setTerminalHeight] = useState(stdout?.rows ?? 40);
|
|
20
|
+
// Monitor terminal size changes
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!stdout)
|
|
23
|
+
return;
|
|
24
|
+
const handleResize = () => {
|
|
25
|
+
setTerminalHeight(stdout.rows ?? 40);
|
|
26
|
+
};
|
|
27
|
+
// Initial size
|
|
28
|
+
setTerminalHeight(stdout.rows ?? 40);
|
|
29
|
+
stdout.on('resize', handleResize);
|
|
30
|
+
return () => {
|
|
31
|
+
stdout.off('resize', handleResize);
|
|
32
|
+
};
|
|
33
|
+
}, [stdout]);
|
|
34
|
+
// QR code visibility: user preference AND terminal has enough space
|
|
35
|
+
const canShowQr = terminalHeight >= MIN_TERMINAL_HEIGHT_FOR_QR;
|
|
36
|
+
const qrCodeVisible = qrCodeUserPref && canShowQr;
|
|
37
|
+
const toggleQrCode = () => {
|
|
38
|
+
const newValue = !qrCodeUserPref;
|
|
39
|
+
setQrCodeUserPref(newValue);
|
|
40
|
+
writeSettings({ qrCodeVisible: newValue });
|
|
41
|
+
};
|
|
13
42
|
const focusSessionByIndex = (index) => {
|
|
14
43
|
const session = sessions[index];
|
|
15
44
|
if (session?.tty) {
|
|
@@ -51,6 +80,11 @@ export function Dashboard() {
|
|
|
51
80
|
if (input === 'c') {
|
|
52
81
|
clearSessions();
|
|
53
82
|
setSelectedIndex(0);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (input === 'h') {
|
|
86
|
+
toggleQrCode();
|
|
87
|
+
return;
|
|
54
88
|
}
|
|
55
89
|
});
|
|
56
90
|
if (loading) {
|
|
@@ -60,5 +94,5 @@ export function Dashboard() {
|
|
|
60
94
|
return _jsxs(Text, { color: "red", children: ["Error: ", error.message] });
|
|
61
95
|
}
|
|
62
96
|
const { running, waiting_input: waitingInput, stopped } = statusCounts;
|
|
63
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Claude Code Monitor" }), _jsx(Text, { dimColor: true, children: "
|
|
97
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "Claude Code Monitor" }), _jsx(Text, { dimColor: true, children: " " }), _jsxs(Text, { color: "gray", children: ["\u25CF ", running] }), _jsx(Text, { dimColor: true, children: " " }), _jsxs(Text, { color: "yellow", children: ["\u25D0 ", waitingInput] }), _jsx(Text, { dimColor: true, children: " " }), _jsxs(Text, { color: "green", children: ["\u2713 ", stopped] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: sessions.length === 0 ? (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: "No active sessions" }) })) : (sessions.map((session, index) => (_jsx(SessionCard, { session: session, index: index, isSelected: index === selectedIndex }, `${session.session_id}:${session.tty || ''}`)))) })] }), _jsxs(Box, { marginTop: 1, justifyContent: "center", gap: 1, children: [_jsx(Text, { dimColor: true, children: "[\u2191\u2193]Select" }), _jsx(Text, { dimColor: true, children: "[Enter]Focus" }), _jsx(Text, { dimColor: true, children: "[1-9]Quick" }), _jsx(Text, { dimColor: true, children: "[c]Clear" }), _jsxs(Text, { dimColor: true, children: ["[h]", qrCodeUserPref ? 'Hide' : 'Show', "URL"] }), _jsx(Text, { dimColor: true, children: "[q]Quit" })] }), !serverLoading && url && !qrCodeUserPref && (_jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsx(Text, { color: "white", children: "\uD83D\uDCF1 Web UI available. Press [h] to show QR code for mobile access. (Same Wi-Fi required)" }) })), !serverLoading && url && qrCodeUserPref && (_jsxs(Box, { marginTop: 1, paddingX: 1, children: [qrCodeVisible && qrCode && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { children: qrCode }) })), _jsxs(Box, { flexDirection: "column", marginLeft: qrCodeVisible && qrCode ? 2 : 0, justifyContent: "center", children: [_jsx(Text, { bold: true, color: "magenta", children: "Web UI" }), _jsx(Text, { dimColor: true, children: url }), _jsx(Text, { dimColor: true, children: "Scan QR code to monitor sessions from your phone." }), _jsx(Text, { dimColor: true, children: "Tap a session to focus its terminal on this Mac." }), _jsx(Text, { color: "yellow", children: "\u26A0 Do not share this URL with others." }), !canShowQr && _jsx(Text, { color: "yellow", children: "\u26A0 Resize window to show QR code" })] })] }))] }));
|
|
64
98
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionCard.d.ts","sourceRoot":"","sources":["../../src/components/SessionCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"SessionCard.d.ts","sourceRoot":"","sources":["../../src/components/SessionCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIjD,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;CACrB;AAOD,eAAO,MAAM,WAAW,8CAyBtB,CAAC"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
import { getStatusDisplay } from '../utils/status.js';
|
|
5
5
|
import { formatRelativeTime } from '../utils/time.js';
|
|
6
|
-
import { Spinner } from './Spinner.js';
|
|
7
6
|
function abbreviateHomePath(path) {
|
|
8
7
|
if (!path)
|
|
9
8
|
return '(unknown)';
|
|
@@ -13,6 +12,5 @@ export const SessionCard = memo(function SessionCard({ session, index, isSelecte
|
|
|
13
12
|
const { symbol, color, label } = getStatusDisplay(session.status);
|
|
14
13
|
const dir = abbreviateHomePath(session.cwd);
|
|
15
14
|
const relativeTime = formatRelativeTime(session.updated_at);
|
|
16
|
-
|
|
17
|
-
return (_jsxs(Box, { paddingX: 1, children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, bold: isSelected, children: [isSelected ? '>' : ' ', " [", index + 1, "]"] }), _jsx(Text, { children: " " }), _jsx(Box, { width: 10, children: isRunning ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { color: "green" }), _jsxs(Text, { color: color, children: [" ", label] })] })) : (_jsxs(Text, { color: color, children: [symbol, " ", label] })) }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: relativeTime.padEnd(8) }), _jsx(Text, { color: isSelected ? 'white' : 'gray', children: dir })] }));
|
|
15
|
+
return (_jsxs(Box, { paddingX: 1, children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, bold: isSelected, children: [isSelected ? '>' : ' ', " [", index + 1, "]"] }), _jsx(Text, { children: " " }), _jsx(Box, { width: 10, children: _jsxs(Text, { color: color, children: [symbol, " ", label] }) }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: relativeTime.padEnd(8) }), _jsx(Text, { color: isSelected ? 'white' : 'gray', children: dir })] }));
|
|
18
16
|
});
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Text } from 'ink';
|
|
3
3
|
import { useSyncExternalStore } from 'react';
|
|
4
4
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
5
|
-
const FRAME_INTERVAL_MS =
|
|
5
|
+
const FRAME_INTERVAL_MS = 200; // 200msでチラつきを軽減
|
|
6
6
|
// Shared global spinner state - only ONE timer for all spinners
|
|
7
7
|
let globalFrame = 0;
|
|
8
8
|
let subscriberCount = 0;
|
package/dist/constants.d.ts
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/** Package name used for npx commands */
|
|
5
5
|
export declare const PACKAGE_NAME = "claude-code-monitor";
|
|
6
|
-
/** Session timeout in milliseconds (30 minutes) */
|
|
7
|
-
export declare const SESSION_TIMEOUT_MS: number;
|
|
8
6
|
/** TTY cache TTL in milliseconds (30 seconds) */
|
|
9
7
|
export declare const TTY_CACHE_TTL_MS = 30000;
|
|
10
8
|
/** Maximum number of entries in TTY cache */
|
|
@@ -15,6 +13,11 @@ export declare const SESSION_UPDATE_DEBOUNCE_MS = 150;
|
|
|
15
13
|
export declare const WRITE_DEBOUNCE_MS = 100;
|
|
16
14
|
/** Periodic refresh interval for timeout detection in milliseconds (60 seconds) */
|
|
17
15
|
export declare const SESSION_REFRESH_INTERVAL_MS = 60000;
|
|
16
|
+
/**
|
|
17
|
+
* QRコード表示に必要な最小ターミナル高さ
|
|
18
|
+
* Header(1) + Sessions(3) + Shortcuts(2) + WebUI with QR(16) = 22行
|
|
19
|
+
*/
|
|
20
|
+
export declare const MIN_TERMINAL_HEIGHT_FOR_QR = 22;
|
|
18
21
|
/** Hook event types supported by Claude Code */
|
|
19
22
|
export declare const HOOK_EVENTS: readonly ["UserPromptSubmit", "PreToolUse", "PostToolUse", "Notification", "Stop"];
|
|
20
23
|
export type HookEventName = (typeof HOOK_EVENTS)[number];
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yCAAyC;AACzC,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAElD,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yCAAyC;AACzC,eAAO,MAAM,YAAY,wBAAwB,CAAC;AAElD,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC,6CAA6C;AAC7C,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,6DAA6D;AAC7D,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,mFAAmF;AACnF,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,gDAAgD;AAChD,eAAO,MAAM,WAAW,oFAMd,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/** Package name used for npx commands */
|
|
5
5
|
export const PACKAGE_NAME = 'claude-code-monitor';
|
|
6
|
-
/** Session timeout in milliseconds (30 minutes) */
|
|
7
|
-
export const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
|
|
8
6
|
/** TTY cache TTL in milliseconds (30 seconds) */
|
|
9
7
|
export const TTY_CACHE_TTL_MS = 30_000;
|
|
10
8
|
/** Maximum number of entries in TTY cache */
|
|
@@ -15,6 +13,11 @@ export const SESSION_UPDATE_DEBOUNCE_MS = 150;
|
|
|
15
13
|
export const WRITE_DEBOUNCE_MS = 100;
|
|
16
14
|
/** Periodic refresh interval for timeout detection in milliseconds (60 seconds) */
|
|
17
15
|
export const SESSION_REFRESH_INTERVAL_MS = 60_000;
|
|
16
|
+
/**
|
|
17
|
+
* QRコード表示に必要な最小ターミナル高さ
|
|
18
|
+
* Header(1) + Sessions(3) + Shortcuts(2) + WebUI with QR(16) = 22行
|
|
19
|
+
*/
|
|
20
|
+
export const MIN_TERMINAL_HEIGHT_FOR_QR = 22;
|
|
18
21
|
/** Hook event types supported by Claude Code */
|
|
19
22
|
export const HOOK_EVENTS = [
|
|
20
23
|
'UserPromptSubmit',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/hook/handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/hook/handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAKlE,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAMhD,CAAC;AAEH,gBAAgB;AAChB,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,aAAa,CAExE;AAED,gBAAgB;AAChB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEhE;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDpF"}
|