aigo 1.0.0 → 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/Makefile +4 -0
- package/README.md +134 -54
- package/bin/vigo.js +31 -31
- package/lib/cli.js +17 -17
- package/lib/config.js +2 -2
- package/lib/server.js +26 -0
- package/package.json +1 -1
- package/public/index.html +132 -2
- package/public/terminal.js +84 -8
- package/specs/claude-code-web-terminal.md +28 -28
- package/specs/cursor-cli-support.md +12 -12
- package/specs/history-view-button.md +64 -0
package/Makefile
ADDED
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# aigo
|
|
2
2
|
|
|
3
3
|
Stream AI coding assistants to the web - run Claude Code or Cursor Agent remotely from any device.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**aigo** wraps Claude Code or Cursor Agent in a tmux session and streams it to your browser via WebSocket, letting you access your AI coding sessions from your phone, tablet, or any browser.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -14,13 +14,13 @@ npm install
|
|
|
14
14
|
npm link
|
|
15
15
|
|
|
16
16
|
# Start Claude Code with web access (ngrok tunnel enabled by default)
|
|
17
|
-
|
|
17
|
+
aigo claude
|
|
18
18
|
|
|
19
19
|
# Or start Cursor Agent
|
|
20
|
-
|
|
20
|
+
aigo cursor
|
|
21
21
|
|
|
22
22
|
# Local only (no tunnel)
|
|
23
|
-
|
|
23
|
+
aigo --tunnel none claude
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Requirements
|
|
@@ -29,7 +29,8 @@ vigo --tunnel none claude
|
|
|
29
29
|
- **tmux** installed and in PATH
|
|
30
30
|
- **Claude Code CLI** installed (`claude` command) - for Claude Code support
|
|
31
31
|
- **Cursor Agent CLI** installed (`agent` command) - for Cursor support
|
|
32
|
-
- **ngrok** (
|
|
32
|
+
- **ngrok** (default for remote access) - sign up at [ngrok.com](https://ngrok.com)
|
|
33
|
+
- **cloudflared** (alternative to ngrok) - free, no signup required
|
|
33
34
|
|
|
34
35
|
### Installing tmux
|
|
35
36
|
|
|
@@ -56,6 +57,23 @@ brew install ngrok
|
|
|
56
57
|
ngrok config add-authtoken YOUR_TOKEN
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
### Installing cloudflared
|
|
61
|
+
|
|
62
|
+
cloudflared is a free alternative to ngrok that doesn't require signup:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# macOS
|
|
66
|
+
brew install cloudflared
|
|
67
|
+
|
|
68
|
+
# Ubuntu/Debian
|
|
69
|
+
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
|
|
70
|
+
sudo dpkg -i cloudflared.deb
|
|
71
|
+
|
|
72
|
+
# Or download from https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
No authentication required for quick tunnels (trycloudflare.com).
|
|
76
|
+
|
|
59
77
|
### Installing Cursor Agent CLI
|
|
60
78
|
|
|
61
79
|
```bash
|
|
@@ -72,10 +90,10 @@ export CURSOR_API_KEY=your_api_key_here
|
|
|
72
90
|
## Usage
|
|
73
91
|
|
|
74
92
|
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
aigo [options] claude [claude-args]
|
|
94
|
+
aigo [options] cursor [cursor-args]
|
|
95
|
+
aigo [options] agent [agent-args]
|
|
96
|
+
aigo [options] --attach <session>
|
|
79
97
|
|
|
80
98
|
Tools:
|
|
81
99
|
claude Use Claude Code CLI
|
|
@@ -97,26 +115,26 @@ Options:
|
|
|
97
115
|
|
|
98
116
|
```bash
|
|
99
117
|
# Claude Code Examples (ngrok tunnel by default)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
aigo claude # Start Claude with ngrok tunnel
|
|
119
|
+
aigo --tunnel none claude # Local only (no tunnel)
|
|
120
|
+
aigo -p 8080 -s myproject claude # Custom port and session
|
|
121
|
+
aigo claude --model sonnet # Pass args to Claude
|
|
104
122
|
|
|
105
123
|
# Cursor Agent Examples
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
aigo cursor # Start Cursor Agent with ngrok tunnel
|
|
125
|
+
aigo --tunnel cloudflared cursor # Use cloudflared instead of ngrok
|
|
126
|
+
aigo -p 8080 -s myproject cursor # Custom port and session
|
|
127
|
+
aigo cursor --model gpt-5 # Pass args to Cursor Agent
|
|
110
128
|
|
|
111
129
|
# General Examples
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
aigo --attach myproject # Attach to existing tmux session
|
|
131
|
+
aigo -P mypass123 claude # Custom password
|
|
132
|
+
aigo -T 15 -E 30 cursor # 15min lock, 30min exit timeout
|
|
133
|
+
aigo -T 0 -E 0 claude # Disable timeouts (default behavior)
|
|
116
134
|
|
|
117
135
|
# Run multiple sessions (ports auto-selected)
|
|
118
|
-
|
|
119
|
-
|
|
136
|
+
aigo -s project-a claude # Gets port 3000
|
|
137
|
+
aigo -s project-b cursor # Gets port 3001 (auto)
|
|
120
138
|
```
|
|
121
139
|
|
|
122
140
|
## Configuration
|
|
@@ -144,17 +162,42 @@ export const config = {
|
|
|
144
162
|
};
|
|
145
163
|
```
|
|
146
164
|
|
|
165
|
+
## Tunnel Options
|
|
166
|
+
|
|
167
|
+
aigo supports two tunnel providers for remote access:
|
|
168
|
+
|
|
169
|
+
| Feature | ngrok | cloudflared |
|
|
170
|
+
|---------|-------|-------------|
|
|
171
|
+
| Signup required | Yes | No |
|
|
172
|
+
| Free tier limits | 1 tunnel, rate limits | Unlimited quick tunnels |
|
|
173
|
+
| Custom domains | Paid plans | Requires Cloudflare account |
|
|
174
|
+
| URL format | `*.ngrok-free.app` | `*.trycloudflare.com` |
|
|
175
|
+
| Authentication | Required (authtoken) | Not required |
|
|
176
|
+
|
|
177
|
+
**Recommendation:** Use cloudflared for quick testing (no signup needed). Use ngrok if you need stable URLs or have an existing account.
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Use cloudflared (no signup required)
|
|
181
|
+
aigo --tunnel cloudflared claude
|
|
182
|
+
|
|
183
|
+
# Use ngrok (default, requires signup)
|
|
184
|
+
aigo claude
|
|
185
|
+
|
|
186
|
+
# Local only (no tunnel)
|
|
187
|
+
aigo --tunnel none claude
|
|
188
|
+
```
|
|
189
|
+
|
|
147
190
|
## How It Works
|
|
148
191
|
|
|
149
|
-
1. **
|
|
192
|
+
1. **aigo** creates a tmux session and runs `claude` or `agent` inside it
|
|
150
193
|
2. A local web server starts, serving an xterm.js terminal
|
|
151
194
|
3. The server attaches to the tmux session via node-pty
|
|
152
195
|
4. Your browser connects via WebSocket for real-time terminal streaming
|
|
153
|
-
5. ngrok creates a public HTTPS
|
|
196
|
+
5. A tunnel (ngrok or cloudflared) creates a public HTTPS URL for remote access
|
|
154
197
|
|
|
155
198
|
```
|
|
156
199
|
┌────────────────────────────────────────┐
|
|
157
|
-
│
|
|
200
|
+
│ aigo - Claude Code Web Terminal │
|
|
158
201
|
├────────────────────────────────────────┤
|
|
159
202
|
│ Session: claude-code │
|
|
160
203
|
│ Local: http://localhost:3000 │
|
|
@@ -171,7 +214,7 @@ Access URL:
|
|
|
171
214
|
|
|
172
215
|
| Feature | Claude Code | Cursor Agent |
|
|
173
216
|
|---------|-------------|--------------|
|
|
174
|
-
| Command | `
|
|
217
|
+
| Command | `aigo claude` | `aigo cursor` |
|
|
175
218
|
| CLI Binary | `claude` | `agent` |
|
|
176
219
|
| Authentication | Claude API key / OAuth | Browser login or `CURSOR_API_KEY` |
|
|
177
220
|
| Models | Claude models | Multiple (GPT-5, Claude, etc.) via subscription |
|
|
@@ -181,7 +224,7 @@ Access URL:
|
|
|
181
224
|
|
|
182
225
|
## Security
|
|
183
226
|
|
|
184
|
-
|
|
227
|
+
aigo uses multiple layers of security:
|
|
185
228
|
|
|
186
229
|
1. **URL Token** - A random 4-character token in the URL path (`/t/<token>`)
|
|
187
230
|
2. **Password** - A 6-character alphanumeric password (or custom password via `-P`)
|
|
@@ -194,17 +237,17 @@ vigo uses multiple layers of security:
|
|
|
194
237
|
- Password uses unambiguous characters (no 0/O, 1/l/I)
|
|
195
238
|
- Lock screen requires re-entering password after inactivity (when enabled)
|
|
196
239
|
- Session auto-terminates and kills tmux after extended inactivity (when enabled)
|
|
197
|
-
- HTTPS via
|
|
240
|
+
- HTTPS via tunnel (encrypted in transit)
|
|
198
241
|
|
|
199
242
|
**Recommendations:**
|
|
200
|
-
- Use HTTPS (ngrok
|
|
243
|
+
- Use HTTPS (both ngrok and cloudflared provide this automatically)
|
|
201
244
|
- Don't share your access URL or password publicly
|
|
202
245
|
- Use strong custom passwords for sensitive sessions
|
|
203
246
|
- Enable timeouts (`-T 15 -E 30`) for shared or sensitive environments
|
|
204
247
|
|
|
205
248
|
## Session Timeouts
|
|
206
249
|
|
|
207
|
-
|
|
250
|
+
aigo has two optional timeout mechanisms (both disabled by default for persistent sessions):
|
|
208
251
|
|
|
209
252
|
### Lock Timeout (default: disabled)
|
|
210
253
|
When enabled with `-T <minutes>`, the session locks after the specified period of no interaction (mouse movement, typing, etc.) and requires password re-entry. The WebSocket connection stays open, so you don't lose any terminal state.
|
|
@@ -215,11 +258,11 @@ When enabled with `-E <minutes>`, the session completely terminates after the sp
|
|
|
215
258
|
- Claude Code / Cursor Agent is stopped
|
|
216
259
|
- The server exits
|
|
217
260
|
|
|
218
|
-
Enable timeouts for security in shared environments: `
|
|
261
|
+
Enable timeouts for security in shared environments: `aigo -T 15 -E 30 claude`
|
|
219
262
|
|
|
220
263
|
## Browser Notifications
|
|
221
264
|
|
|
222
|
-
|
|
265
|
+
aigo can send browser notifications when:
|
|
223
266
|
- The AI assistant is waiting for input
|
|
224
267
|
- The WebSocket connection is lost
|
|
225
268
|
|
|
@@ -227,10 +270,11 @@ Notifications are only sent when the browser tab is not active. Allow notificati
|
|
|
227
270
|
|
|
228
271
|
## Mobile Controls
|
|
229
272
|
|
|
230
|
-
When accessing
|
|
273
|
+
When accessing aigo from a mobile device (phone or tablet), control buttons appear at the bottom of the screen:
|
|
231
274
|
|
|
232
275
|
| Button | Action | Description |
|
|
233
276
|
|--------|--------|-------------|
|
|
277
|
+
| **History** | View Buffer | Opens a fullscreen modal showing the terminal scrollback buffer (useful when TUI scrolling doesn't work on mobile) |
|
|
234
278
|
| **Mode** | `Shift+Tab` | Toggle between chat/edit modes in Claude Code or switch modes in Cursor Agent |
|
|
235
279
|
| **Enter** | `Enter` | Send Enter key to confirm prompts or submit input |
|
|
236
280
|
| **Stop** | `Ctrl+C` | Interrupt the current operation (stop running commands or cancel AI responses) |
|
|
@@ -238,6 +282,17 @@ When accessing vigo from a mobile device (phone or tablet), control buttons appe
|
|
|
238
282
|
|
|
239
283
|
These buttons are useful because mobile keyboards don't easily support modifier key combinations like Ctrl+C or Shift+Tab.
|
|
240
284
|
|
|
285
|
+
### History Button
|
|
286
|
+
|
|
287
|
+
The **History** button is particularly useful on mobile devices where terminal scrolling often doesn't work properly with TUI applications like Claude Code or Cursor Agent. When pressed:
|
|
288
|
+
|
|
289
|
+
1. Opens a fullscreen modal overlay
|
|
290
|
+
2. Captures the entire terminal scrollback buffer from tmux
|
|
291
|
+
3. Displays the content in a scrollable view with ANSI codes stripped for readability
|
|
292
|
+
4. Press **Close** or the **ESC** key to return to the terminal
|
|
293
|
+
|
|
294
|
+
This provides an alternative way to review previous AI responses and conversation history without relying on terminal scrolling.
|
|
295
|
+
|
|
241
296
|
The buttons are automatically shown based on:
|
|
242
297
|
- Touch screen capability
|
|
243
298
|
- Mobile user agent detection
|
|
@@ -246,34 +301,34 @@ The buttons are automatically shown based on:
|
|
|
246
301
|
|
|
247
302
|
## Running Persistently
|
|
248
303
|
|
|
249
|
-
To keep
|
|
304
|
+
To keep aigo running indefinitely (even after closing your terminal), run it inside tmux or use a process manager:
|
|
250
305
|
|
|
251
306
|
```bash
|
|
252
|
-
# Run
|
|
253
|
-
tmux new-session -d -s
|
|
307
|
+
# Run aigo inside its own tmux session
|
|
308
|
+
tmux new-session -d -s aigo-server 'aigo claude'
|
|
254
309
|
|
|
255
310
|
# Attach to check status
|
|
256
|
-
tmux attach -t
|
|
311
|
+
tmux attach -t aigo-server
|
|
257
312
|
|
|
258
313
|
# Or use pm2 for auto-restart on crash
|
|
259
|
-
pm2 start "
|
|
314
|
+
pm2 start "aigo claude" --name aigo-session
|
|
260
315
|
```
|
|
261
316
|
|
|
262
|
-
When the
|
|
317
|
+
When the aigo server stays running, your tunnel URL remains stable and you can return to the same URL anytime. Note: cloudflared quick tunnel URLs change on restart, while ngrok URLs persist within a session.
|
|
263
318
|
|
|
264
319
|
## Auto Port Selection
|
|
265
320
|
|
|
266
|
-
If the default port (3000) or your specified port is already in use,
|
|
321
|
+
If the default port (3000) or your specified port is already in use, aigo automatically finds the next available port:
|
|
267
322
|
|
|
268
323
|
```
|
|
269
324
|
⚠ Port 3000 in use, using port 3001 instead
|
|
270
325
|
```
|
|
271
326
|
|
|
272
|
-
This allows running multiple
|
|
327
|
+
This allows running multiple aigo sessions simultaneously without manual port management.
|
|
273
328
|
|
|
274
329
|
## Exiting and Cleanup
|
|
275
330
|
|
|
276
|
-
|
|
331
|
+
aigo provides different exit behaviors depending on how you terminate the session:
|
|
277
332
|
|
|
278
333
|
### Exit Behavior Summary
|
|
279
334
|
|
|
@@ -290,7 +345,7 @@ Click the **Exit** button in the top-right corner of the web interface to:
|
|
|
290
345
|
2. Kill the PTY process
|
|
291
346
|
3. Kill the tmux session
|
|
292
347
|
4. Close all WebSocket connections
|
|
293
|
-
5. Shut down the
|
|
348
|
+
5. Shut down the aigo server
|
|
294
349
|
6. Exit the process
|
|
295
350
|
|
|
296
351
|
**Result:** Everything is cleaned up - tmux session, server, and process all terminate. The web client shows a "Session Ended" message.
|
|
@@ -299,7 +354,7 @@ This is useful when you're completely done with a session and want to free all r
|
|
|
299
354
|
|
|
300
355
|
### Session Persistence (Ctrl+C on console)
|
|
301
356
|
|
|
302
|
-
When you press `Ctrl+C` on the terminal running
|
|
357
|
+
When you press `Ctrl+C` on the terminal running aigo:
|
|
303
358
|
1. The web server stops
|
|
304
359
|
2. The tunnel closes (if running)
|
|
305
360
|
3. **The tmux session keeps running!**
|
|
@@ -310,9 +365,9 @@ When you press `Ctrl+C` on the terminal running vigo:
|
|
|
310
365
|
Re-attach later:
|
|
311
366
|
|
|
312
367
|
```bash
|
|
313
|
-
# Via
|
|
314
|
-
|
|
315
|
-
|
|
368
|
+
# Via aigo (starts new web server for existing session)
|
|
369
|
+
aigo --attach claude-code
|
|
370
|
+
aigo --attach cursor-agent
|
|
316
371
|
|
|
317
372
|
# Or directly with tmux (local terminal only)
|
|
318
373
|
tmux attach -t claude-code
|
|
@@ -401,7 +456,7 @@ ngrok config add-authtoken YOUR_TOKEN
|
|
|
401
456
|
|
|
402
457
|
Free ngrok accounts are limited to 1 simultaneous tunnel. If you see this error, it means an ngrok process from a previous session is still running.
|
|
403
458
|
|
|
404
|
-
|
|
459
|
+
aigo will detect this and show you the command to kill it:
|
|
405
460
|
|
|
406
461
|
```
|
|
407
462
|
Error: An ngrok process is already running.
|
|
@@ -411,7 +466,7 @@ To kill the existing ngrok process, run:
|
|
|
411
466
|
kill 12345
|
|
412
467
|
|
|
413
468
|
Or to run without a tunnel:
|
|
414
|
-
|
|
469
|
+
aigo --tunnel none cursor
|
|
415
470
|
```
|
|
416
471
|
|
|
417
472
|
You can also find and kill ngrok manually:
|
|
@@ -427,6 +482,21 @@ kill <pid>
|
|
|
427
482
|
pkill -f "ngrok http"
|
|
428
483
|
```
|
|
429
484
|
|
|
485
|
+
### cloudflared connection issues
|
|
486
|
+
|
|
487
|
+
If cloudflared fails to connect:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
# Check if cloudflared is installed
|
|
491
|
+
which cloudflared
|
|
492
|
+
|
|
493
|
+
# Test a quick tunnel manually
|
|
494
|
+
cloudflared tunnel --url http://localhost:3000
|
|
495
|
+
|
|
496
|
+
# Check for firewall issues (verbose output)
|
|
497
|
+
cloudflared tunnel --url http://localhost:3000 --loglevel debug
|
|
498
|
+
```
|
|
499
|
+
|
|
430
500
|
### WebSocket connection fails
|
|
431
501
|
|
|
432
502
|
- Check that the server is running
|
|
@@ -441,13 +511,23 @@ Try resizing your browser window to trigger a terminal resize.
|
|
|
441
511
|
|
|
442
512
|
```bash
|
|
443
513
|
# Run without installing globally
|
|
444
|
-
node bin/
|
|
445
|
-
node bin/
|
|
514
|
+
node bin/aigo.js claude
|
|
515
|
+
node bin/aigo.js cursor
|
|
446
516
|
|
|
447
517
|
# Watch for changes (requires nodemon)
|
|
448
|
-
npx nodemon bin/
|
|
518
|
+
npx nodemon bin/aigo.js claude
|
|
449
519
|
```
|
|
450
520
|
|
|
521
|
+
### Publishing
|
|
522
|
+
|
|
523
|
+
Bump the minor version and publish to npm:
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
make publish-minor
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
This runs `npm version minor` (updates `package.json` and creates a git commit/tag) then `npm publish`.
|
|
530
|
+
|
|
451
531
|
## License
|
|
452
532
|
|
|
453
533
|
MIT
|
package/bin/vigo.js
CHANGED
|
@@ -21,7 +21,7 @@ function printBanner(info) {
|
|
|
21
21
|
const timeoutStr = `lock:${lockStr} exit:${exitStr}`;
|
|
22
22
|
|
|
23
23
|
const toolTitle = info.tool === 'cursor' ? 'Cursor Agent' : 'Claude Code';
|
|
24
|
-
const header = `
|
|
24
|
+
const header = `aigo - ${toolTitle} Web Terminal`;
|
|
25
25
|
|
|
26
26
|
console.log('');
|
|
27
27
|
console.log('┌────────────────────────────────────────┐');
|
|
@@ -45,13 +45,13 @@ function printBanner(info) {
|
|
|
45
45
|
|
|
46
46
|
function printHelp() {
|
|
47
47
|
console.log(`
|
|
48
|
-
|
|
48
|
+
aigo v${VERSION} - Stream Claude Code or Cursor Agent to the web
|
|
49
49
|
|
|
50
50
|
Usage:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
aigo [options] claude [claude-args]
|
|
52
|
+
aigo [options] cursor [cursor-args]
|
|
53
|
+
aigo [options] agent [agent-args]
|
|
54
|
+
aigo [options] --attach <session>
|
|
55
55
|
|
|
56
56
|
Tools:
|
|
57
57
|
claude Use Claude Code CLI
|
|
@@ -69,28 +69,28 @@ Options:
|
|
|
69
69
|
--version, -v Show version
|
|
70
70
|
|
|
71
71
|
Examples:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
aigo claude Start Claude Code with ngrok tunnel
|
|
73
|
+
aigo cursor Start Cursor Agent with ngrok tunnel
|
|
74
|
+
aigo --tunnel none claude Local only (no tunnel)
|
|
75
|
+
aigo -p 8080 -s myproject cursor Custom port and session
|
|
76
|
+
aigo claude --model sonnet Pass args to Claude
|
|
77
|
+
aigo cursor --model gpt-5 Pass args to Cursor Agent
|
|
78
|
+
aigo --attach myproject Attach to existing session
|
|
79
|
+
aigo -P mypass123 cursor Custom password
|
|
80
80
|
`);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
async function main() {
|
|
84
|
-
const {
|
|
84
|
+
const { aigoArgs, toolArgs, tool } = parseArgs(process.argv.slice(2));
|
|
85
85
|
|
|
86
86
|
// Handle help and version
|
|
87
|
-
if (
|
|
87
|
+
if (aigoArgs.help) {
|
|
88
88
|
printHelp();
|
|
89
89
|
process.exit(0);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
if (
|
|
93
|
-
console.log(`
|
|
92
|
+
if (aigoArgs.version) {
|
|
93
|
+
console.log(`aigo v${VERSION}`);
|
|
94
94
|
process.exit(0);
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -110,7 +110,7 @@ async function main() {
|
|
|
110
110
|
const toolConfig = config.tools[activeTool];
|
|
111
111
|
|
|
112
112
|
// Check if the tool CLI is installed (for non-attach mode)
|
|
113
|
-
if (!
|
|
113
|
+
if (!aigoArgs.attach && activeTool === 'cursor') {
|
|
114
114
|
if (!isAgentInstalled()) {
|
|
115
115
|
console.error('Error: Cursor Agent CLI is not installed or not in PATH');
|
|
116
116
|
console.error('');
|
|
@@ -135,9 +135,9 @@ async function main() {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
const sessionName =
|
|
139
|
-
const requestedPort =
|
|
140
|
-
const tunnelType =
|
|
138
|
+
const sessionName = aigoArgs.session || toolConfig.defaultSession;
|
|
139
|
+
const requestedPort = aigoArgs.port || config.defaultPort;
|
|
140
|
+
const tunnelType = aigoArgs.tunnel || config.defaultTunnel;
|
|
141
141
|
|
|
142
142
|
// Find available port
|
|
143
143
|
let port;
|
|
@@ -156,8 +156,8 @@ async function main() {
|
|
|
156
156
|
|
|
157
157
|
// Use custom password or generate password from config
|
|
158
158
|
let password;
|
|
159
|
-
if (
|
|
160
|
-
password =
|
|
159
|
+
if (aigoArgs.password) {
|
|
160
|
+
password = aigoArgs.password;
|
|
161
161
|
} else {
|
|
162
162
|
password = '';
|
|
163
163
|
const randomBytes = crypto.randomBytes(config.passwordLength);
|
|
@@ -167,12 +167,12 @@ async function main() {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
// Timeout settings (in minutes) - use config defaults
|
|
170
|
-
const lockTimeout =
|
|
171
|
-
const exitTimeout =
|
|
170
|
+
const lockTimeout = aigoArgs.timeout !== null ? aigoArgs.timeout : config.lockTimeout;
|
|
171
|
+
const exitTimeout = aigoArgs.exitTimeout !== null ? aigoArgs.exitTimeout : config.exitTimeout;
|
|
172
172
|
|
|
173
173
|
// Handle --attach mode
|
|
174
|
-
if (
|
|
175
|
-
const attachSession = typeof
|
|
174
|
+
if (aigoArgs.attach) {
|
|
175
|
+
const attachSession = typeof aigoArgs.attach === 'string' ? aigoArgs.attach : sessionName;
|
|
176
176
|
if (!hasSession(attachSession)) {
|
|
177
177
|
console.error(`Error: tmux session '${attachSession}' does not exist`);
|
|
178
178
|
console.error(`Create it first with: tmux new-session -s ${attachSession}`);
|
|
@@ -190,7 +190,7 @@ async function main() {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
const actualSession =
|
|
193
|
+
const actualSession = aigoArgs.attach || sessionName;
|
|
194
194
|
|
|
195
195
|
// Start web server
|
|
196
196
|
console.log(`✓ Starting web server on port ${port}...`);
|
|
@@ -223,7 +223,7 @@ async function main() {
|
|
|
223
223
|
}
|
|
224
224
|
console.error('');
|
|
225
225
|
console.error('Or to run without a tunnel:');
|
|
226
|
-
console.error('
|
|
226
|
+
console.error(' aigo --tunnel none cursor');
|
|
227
227
|
console.error('');
|
|
228
228
|
process.exit(1);
|
|
229
229
|
}
|
|
@@ -261,7 +261,7 @@ async function main() {
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
console.log(`tmux session '${actualSession}' is still running.`);
|
|
264
|
-
console.log(`Re-attach with:
|
|
264
|
+
console.log(`Re-attach with: aigo --attach ${actualSession}`);
|
|
265
265
|
console.log(`Or directly: tmux attach -t ${actualSession}`);
|
|
266
266
|
server.close();
|
|
267
267
|
process.exit(0);
|
package/lib/cli.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parse command line arguments, separating
|
|
2
|
+
* Parse command line arguments, separating aigo args from tool args
|
|
3
3
|
*
|
|
4
|
-
* Example:
|
|
5
|
-
* Example:
|
|
4
|
+
* Example: aigo --port 8080 --tunnel ngrok claude --model sonnet
|
|
5
|
+
* Example: aigo --port 8080 cursor --model gpt-5
|
|
6
6
|
* Returns:
|
|
7
|
-
*
|
|
7
|
+
* aigoArgs: { port: 8080, tunnel: 'ngrok' }
|
|
8
8
|
* toolArgs: ['--model', 'sonnet']
|
|
9
9
|
* tool: 'claude' | 'cursor' | null
|
|
10
10
|
*/
|
|
11
11
|
export function parseArgs(args) {
|
|
12
|
-
const
|
|
12
|
+
const aigoArgs = {
|
|
13
13
|
port: null,
|
|
14
14
|
session: null,
|
|
15
15
|
tunnel: null,
|
|
@@ -39,31 +39,31 @@ export function parseArgs(args) {
|
|
|
39
39
|
break;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// Parse
|
|
42
|
+
// Parse aigo options
|
|
43
43
|
if (arg === '--port' || arg === '-p') {
|
|
44
|
-
|
|
44
|
+
aigoArgs.port = parseInt(args[++i], 10);
|
|
45
45
|
} else if (arg === '--session' || arg === '-s') {
|
|
46
|
-
|
|
46
|
+
aigoArgs.session = args[++i];
|
|
47
47
|
} else if (arg === '--tunnel' || arg === '-t') {
|
|
48
|
-
|
|
48
|
+
aigoArgs.tunnel = args[++i];
|
|
49
49
|
} else if (arg === '--attach' || arg === '-a') {
|
|
50
50
|
const next = args[i + 1];
|
|
51
51
|
// Check if next arg is a session name (not another flag)
|
|
52
52
|
if (next && !next.startsWith('-')) {
|
|
53
|
-
|
|
53
|
+
aigoArgs.attach = args[++i];
|
|
54
54
|
} else {
|
|
55
|
-
|
|
55
|
+
aigoArgs.attach = true; // Use default session name
|
|
56
56
|
}
|
|
57
57
|
} else if (arg === '--password' || arg === '-P') {
|
|
58
|
-
|
|
58
|
+
aigoArgs.password = args[++i];
|
|
59
59
|
} else if (arg === '--timeout' || arg === '-T') {
|
|
60
|
-
|
|
60
|
+
aigoArgs.timeout = parseInt(args[++i], 10);
|
|
61
61
|
} else if (arg === '--exit-timeout' || arg === '-E') {
|
|
62
|
-
|
|
62
|
+
aigoArgs.exitTimeout = parseInt(args[++i], 10);
|
|
63
63
|
} else if (arg === '--help' || arg === '-h') {
|
|
64
|
-
|
|
64
|
+
aigoArgs.help = true;
|
|
65
65
|
} else if (arg === '--version' || arg === '-v') {
|
|
66
|
-
|
|
66
|
+
aigoArgs.version = true;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
i++;
|
|
@@ -71,7 +71,7 @@ export function parseArgs(args) {
|
|
|
71
71
|
|
|
72
72
|
// For backward compatibility, also export as claudeArgs/foundClaude
|
|
73
73
|
return {
|
|
74
|
-
|
|
74
|
+
aigoArgs,
|
|
75
75
|
toolArgs,
|
|
76
76
|
tool,
|
|
77
77
|
// Backward compatibility
|
package/lib/config.js
CHANGED
package/lib/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createServer } from 'http';
|
|
|
3
3
|
import { WebSocketServer } from 'ws';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
6
7
|
import pty from 'node-pty';
|
|
7
8
|
import { getTmuxPath, killSession } from './tmux.js';
|
|
8
9
|
|
|
@@ -234,6 +235,31 @@ export function startServer({ port, token, password, session, lockTimeout, exitT
|
|
|
234
235
|
}, 500);
|
|
235
236
|
return;
|
|
236
237
|
}
|
|
238
|
+
|
|
239
|
+
// Handle history capture request
|
|
240
|
+
if (parsed.type === 'capture_history') {
|
|
241
|
+
updateActivity();
|
|
242
|
+
try {
|
|
243
|
+
const tmuxBin = getTmuxPath() || 'tmux';
|
|
244
|
+
// Capture entire scrollback buffer from tmux session
|
|
245
|
+
// -p: print to stdout, -S -: start from beginning, -E -: end at bottom
|
|
246
|
+
const historyData = execSync(
|
|
247
|
+
`${tmuxBin} capture-pane -t "${session}:0" -p -S - -E -`,
|
|
248
|
+
{ encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 } // 10MB max
|
|
249
|
+
);
|
|
250
|
+
ws.send(JSON.stringify({
|
|
251
|
+
type: 'history_data',
|
|
252
|
+
data: historyData
|
|
253
|
+
}));
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error('Failed to capture history:', err.message);
|
|
256
|
+
ws.send(JSON.stringify({
|
|
257
|
+
type: 'history_error',
|
|
258
|
+
error: 'Failed to capture terminal history'
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
237
263
|
} catch {
|
|
238
264
|
// Not JSON - treat as terminal input
|
|
239
265
|
}
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
6
|
-
<title>
|
|
6
|
+
<title>aigo - Claude Code Terminal</title>
|
|
7
7
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>▸</text></svg>">
|
|
8
8
|
|
|
9
9
|
<!-- xterm.js from CDN (use older compatible CDN paths) -->
|
|
@@ -478,12 +478,126 @@
|
|
|
478
478
|
background: #ff5555;
|
|
479
479
|
color: white;
|
|
480
480
|
}
|
|
481
|
+
|
|
482
|
+
/* History button */
|
|
483
|
+
#mobile-history-btn {
|
|
484
|
+
color: #00ffff;
|
|
485
|
+
border-color: #00ffff;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
#mobile-history-btn:hover,
|
|
489
|
+
#mobile-history-btn:active {
|
|
490
|
+
background: #00ffff;
|
|
491
|
+
color: var(--bg-primary);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/* History modal overlay */
|
|
495
|
+
#history-overlay {
|
|
496
|
+
display: none;
|
|
497
|
+
position: fixed;
|
|
498
|
+
top: 0;
|
|
499
|
+
left: 0;
|
|
500
|
+
right: 0;
|
|
501
|
+
bottom: 0;
|
|
502
|
+
background: var(--bg-primary);
|
|
503
|
+
z-index: 300;
|
|
504
|
+
flex-direction: column;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
#history-overlay.visible {
|
|
508
|
+
display: flex;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.history-header {
|
|
512
|
+
display: flex;
|
|
513
|
+
align-items: center;
|
|
514
|
+
justify-content: space-between;
|
|
515
|
+
padding: 12px 16px;
|
|
516
|
+
background: var(--bg-secondary);
|
|
517
|
+
border-bottom: 1px solid var(--border);
|
|
518
|
+
flex-shrink: 0;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.history-title {
|
|
522
|
+
color: #00ffff;
|
|
523
|
+
font-size: 16px;
|
|
524
|
+
font-weight: 600;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.history-close-btn {
|
|
528
|
+
padding: 8px 16px;
|
|
529
|
+
background: transparent;
|
|
530
|
+
color: var(--text-secondary);
|
|
531
|
+
border: 1px solid var(--border);
|
|
532
|
+
border-radius: 4px;
|
|
533
|
+
font-family: inherit;
|
|
534
|
+
font-size: 13px;
|
|
535
|
+
cursor: pointer;
|
|
536
|
+
transition: all 0.2s;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.history-close-btn:hover {
|
|
540
|
+
color: var(--text-primary);
|
|
541
|
+
border-color: var(--text-primary);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.history-content {
|
|
545
|
+
flex: 1;
|
|
546
|
+
overflow-y: auto;
|
|
547
|
+
padding: 16px;
|
|
548
|
+
-webkit-overflow-scrolling: touch;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.history-text {
|
|
552
|
+
white-space: pre-wrap;
|
|
553
|
+
word-wrap: break-word;
|
|
554
|
+
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
|
|
555
|
+
font-size: 13px;
|
|
556
|
+
line-height: 1.5;
|
|
557
|
+
color: var(--text-primary);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.history-loading {
|
|
561
|
+
display: flex;
|
|
562
|
+
justify-content: center;
|
|
563
|
+
align-items: center;
|
|
564
|
+
height: 100%;
|
|
565
|
+
color: var(--text-secondary);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.history-loading .spinner {
|
|
569
|
+
width: 24px;
|
|
570
|
+
height: 24px;
|
|
571
|
+
border: 2px solid var(--border);
|
|
572
|
+
border-top-color: #00ffff;
|
|
573
|
+
border-radius: 50%;
|
|
574
|
+
animation: spin 1s linear infinite;
|
|
575
|
+
margin-right: 12px;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* Custom scrollbar for history */
|
|
579
|
+
.history-content::-webkit-scrollbar {
|
|
580
|
+
width: 8px;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.history-content::-webkit-scrollbar-track {
|
|
584
|
+
background: var(--bg-primary);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.history-content::-webkit-scrollbar-thumb {
|
|
588
|
+
background: var(--border);
|
|
589
|
+
border-radius: 4px;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.history-content::-webkit-scrollbar-thumb:hover {
|
|
593
|
+
background: #2a2a35;
|
|
594
|
+
}
|
|
481
595
|
</style>
|
|
482
596
|
</head>
|
|
483
597
|
<body>
|
|
484
598
|
<div id="app">
|
|
485
599
|
<header id="header">
|
|
486
|
-
<div class="logo">
|
|
600
|
+
<div class="logo">aigo</div>
|
|
487
601
|
<div class="header-right">
|
|
488
602
|
<div id="status" class="connecting">
|
|
489
603
|
<span class="dot"></span>
|
|
@@ -528,11 +642,27 @@
|
|
|
528
642
|
|
|
529
643
|
<!-- Mobile control buttons -->
|
|
530
644
|
<div id="mobile-controls">
|
|
645
|
+
<button id="mobile-history-btn" class="mobile-btn" title="View terminal history">History</button>
|
|
531
646
|
<button id="mobile-mode-toggle" class="mobile-btn" title="Switch mode (Shift+Tab)">Mode</button>
|
|
532
647
|
<button id="mobile-enter-btn" class="mobile-btn" title="Send Enter key">Enter</button>
|
|
533
648
|
<button id="mobile-stop-btn" class="mobile-btn" title="Stop (Ctrl+C)">Stop</button>
|
|
534
649
|
<button id="mobile-exit-btn" class="mobile-btn" title="Exit session">Exit</button>
|
|
535
650
|
</div>
|
|
651
|
+
|
|
652
|
+
<!-- History modal overlay -->
|
|
653
|
+
<div id="history-overlay">
|
|
654
|
+
<div class="history-header">
|
|
655
|
+
<div class="history-title">Terminal History</div>
|
|
656
|
+
<button id="history-close-btn" class="history-close-btn">Close</button>
|
|
657
|
+
</div>
|
|
658
|
+
<div class="history-content">
|
|
659
|
+
<div class="history-loading">
|
|
660
|
+
<div class="spinner"></div>
|
|
661
|
+
<span>Loading history...</span>
|
|
662
|
+
</div>
|
|
663
|
+
<pre class="history-text" style="display: none;"></pre>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
536
666
|
</div>
|
|
537
667
|
|
|
538
668
|
<!-- xterm.js scripts from CDN (non-scoped package names for UMD globals) -->
|
package/public/terminal.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* aigo terminal client
|
|
3
3
|
* Connects to the WebSocket server and renders the terminal
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -52,6 +52,12 @@
|
|
|
52
52
|
const mobileEnterBtn = document.getElementById('mobile-enter-btn');
|
|
53
53
|
const mobileStopBtn = document.getElementById('mobile-stop-btn');
|
|
54
54
|
const mobileExitBtn = document.getElementById('mobile-exit-btn');
|
|
55
|
+
const mobileHistoryBtn = document.getElementById('mobile-history-btn');
|
|
56
|
+
const historyOverlay = document.getElementById('history-overlay');
|
|
57
|
+
const historyCloseBtn = document.getElementById('history-close-btn');
|
|
58
|
+
const historyContent = historyOverlay.querySelector('.history-content');
|
|
59
|
+
const historyLoading = historyOverlay.querySelector('.history-loading');
|
|
60
|
+
const historyText = historyOverlay.querySelector('.history-text');
|
|
55
61
|
|
|
56
62
|
// Terminal setup
|
|
57
63
|
const terminal = new Terminal({
|
|
@@ -261,7 +267,7 @@
|
|
|
261
267
|
const notification = new Notification(title, {
|
|
262
268
|
body: body,
|
|
263
269
|
icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">▸</text></svg>',
|
|
264
|
-
tag: '
|
|
270
|
+
tag: 'aigo-notification',
|
|
265
271
|
requireInteraction: false
|
|
266
272
|
});
|
|
267
273
|
|
|
@@ -289,7 +295,7 @@
|
|
|
289
295
|
// Check patterns
|
|
290
296
|
for (const pattern of idlePatterns) {
|
|
291
297
|
if (pattern.test(outputBuffer)) {
|
|
292
|
-
showNotification('
|
|
298
|
+
showNotification('aigo', 'Claude Code is waiting for input');
|
|
293
299
|
outputBuffer = ''; // Reset to avoid repeated notifications
|
|
294
300
|
return;
|
|
295
301
|
}
|
|
@@ -453,10 +459,70 @@
|
|
|
453
459
|
}
|
|
454
460
|
});
|
|
455
461
|
|
|
456
|
-
//
|
|
462
|
+
// Strip ANSI escape codes from text for clean display
|
|
463
|
+
function stripAnsiCodes(text) {
|
|
464
|
+
// Remove ANSI escape sequences (colors, cursor movement, etc.)
|
|
465
|
+
return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
|
|
466
|
+
.replace(/\x1b\][^\x07]*\x07/g, '') // OSC sequences
|
|
467
|
+
.replace(/\x1b[PX^_].*?\x1b\\/g, '') // DCS, SOS, PM, APC sequences
|
|
468
|
+
.replace(/\x1b./g, ''); // Other escape sequences
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Show history modal
|
|
472
|
+
function showHistoryModal() {
|
|
473
|
+
historyOverlay.classList.add('visible');
|
|
474
|
+
historyLoading.style.display = 'flex';
|
|
475
|
+
historyText.style.display = 'none';
|
|
476
|
+
historyText.textContent = '';
|
|
477
|
+
|
|
478
|
+
// Request history from server
|
|
479
|
+
if (ws && ws.readyState === WebSocket.OPEN && authenticated) {
|
|
480
|
+
ws.send(JSON.stringify({ type: 'capture_history' }));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Hide history modal
|
|
485
|
+
function hideHistoryModal() {
|
|
486
|
+
historyOverlay.classList.remove('visible');
|
|
487
|
+
terminal.focus();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Display history data in the modal
|
|
491
|
+
function displayHistoryData(data) {
|
|
492
|
+
historyLoading.style.display = 'none';
|
|
493
|
+
historyText.style.display = 'block';
|
|
494
|
+
// Strip ANSI codes for clean, readable text
|
|
495
|
+
historyText.textContent = stripAnsiCodes(data);
|
|
496
|
+
// Scroll to bottom to show most recent content
|
|
497
|
+
historyContent.scrollTop = historyContent.scrollHeight;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Display history error
|
|
501
|
+
function displayHistoryError(error) {
|
|
502
|
+
historyLoading.style.display = 'none';
|
|
503
|
+
historyText.style.display = 'block';
|
|
504
|
+
historyText.textContent = `Error: ${error}`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Mobile history button handler
|
|
508
|
+
mobileHistoryBtn.addEventListener('click', () => {
|
|
509
|
+
if (ws && ws.readyState === WebSocket.OPEN && authenticated && !isLocked) {
|
|
510
|
+
mobileButtonFeedback(mobileHistoryBtn);
|
|
511
|
+
showHistoryModal();
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// History close button handler
|
|
516
|
+
historyCloseBtn.addEventListener('click', hideHistoryModal);
|
|
517
|
+
|
|
518
|
+
// ESC to close exit confirm or history
|
|
457
519
|
document.addEventListener('keydown', (e) => {
|
|
458
|
-
if (e.key === 'Escape'
|
|
459
|
-
|
|
520
|
+
if (e.key === 'Escape') {
|
|
521
|
+
if (historyOverlay.classList.contains('visible')) {
|
|
522
|
+
hideHistoryModal();
|
|
523
|
+
} else if (exitOverlay.classList.contains('visible')) {
|
|
524
|
+
hideExitConfirm();
|
|
525
|
+
}
|
|
460
526
|
}
|
|
461
527
|
});
|
|
462
528
|
|
|
@@ -606,6 +672,16 @@
|
|
|
606
672
|
showCleanupComplete();
|
|
607
673
|
return;
|
|
608
674
|
}
|
|
675
|
+
|
|
676
|
+
if (msg.type === 'history_data') {
|
|
677
|
+
displayHistoryData(msg.data);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (msg.type === 'history_error') {
|
|
682
|
+
displayHistoryError(msg.error);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
609
685
|
} catch {
|
|
610
686
|
// Not JSON, treat as terminal data
|
|
611
687
|
}
|
|
@@ -630,7 +706,7 @@
|
|
|
630
706
|
|
|
631
707
|
if (event.code !== 1000) {
|
|
632
708
|
// Notify user of unexpected disconnect
|
|
633
|
-
showNotification('
|
|
709
|
+
showNotification('aigo', 'Connection lost. Attempting to reconnect...');
|
|
634
710
|
scheduleReconnect();
|
|
635
711
|
} else {
|
|
636
712
|
showOverlay(true, 'Session ended.', true);
|
|
@@ -737,7 +813,7 @@
|
|
|
737
813
|
connect();
|
|
738
814
|
|
|
739
815
|
// Expose for debugging
|
|
740
|
-
window.
|
|
816
|
+
window.aigoTerminal = {
|
|
741
817
|
terminal,
|
|
742
818
|
fitAddon,
|
|
743
819
|
reconnect: () => {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**aigo** is a CLI tool that lets you run Claude Code remotely from any device. It wraps Claude in a tmux session and streams it to your browser via WebSocket.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
# Start Claude Code accessible from anywhere
|
|
9
|
-
|
|
9
|
+
aigo --tunnel ngrok claude --model sonnet
|
|
10
10
|
|
|
11
11
|
# Output:
|
|
12
12
|
# ✓ tmux session: claude-code
|
|
@@ -19,8 +19,8 @@ vigo --tunnel ngrok claude --model sonnet
|
|
|
19
19
|
## CLI Interface
|
|
20
20
|
|
|
21
21
|
```
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
aigo [aigo-options] claude [claude-args]
|
|
23
|
+
aigo [aigo-options] --attach <session>
|
|
24
24
|
|
|
25
25
|
Vigo Options:
|
|
26
26
|
--port, -p <port> Web server port (default: 3000)
|
|
@@ -30,19 +30,19 @@ Vigo Options:
|
|
|
30
30
|
--help, -h Show help
|
|
31
31
|
|
|
32
32
|
Examples:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
aigo claude # Basic: start claude in tmux + web server
|
|
34
|
+
aigo --tunnel ngrok claude # With ngrok tunnel
|
|
35
|
+
aigo -p 8080 -s myproject claude # Custom port and session
|
|
36
|
+
aigo claude --model sonnet # Pass args to claude
|
|
37
|
+
aigo --attach myproject # Attach to existing session
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
## Architecture
|
|
41
41
|
|
|
42
42
|
```mermaid
|
|
43
43
|
flowchart TB
|
|
44
|
-
subgraph cli [
|
|
45
|
-
CLI[bin/
|
|
44
|
+
subgraph cli [aigo CLI]
|
|
45
|
+
CLI[bin/aigo.js]
|
|
46
46
|
CLI --> TMUX_MGR[tmux manager]
|
|
47
47
|
CLI --> SERVER[web server]
|
|
48
48
|
CLI --> TUNNEL[tunnel manager]
|
|
@@ -78,10 +78,10 @@ flowchart TB
|
|
|
78
78
|
## Project Structure
|
|
79
79
|
|
|
80
80
|
```
|
|
81
|
-
|
|
81
|
+
aigo/
|
|
82
82
|
├── package.json # dependencies + bin entry
|
|
83
83
|
├── bin/
|
|
84
|
-
│ └──
|
|
84
|
+
│ └── aigo.js # CLI entry point (#!/usr/bin/env node)
|
|
85
85
|
├── lib/
|
|
86
86
|
│ ├── cli.js # Argument parsing
|
|
87
87
|
│ ├── tmux.js # tmux session management
|
|
@@ -95,25 +95,25 @@ vigo/
|
|
|
95
95
|
|
|
96
96
|
## Implementation Details
|
|
97
97
|
|
|
98
|
-
### 1. CLI Entry Point (bin/
|
|
98
|
+
### 1. CLI Entry Point (bin/aigo.js)
|
|
99
99
|
|
|
100
100
|
- Shebang for direct execution: `#!/usr/bin/env node`
|
|
101
|
-
- Parse arguments, separating
|
|
101
|
+
- Parse arguments, separating aigo args from claude args
|
|
102
102
|
- Orchestrate: tmux → server → tunnel (if requested)
|
|
103
103
|
- Handle graceful shutdown (Ctrl+C kills tunnel, keeps tmux alive)
|
|
104
104
|
|
|
105
105
|
### 2. Argument Parsing (lib/cli.js)
|
|
106
106
|
|
|
107
107
|
```javascript
|
|
108
|
-
//
|
|
108
|
+
// aigo --port 8080 --tunnel ngrok claude --model sonnet --allowedTools Bash
|
|
109
109
|
//
|
|
110
110
|
// Parsed as:
|
|
111
|
-
//
|
|
111
|
+
// aigoArgs = { port: 8080, tunnel: 'ngrok', session: 'claude-code' }
|
|
112
112
|
// claudeArgs = ['--model', 'sonnet', '--allowedTools', 'Bash']
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
- Split on `claude` keyword
|
|
116
|
-
- Everything before =
|
|
116
|
+
- Everything before = aigo options
|
|
117
117
|
- Everything after = passed to claude CLI
|
|
118
118
|
|
|
119
119
|
### 3. tmux Manager (lib/tmux.js)
|
|
@@ -164,7 +164,7 @@ console.log(`Tunnel URL: ${tunnel.url}`);
|
|
|
164
164
|
|
|
165
165
|
## Token Security
|
|
166
166
|
|
|
167
|
-
- Generated once per `
|
|
167
|
+
- Generated once per `aigo` invocation
|
|
168
168
|
- 32 bytes from `crypto.randomBytes()` → 64 char hex
|
|
169
169
|
- Embedded in URL path: `/t/<token>`
|
|
170
170
|
- Required for both HTTP and WebSocket
|
|
@@ -176,12 +176,12 @@ console.log(`Tunnel URL: ${tunnel.url}`);
|
|
|
176
176
|
# 1. Install globally (or use npx)
|
|
177
177
|
npm install -g .
|
|
178
178
|
|
|
179
|
-
# 2. Run
|
|
180
|
-
|
|
179
|
+
# 2. Run aigo with Claude
|
|
180
|
+
aigo --tunnel ngrok claude --model sonnet
|
|
181
181
|
|
|
182
182
|
# 3. Output shows access URL
|
|
183
183
|
# ┌─────────────────────────────────────────────────┐
|
|
184
|
-
# │
|
|
184
|
+
# │ aigo - Claude Code Web Terminal │
|
|
185
185
|
# ├─────────────────────────────────────────────────┤
|
|
186
186
|
# │ tmux session: claude-code │
|
|
187
187
|
# │ Local server: http://localhost:3000 │
|
|
@@ -194,18 +194,18 @@ vigo --tunnel ngrok claude --model sonnet
|
|
|
194
194
|
# 4. Open URL on phone/tablet - full Claude Code access!
|
|
195
195
|
|
|
196
196
|
# 5. Ctrl+C stops server + tunnel, tmux session persists
|
|
197
|
-
# Re-attach later with:
|
|
197
|
+
# Re-attach later with: aigo --attach claude-code
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
## Dependencies
|
|
201
201
|
|
|
202
202
|
```json
|
|
203
203
|
{
|
|
204
|
-
"name": "
|
|
204
|
+
"name": "aigo",
|
|
205
205
|
"version": "1.0.0",
|
|
206
206
|
"type": "module",
|
|
207
207
|
"bin": {
|
|
208
|
-
"
|
|
208
|
+
"aigo": "./bin/aigo.js"
|
|
209
209
|
},
|
|
210
210
|
"dependencies": {
|
|
211
211
|
"express": "^4.21.0",
|
|
@@ -226,14 +226,14 @@ No build step. Frontend loads xterm.js from CDN.
|
|
|
226
226
|
### New CLI option
|
|
227
227
|
|
|
228
228
|
```bash
|
|
229
|
-
|
|
229
|
+
aigo --tunnel relay claude # Use self-hosted WebRTC relay
|
|
230
230
|
```
|
|
231
231
|
|
|
232
232
|
### Architecture
|
|
233
233
|
|
|
234
234
|
```mermaid
|
|
235
235
|
sequenceDiagram
|
|
236
|
-
participant Local as
|
|
236
|
+
participant Local as aigo (local)
|
|
237
237
|
participant Signal as Railway Signaling Server
|
|
238
238
|
participant Browser as Mobile Browser
|
|
239
239
|
|
|
@@ -248,7 +248,7 @@ sequenceDiagram
|
|
|
248
248
|
### Components to add
|
|
249
249
|
|
|
250
250
|
1. **Signaling server** (new repo, deploy to Railway)
|
|
251
|
-
2. **WebRTC module** in
|
|
251
|
+
2. **WebRTC module** in aigo (`lib/webrtc.js`)
|
|
252
252
|
3. **Frontend WebRTC client**
|
|
253
253
|
|
|
254
254
|
### Benefits
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# Cursor CLI Support for
|
|
1
|
+
# Cursor CLI Support for aigo
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
Add support for Cursor Agent CLI alongside Claude Code, allowing
|
|
5
|
+
Add support for Cursor Agent CLI alongside Claude Code, allowing aigo to stream either tool to a browser via WebSocket. This enables users with Cursor subscriptions to access Cursor's AI features remotely.
|
|
6
6
|
|
|
7
7
|
## Architecture Comparison
|
|
8
8
|
|
|
9
9
|
```mermaid
|
|
10
10
|
flowchart TB
|
|
11
11
|
subgraph current [Current: Claude Code]
|
|
12
|
-
|
|
12
|
+
aigo1[aigo] --> tmux1[tmux session]
|
|
13
13
|
tmux1 --> claude[claude CLI]
|
|
14
14
|
claude --> ws1[WebSocket]
|
|
15
15
|
ws1 --> browser1[Browser xterm.js]
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
subgraph new [New: Cursor Agent]
|
|
19
|
-
|
|
19
|
+
aigo2[aigo] --> tmux2[tmux session]
|
|
20
20
|
tmux2 --> agent[agent CLI]
|
|
21
21
|
agent --> ws2[WebSocket]
|
|
22
22
|
ws2 --> browser2[Browser xterm.js]
|
|
@@ -95,10 +95,10 @@ export function getCursorAgentPath() {
|
|
|
95
95
|
|
|
96
96
|
**For Cursor CLI:**
|
|
97
97
|
|
|
98
|
-
1. **Browser login (recommended for interactive)**: User runs `agent login` before starting
|
|
98
|
+
1. **Browser login (recommended for interactive)**: User runs `agent login` before starting aigo
|
|
99
99
|
2. **API key (for headless/automation)**: Set `CURSOR_API_KEY` environment variable
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
aigo should:
|
|
102
102
|
|
|
103
103
|
- Check if authentication is configured before starting
|
|
104
104
|
- Provide clear error messages if not authenticated
|
|
@@ -108,18 +108,18 @@ vigo should:
|
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
110
|
# Start Claude Code (existing)
|
|
111
|
-
|
|
111
|
+
aigo claude
|
|
112
112
|
|
|
113
113
|
# Start Cursor Agent (new)
|
|
114
|
-
|
|
114
|
+
aigo cursor
|
|
115
115
|
# or
|
|
116
|
-
|
|
116
|
+
aigo agent
|
|
117
117
|
|
|
118
118
|
# With tunnel for remote access
|
|
119
|
-
|
|
119
|
+
aigo --tunnel ngrok cursor
|
|
120
120
|
|
|
121
121
|
# Pass arguments to Cursor Agent
|
|
122
|
-
|
|
122
|
+
aigo cursor --model gpt-5
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
## Requirements Update
|
|
@@ -134,6 +134,6 @@ Add to README:
|
|
|
134
134
|
|
|
135
135
|
1. [lib/cli.js](lib/cli.js) - Add cursor/agent command detection
|
|
136
136
|
2. [lib/tmux.js](lib/tmux.js) - Generalize command execution
|
|
137
|
-
3. [bin/
|
|
137
|
+
3. [bin/aigo.js](bin/aigo.js) - Update startup logic for both tools
|
|
138
138
|
4. [README.md](README.md) - Document Cursor support
|
|
139
139
|
5. [lib/cursor.js](lib/cursor.js) - Cursor-specific utilities
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# History View Button for Mobile
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
Terminal scrolling doesn't work well on mobile devices with TUI applications like Claude Code and Cursor Agent, making it difficult to view previous messages.
|
|
5
|
+
|
|
6
|
+
## Solution
|
|
7
|
+
Add a "History" button that opens a modal overlay displaying the terminal scrollback buffer captured directly from tmux using `tmux capture-pane`.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
sequenceDiagram
|
|
13
|
+
participant User
|
|
14
|
+
participant Frontend as WebClient
|
|
15
|
+
participant Server as Node Server
|
|
16
|
+
participant Tmux
|
|
17
|
+
|
|
18
|
+
User->>Frontend: Clicks History button
|
|
19
|
+
Frontend->>Server: WebSocket message type: "capture_history"
|
|
20
|
+
Server->>Tmux: capture-pane -t session:0 -p -S - -E -
|
|
21
|
+
Tmux-->>Server: Scrollback buffer text
|
|
22
|
+
Server-->>Frontend: WebSocket message type: "history_data"
|
|
23
|
+
Frontend->>User: Shows modal with scrollable history
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Implementation
|
|
27
|
+
|
|
28
|
+
### 1. Backend - Add history capture endpoint ([lib/server.js](lib/server.js))
|
|
29
|
+
|
|
30
|
+
Add a new WebSocket message handler for `capture_history` that runs:
|
|
31
|
+
```bash
|
|
32
|
+
tmux capture-pane -t <session>:0 -p -S - -E -
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This captures the entire scrollback buffer (tested and confirmed working - returns all terminal output including Claude/Cursor responses).
|
|
36
|
+
|
|
37
|
+
### 2. Frontend - Add History button ([public/index.html](public/index.html))
|
|
38
|
+
|
|
39
|
+
Add a new mobile button styled like the existing controls:
|
|
40
|
+
- Button label: "History"
|
|
41
|
+
- Color: Use a distinct color (cyan `#00ffff`)
|
|
42
|
+
- Position: Add to the existing mobile controls row
|
|
43
|
+
|
|
44
|
+
### 3. Frontend - Add modal overlay ([public/index.html](public/index.html))
|
|
45
|
+
|
|
46
|
+
Create a fullscreen modal that:
|
|
47
|
+
- Has a dark background matching the terminal theme
|
|
48
|
+
- Contains a scrollable text area with monospace font
|
|
49
|
+
- Has a "Close" button at the top
|
|
50
|
+
- Strips ANSI escape codes for clean display
|
|
51
|
+
|
|
52
|
+
### 4. Frontend - Handle history messages ([public/terminal.js](public/terminal.js))
|
|
53
|
+
|
|
54
|
+
- Send `capture_history` request when History button is clicked
|
|
55
|
+
- Receive `history_data` response and display in modal
|
|
56
|
+
- Strip ANSI escape codes for readable plain text display
|
|
57
|
+
|
|
58
|
+
## Key Files Modified
|
|
59
|
+
- [lib/server.js](lib/server.js) - Add tmux capture-pane handler
|
|
60
|
+
- [public/index.html](public/index.html) - Add History button and modal HTML/CSS
|
|
61
|
+
- [public/terminal.js](public/terminal.js) - Add click handler and modal logic
|
|
62
|
+
- [README.md](README.md) - Document the new History feature
|
|
63
|
+
|
|
64
|
+
## Status: Completed
|