claude-code-monitor 1.0.4 → 1.1.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 +28 -0
- package/README.md +83 -104
- 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,76 +1,61 @@
|
|
|
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
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npx claude-code-monitor
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
> **Note**: With npx, you must run `npx claude-code-monitor` each time (the `ccm` shortcut is only available with global install). Global install is recommended since this tool requires hook setup and is designed for continuous use.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## ⚡ Quick Start
|
|
58
|
+
### Run
|
|
74
59
|
|
|
75
60
|
```bash
|
|
76
61
|
ccm
|
|
@@ -78,90 +63,78 @@ ccm
|
|
|
78
63
|
|
|
79
64
|
On first run, it automatically sets up hooks and launches the monitor.
|
|
80
65
|
|
|
66
|
+
### Mobile Access
|
|
67
|
+
|
|
68
|
+
1. Press `h` to show QR code
|
|
69
|
+
2. Scan with your smartphone (same Wi-Fi required)
|
|
70
|
+
|
|
81
71
|
---
|
|
82
72
|
|
|
83
|
-
## 📖
|
|
73
|
+
## 📖 Usage
|
|
74
|
+
|
|
75
|
+
### Commands
|
|
84
76
|
|
|
85
77
|
| Command | Alias | Description |
|
|
86
78
|
|---------|-------|-------------|
|
|
87
|
-
| `ccm` | - | Launch monitor
|
|
88
|
-
| `ccm watch` | `ccm w` | Launch monitor
|
|
79
|
+
| `ccm` | - | Launch monitor (auto-setup if needed) |
|
|
80
|
+
| `ccm watch` | `ccm w` | Launch monitor |
|
|
81
|
+
| `ccm serve` | `ccm s` | Start mobile web server only |
|
|
89
82
|
| `ccm setup` | - | Configure Claude Code hooks |
|
|
90
83
|
| `ccm list` | `ccm ls` | List sessions |
|
|
91
84
|
| `ccm clear` | - | Clear all sessions |
|
|
92
|
-
| `ccm --version` | `ccm -V` | Show version |
|
|
93
|
-
| `ccm --help` | `ccm -h` | Show help |
|
|
94
85
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
## ⌨️ Keybindings (watch mode)
|
|
86
|
+
### Keybindings
|
|
98
87
|
|
|
99
88
|
| Key | Action |
|
|
100
89
|
|-----|--------|
|
|
101
90
|
| `↑` / `k` | Move up |
|
|
102
91
|
| `↓` / `j` | Move down |
|
|
103
92
|
| `Enter` / `f` | Focus selected session |
|
|
104
|
-
| `1-9` | Quick select & focus
|
|
93
|
+
| `1-9` | Quick select & focus |
|
|
94
|
+
| `h` | Show/Hide QR code |
|
|
105
95
|
| `c` | Clear all sessions |
|
|
106
96
|
| `q` / `Esc` | Quit |
|
|
107
97
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
## 🎨 Status Icons
|
|
98
|
+
### Status Icons
|
|
111
99
|
|
|
112
100
|
| Icon | Status | Description |
|
|
113
101
|
|------|--------|-------------|
|
|
114
102
|
| `●` | Running | Claude Code is processing |
|
|
115
|
-
| `◐` | Waiting | Waiting for user input
|
|
103
|
+
| `◐` | Waiting | Waiting for user input |
|
|
116
104
|
| `✓` | Done | Session ended |
|
|
117
105
|
|
|
118
106
|
---
|
|
119
107
|
|
|
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) |
|
|
129
|
-
|
|
130
|
-
> **Note**: Other terminals (Alacritty, kitty, Warp, etc.) can use monitoring but focus feature is not supported.
|
|
108
|
+
## 📱 Mobile Web Interface
|
|
131
109
|
|
|
132
|
-
|
|
110
|
+
Monitor and control Claude Code sessions from your smartphone.
|
|
133
111
|
|
|
134
|
-
|
|
112
|
+
### Features
|
|
135
113
|
|
|
136
|
-
|
|
114
|
+
- Real-time session status via WebSocket
|
|
115
|
+
- View latest Claude messages
|
|
116
|
+
- Focus terminal sessions remotely
|
|
117
|
+
- Send text messages to terminal
|
|
137
118
|
|
|
138
|
-
###
|
|
119
|
+
### Security
|
|
139
120
|
|
|
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 |
|
|
121
|
+
> **Important**: Your smartphone and Mac must be on the **same Wi-Fi network**.
|
|
147
122
|
|
|
148
|
-
|
|
123
|
+
- **Token Authentication** - Each session generates a unique token
|
|
124
|
+
- **Local Network Only** - Not accessible from the internet
|
|
125
|
+
- **Do not share the URL** - Treat it like a password
|
|
149
126
|
|
|
150
127
|
---
|
|
151
128
|
|
|
152
|
-
##
|
|
153
|
-
|
|
154
|
-
Can also be used as a library:
|
|
129
|
+
## 🖥️ Supported Terminals
|
|
155
130
|
|
|
156
|
-
|
|
157
|
-
|
|
131
|
+
| Terminal | Focus Support | Notes |
|
|
132
|
+
|----------|--------------|-------|
|
|
133
|
+
| iTerm2 | ✅ Full | TTY-based targeting |
|
|
134
|
+
| Terminal.app | ✅ Full | TTY-based targeting |
|
|
135
|
+
| Ghostty | ⚠️ Limited | App activation only |
|
|
158
136
|
|
|
159
|
-
|
|
160
|
-
for (const session of sessions) {
|
|
161
|
-
const { symbol, label } = getStatusDisplay(session.status);
|
|
162
|
-
console.log(`${symbol} ${label}: ${session.cwd}`);
|
|
163
|
-
}
|
|
164
|
-
```
|
|
137
|
+
> Other terminals can use monitoring, but focus feature is not supported.
|
|
165
138
|
|
|
166
139
|
---
|
|
167
140
|
|
|
@@ -170,48 +143,54 @@ for (const session of sessions) {
|
|
|
170
143
|
### Sessions not showing
|
|
171
144
|
|
|
172
145
|
1. Run `ccm setup` to verify hook configuration
|
|
173
|
-
2. Check
|
|
146
|
+
2. Check `~/.claude/settings.json` for hook settings
|
|
174
147
|
3. Restart Claude Code
|
|
175
148
|
|
|
176
|
-
```bash
|
|
177
|
-
# Check configuration
|
|
178
|
-
cat ~/.claude/settings.json | grep ccm
|
|
179
|
-
```
|
|
180
|
-
|
|
181
149
|
### Focus not working
|
|
182
150
|
|
|
183
|
-
1. Verify you're using
|
|
184
|
-
2.
|
|
185
|
-
3. Check System Preferences > Privacy & Security > Accessibility permissions
|
|
151
|
+
1. Verify you're using a supported terminal
|
|
152
|
+
2. Check System Preferences > Privacy & Security > Accessibility
|
|
186
153
|
|
|
187
|
-
### Reset
|
|
154
|
+
### Reset data
|
|
188
155
|
|
|
189
156
|
```bash
|
|
190
157
|
ccm clear
|
|
191
|
-
# or
|
|
192
|
-
rm ~/.claude-monitor/sessions.json
|
|
193
158
|
```
|
|
194
159
|
|
|
195
160
|
---
|
|
196
161
|
|
|
197
162
|
## 🔒 Security
|
|
198
163
|
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
164
|
+
- **No data sent to external servers** - All data stays on your machine
|
|
165
|
+
- Hook registration modifies `~/.claude/settings.json`
|
|
166
|
+
- Focus feature uses AppleScript for terminal control
|
|
167
|
+
- Mobile Web uses token authentication on local network only
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 📦 Programmatic Usage
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { getSessions, focusSession } from 'claude-code-monitor';
|
|
175
|
+
|
|
176
|
+
const sessions = getSessions();
|
|
177
|
+
if (sessions[0]?.tty) {
|
|
178
|
+
focusSession(sessions[0].tty);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
202
181
|
|
|
203
182
|
---
|
|
204
183
|
|
|
205
184
|
## ⚠️ Disclaimer
|
|
206
185
|
|
|
207
|
-
This is an unofficial community tool and is not affiliated with
|
|
186
|
+
This is an unofficial community tool and is not affiliated with Anthropic.
|
|
208
187
|
"Claude" and "Claude Code" are trademarks of Anthropic.
|
|
209
188
|
|
|
210
189
|
---
|
|
211
190
|
|
|
212
191
|
## 📝 Changelog
|
|
213
192
|
|
|
214
|
-
See [CHANGELOG.md](./CHANGELOG.md) for
|
|
193
|
+
See [CHANGELOG.md](./CHANGELOG.md) for details.
|
|
215
194
|
|
|
216
195
|
---
|
|
217
196
|
|
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"}
|