aigo 1.0.1 → 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/Makefile ADDED
@@ -0,0 +1,8 @@
1
+ .PHONY: publish-minor publish-patch
2
+ publish-minor:
3
+ npm version minor
4
+ npm publish
5
+
6
+ publish-patch:
7
+ npm version patch
8
+ npm publish
package/README.md CHANGED
@@ -29,7 +29,8 @@ aigo --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** (required for default remote access) - sign up at [ngrok.com](https://ngrok.com)
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
@@ -144,13 +162,38 @@ 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
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 tunnel for remote access (default)
196
+ 5. A tunnel (ngrok or cloudflared) creates a public HTTPS URL for remote access
154
197
 
155
198
  ```
156
199
  ┌────────────────────────────────────────┐
@@ -194,10 +237,10 @@ aigo 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 ngrok (encrypted in transit)
240
+ - HTTPS via tunnel (encrypted in transit)
198
241
 
199
242
  **Recommendations:**
200
- - Use HTTPS (ngrok provides this automatically)
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
@@ -231,6 +274,7 @@ When accessing aigo from a mobile device (phone or tablet), control buttons appe
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 aigo 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
@@ -259,7 +314,7 @@ tmux attach -t aigo-server
259
314
  pm2 start "aigo claude" --name aigo-session
260
315
  ```
261
316
 
262
- When the aigo server stays running, your ngrok URL remains stable and you can return to the same URL anytime.
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
 
@@ -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
@@ -448,6 +518,17 @@ node bin/aigo.js cursor
448
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 # bump minor (e.g. 1.0.1 → 1.1.0), then publish
527
+ make publish-patch # bump patch (e.g. 1.0.1 → 1.0.2), then publish
528
+ ```
529
+
530
+ Each runs `npm version minor` or `npm version patch` (updates `package.json` and creates a git commit/tag) then `npm publish`.
531
+
451
532
  ## License
452
533
 
453
534
  MIT
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aigo",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Stream Claude Code to the web - run Claude remotely from any device",
5
5
  "type": "module",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -478,6 +478,120 @@
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>
@@ -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) -->
@@ -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({
@@ -453,10 +459,70 @@
453
459
  }
454
460
  });
455
461
 
456
- // ESC to close exit confirm
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' && exitOverlay.classList.contains('visible')) {
459
- hideExitConfirm();
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
  }
@@ -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