minivibe 0.1.0 → 0.1.2

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.
@@ -0,0 +1,241 @@
1
+ # Getting Started with MiniVibe CLI
2
+
3
+ This guide walks you through setting up MiniVibe CLI to control Claude Code from your iOS device.
4
+
5
+ ## Prerequisites
6
+
7
+ Before you begin, ensure you have:
8
+
9
+ 1. **Node.js 18+** installed
10
+ 2. **Claude Code CLI** installed and working (`claude --version`)
11
+ 3. **MiniVibe iOS app** installed on your iPhone/iPad
12
+ 4. **Python 3** (macOS/Linux) or **node-pty** (Windows)
13
+
14
+ ## Step 1: Install MiniVibe CLI
15
+
16
+ ```bash
17
+ npm install -g minivibe
18
+ ```
19
+
20
+ Verify installation:
21
+
22
+ ```bash
23
+ vibe --help
24
+ vibe-agent --help
25
+ ```
26
+
27
+ ## Step 2: Authenticate
28
+
29
+ ### On Desktop/Laptop (with browser)
30
+
31
+ ```bash
32
+ vibe --login
33
+ ```
34
+
35
+ This opens your browser for Google sign-in. After signing in, authentication is saved automatically.
36
+
37
+ ### On Server/EC2 (headless)
38
+
39
+ ```bash
40
+ vibe --login --headless
41
+ ```
42
+
43
+ You'll see output like:
44
+
45
+ ```
46
+ Requesting device code...
47
+ Visit: https://ws.neng.ai/device
48
+ Code: ABC123
49
+
50
+ Code expires in 5 minutes.
51
+ Waiting for authentication...
52
+ ```
53
+
54
+ 1. On your phone or another computer, visit the URL shown
55
+ 2. Enter the code
56
+ 3. Sign in with Google
57
+ 4. The CLI will automatically detect the login and save your token
58
+
59
+ ## Step 3: Choose Your Setup
60
+
61
+ ### Option A: Direct Mode (Simple)
62
+
63
+ Best for: Single session, desktop use
64
+
65
+ ```bash
66
+ vibe --bridge wss://ws.neng.ai
67
+ ```
68
+
69
+ ### Option B: Agent Mode (Recommended for Servers)
70
+
71
+ Best for: EC2, multiple sessions, persistent connection
72
+
73
+ **Terminal 1 - Start the agent:**
74
+
75
+ ```bash
76
+ vibe-agent --bridge wss://ws.neng.ai
77
+ ```
78
+
79
+ Keep this running. The agent maintains the bridge connection and manages all sessions.
80
+
81
+ **Terminal 2+ - Create sessions:**
82
+
83
+ ```bash
84
+ vibe --agent
85
+ ```
86
+
87
+ ## Step 4: Connect from iOS
88
+
89
+ 1. Open the **MiniVibe** app on your iOS device
90
+ 2. Sign in with the same Google account
91
+ 3. Your sessions will appear automatically
92
+ 4. Tap a session to view, send messages, and approve permissions
93
+
94
+ ## Common Workflows
95
+
96
+ ### Basic Development Session
97
+
98
+ ```bash
99
+ # Start a named session
100
+ vibe --agent --name "Feature: User Auth"
101
+
102
+ # Claude will start, you can work locally
103
+ # Monitor and control from iOS app
104
+ ```
105
+
106
+ ### Automated Tasks
107
+
108
+ ```bash
109
+ # Skip all permission prompts (use with caution!)
110
+ vibe --agent --dangerously-skip-permissions "Run all tests and fix failures"
111
+ ```
112
+
113
+ ### Resume Previous Session
114
+
115
+ ```bash
116
+ # Find session ID from iOS app or previous output
117
+ vibe --agent --resume abc12345-6789-...
118
+ ```
119
+
120
+ ## Running Agent as a Service
121
+
122
+ ### Using systemd (Linux)
123
+
124
+ Create `/etc/systemd/system/vibe-agent.service`:
125
+
126
+ ```ini
127
+ [Unit]
128
+ Description=MiniVibe Agent
129
+ After=network.target
130
+
131
+ [Service]
132
+ Type=simple
133
+ User=ubuntu
134
+ WorkingDirectory=/home/ubuntu
135
+ ExecStart=/usr/bin/vibe-agent --bridge wss://ws.neng.ai
136
+ Restart=always
137
+ RestartSec=10
138
+
139
+ [Install]
140
+ WantedBy=multi-user.target
141
+ ```
142
+
143
+ Enable and start:
144
+
145
+ ```bash
146
+ sudo systemctl enable vibe-agent
147
+ sudo systemctl start vibe-agent
148
+ ```
149
+
150
+ ### Using tmux/screen
151
+
152
+ ```bash
153
+ # Start in detached tmux session
154
+ tmux new-session -d -s vibe-agent "vibe-agent --bridge wss://ws.neng.ai"
155
+
156
+ # Attach to view logs
157
+ tmux attach -t vibe-agent
158
+ ```
159
+
160
+ ### Using pm2
161
+
162
+ ```bash
163
+ npm install -g pm2
164
+ pm2 start vibe-agent -- --bridge wss://ws.neng.ai
165
+ pm2 save
166
+ pm2 startup
167
+ ```
168
+
169
+ ## Troubleshooting
170
+
171
+ ### "Session file not found"
172
+
173
+ This is normal during startup. The session file is created when Claude starts processing. If it persists:
174
+
175
+ 1. Check Claude Code is installed: `claude --version`
176
+ 2. Check the projects directory exists: `ls ~/.claude/projects/`
177
+
178
+ ### "Authentication failed"
179
+
180
+ Token may have expired. Re-authenticate:
181
+
182
+ ```bash
183
+ vibe --logout
184
+ vibe --login --headless
185
+ ```
186
+
187
+ ### Agent not discovered
188
+
189
+ If `vibe --agent` can't find the agent:
190
+
191
+ ```bash
192
+ # Specify agent URL explicitly
193
+ vibe --agent ws://localhost:9999
194
+
195
+ # Check agent is running
196
+ ps aux | grep vibe-agent
197
+ ```
198
+
199
+ ### Permission prompts not appearing on iOS
200
+
201
+ 1. Ensure you're signed into the same Google account on both CLI and iOS
202
+ 2. Check the session is "active" in the iOS app
203
+ 3. Verify bridge connection: look for "Connected to bridge" in CLI output
204
+
205
+ ## Architecture Overview
206
+
207
+ ```
208
+ Your Mac/PC Cloud Your iPhone
209
+ ┌─────────────────┐ ┌─────────────┐
210
+ │ │ │ │
211
+ │ Terminal 1 │ │ MiniVibe │
212
+ │ ┌───────────┐ │ ┌─────────────────┐ │ App │
213
+ │ │vibe-agent │──┼────▶│ Bridge Server │◀─────────────────│ │
214
+ │ └───────────┘ │ │ ws.neng.ai │ │ - View │
215
+ │ ▲ │ └─────────────────┘ │ - Chat │
216
+ │ │ │ │ - Approve │
217
+ │ Terminal 2 │ │ │
218
+ │ ┌───────────┐ │ └─────────────┘
219
+ │ │ vibe │──┘
220
+ │ │ + Claude │
221
+ │ └───────────┘
222
+ │ │
223
+ └─────────────────┘
224
+ ```
225
+
226
+ 1. **vibe-agent** connects to the bridge server and stays connected
227
+ 2. **vibe --agent** connects to the local agent, which spawns Claude Code
228
+ 3. Messages flow: iOS ↔ Bridge ↔ Agent ↔ vibe ↔ Claude
229
+ 4. Permissions appear on iOS for approval
230
+
231
+ ## Next Steps
232
+
233
+ - Explore session management in the iOS app
234
+ - Set up multiple named sessions for different projects
235
+ - Configure auto-start on your development servers
236
+ - Check token usage in iOS Settings
237
+
238
+ ## Getting Help
239
+
240
+ - GitHub Issues: https://github.com/python3isfun/neng/issues
241
+ - Check `vibe --help` and `vibe-agent --help` for all options
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # MiniVibe CLI
2
+
3
+ CLI wrapper for Claude Code with mobile remote control via MiniVibe iOS app.
4
+
5
+ ## Features
6
+
7
+ - Remote control Claude Code from your iOS device
8
+ - Agent mode for managing multiple sessions
9
+ - Session management with resume capability
10
+ - Permission handling from mobile
11
+ - Token usage tracking
12
+ - Headless authentication for servers (EC2, etc.)
13
+ - Skip permissions mode for automation
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Install globally from npm
19
+ npm install -g minivibe
20
+
21
+ # Authenticate (one-time)
22
+ vibe --login # Desktop (opens browser)
23
+ vibe --login --headless # Server/EC2 (device code)
24
+
25
+ # Option 1: Direct bridge connection
26
+ vibe --bridge wss://ws.neng.ai
27
+
28
+ # Option 2: Agent mode (recommended for servers)
29
+ vibe-agent --bridge wss://ws.neng.ai & # Start agent
30
+ vibe --agent # Create sessions
31
+ ```
32
+
33
+ See [GETTING_STARTED.md](GETTING_STARTED.md) for detailed setup instructions.
34
+
35
+ ## Installation
36
+
37
+ ### From npm (Recommended)
38
+
39
+ ```bash
40
+ npm install -g minivibe
41
+ ```
42
+
43
+ This installs two commands:
44
+ - `vibe` - Start Claude Code sessions
45
+ - `vibe-agent` - Background agent for managing sessions
46
+
47
+ ### From Source
48
+
49
+ ```bash
50
+ git clone https://github.com/python3isfun/neng.git
51
+ cd neng/vibe-cli
52
+ npm install
53
+ npm link
54
+ ```
55
+
56
+ ## Authentication
57
+
58
+ ### Browser Login (Desktop)
59
+
60
+ ```bash
61
+ vibe --login
62
+ ```
63
+
64
+ Opens browser for Google sign-in. Token is saved to `~/.vibe/auth.json`.
65
+
66
+ ### Headless Login (Servers/EC2)
67
+
68
+ ```bash
69
+ vibe --login --headless
70
+ ```
71
+
72
+ Displays a device code. Visit the URL on any device to authenticate.
73
+
74
+ ### Manual Token
75
+
76
+ ```bash
77
+ vibe --token <firebase-token>
78
+ ```
79
+
80
+ Get token from MiniVibe iOS app: Settings > Copy Token for CLI.
81
+
82
+ ## Usage Modes
83
+
84
+ ### Direct Bridge Mode
85
+
86
+ Connect directly to the bridge server:
87
+
88
+ ```bash
89
+ vibe --bridge wss://ws.neng.ai
90
+ vibe --bridge wss://ws.neng.ai "Fix the bug in main.js"
91
+ ```
92
+
93
+ ### Agent Mode (Recommended for Servers)
94
+
95
+ Use a local agent to manage sessions:
96
+
97
+ ```bash
98
+ # Terminal 1: Start the agent (runs continuously)
99
+ vibe-agent --bridge wss://ws.neng.ai
100
+
101
+ # Terminal 2+: Create sessions via agent
102
+ vibe --agent
103
+ vibe --agent "Deploy the application"
104
+ vibe --agent --name "Backend Work"
105
+ ```
106
+
107
+ **Benefits of Agent Mode:**
108
+ - Single bridge connection for multiple sessions
109
+ - Start/stop sessions remotely from iOS app
110
+ - Sessions survive network hiccups
111
+ - Cleaner process management
112
+
113
+ ### Local Mode (No Bridge)
114
+
115
+ Run without remote control:
116
+
117
+ ```bash
118
+ vibe # Interactive
119
+ vibe "Explain this code" # With prompt
120
+ ```
121
+
122
+ ## Options
123
+
124
+ ### vibe
125
+
126
+ | Option | Description |
127
+ |--------|-------------|
128
+ | `--bridge <url>` | Connect to bridge server |
129
+ | `--agent [url]` | Connect via local vibe-agent (default: auto-discover) |
130
+ | `--name <name>` | Name this session (shown in mobile app) |
131
+ | `--resume <id>` | Resume a previous session |
132
+ | `--login` | Sign in with Google |
133
+ | `--headless` | Use device code flow for headless environments |
134
+ | `--token <token>` | Set Firebase auth token manually |
135
+ | `--logout` | Remove stored auth token |
136
+ | `--dangerously-skip-permissions` | Auto-approve all tool executions |
137
+ | `--node-pty` | Use Node.js PTY wrapper (required for Windows) |
138
+ | `--help, -h` | Show help message |
139
+
140
+ ### vibe-agent
141
+
142
+ | Option | Description |
143
+ |--------|-------------|
144
+ | `--bridge <url>` | Bridge server URL (required) |
145
+ | `--port <port>` | Local WebSocket port (default: 9999) |
146
+ | `--help, -h` | Show help message |
147
+
148
+ ## In-Session Commands
149
+
150
+ | Command | Description |
151
+ |---------|-------------|
152
+ | `/name <name>` | Rename the current session |
153
+ | `//` | Type literal `/` (escape sequence) |
154
+ | `Escape` | Cancel command mode, forward to Claude |
155
+ | `Ctrl+C` | Cancel command mode, forward to Claude |
156
+
157
+ ## Skip Permissions Mode
158
+
159
+ For automated/headless environments where you trust the execution context:
160
+
161
+ ```bash
162
+ vibe --dangerously-skip-permissions --bridge wss://ws.neng.ai
163
+ vibe --dangerously-skip-permissions --agent
164
+ ```
165
+
166
+ **Warning:** This mode auto-approves ALL tool executions (commands, file writes, etc.) without prompting. Only use in trusted/sandboxed environments.
167
+
168
+ ## Architecture
169
+
170
+ ```
171
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
172
+ │ MiniVibe │────▶│ Bridge │◀────│ vibe-agent │◀────│ vibe │
173
+ │ iOS App │ │ Server │ │ (daemon) │ │ (session) │
174
+ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
175
+ ```
176
+
177
+ **Direct mode:** `vibe --bridge` connects directly to bridge server
178
+
179
+ **Agent mode:** `vibe --agent` connects to local `vibe-agent`, which manages bridge connection
180
+
181
+ ## Requirements
182
+
183
+ - Node.js >= 18.0.0
184
+ - Claude Code CLI installed and in PATH
185
+ - Python 3 (for Unix PTY wrapper) or node-pty (for Windows)
186
+
187
+ ## Platform Notes
188
+
189
+ ### macOS/Linux
190
+
191
+ Uses Python PTY wrapper by default. Requires `python3`.
192
+
193
+ ### Windows
194
+
195
+ Requires `node-pty`:
196
+
197
+ ```bash
198
+ npm install node-pty
199
+ ```
200
+
201
+ May also need Visual Studio Build Tools and Python for native compilation.
202
+
203
+ ## Files
204
+
205
+ | Path | Description |
206
+ |------|-------------|
207
+ | `~/.vibe/auth.json` | Stored authentication (token + refresh token) |
208
+ | `~/.vibe/token` | Legacy token file |
209
+ | `~/.vibe-agent/port` | Agent port file for auto-discovery |
210
+
211
+ ## License
212
+
213
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minivibe",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "CLI wrapper for Claude Code with mobile remote control",
5
5
  "author": "neng.ai",
6
6
  "homepage": "https://github.com/python3isfun/neng",
@@ -21,7 +21,9 @@
21
21
  "login.html",
22
22
  "pty-wrapper.py",
23
23
  "pty-wrapper-node.js",
24
- "agent/"
24
+ "agent/",
25
+ "README.md",
26
+ "GETTING_STARTED.md"
25
27
  ],
26
28
  "engines": {
27
29
  "node": ">=18.0.0"
package/vibe.js CHANGED
@@ -382,6 +382,7 @@ let authToken = null;
382
382
  let headlessMode = false;
383
383
  let sessionName = null;
384
384
  let useNodePty = os.platform() === 'win32'; // Auto-detect Windows, can be overridden
385
+ let skipPermissions = false; // --dangerously-skip-permissions mode
385
386
 
386
387
  for (let i = 0; i < args.length; i++) {
387
388
  if (args[i] === '--help' || args[i] === '-h') {
@@ -407,10 +408,12 @@ Options:
407
408
  --token <token> Set Firebase auth token manually
408
409
  --logout Remove stored auth token
409
410
  --node-pty Use Node.js PTY wrapper (required for Windows, optional for Unix)
411
+ --dangerously-skip-permissions Run Claude without permission prompts (use with caution!)
410
412
  --help, -h Show this help message
411
413
 
412
414
  In-Session Commands:
413
415
  /name <name> Rename the current session
416
+ // Type literal '/' (escape sequence)
414
417
 
415
418
  Authentication:
416
419
  Use --login to sign in via browser, or get token from MiniVibe iOS app.
@@ -466,6 +469,8 @@ Examples:
466
469
  process.exit(0);
467
470
  } else if (args[i] === '--node-pty') {
468
471
  useNodePty = true;
472
+ } else if (args[i] === '--dangerously-skip-permissions') {
473
+ skipPermissions = true;
469
474
  } else if (!args[i].startsWith('--')) {
470
475
  initialPrompt = args[i];
471
476
  }
@@ -534,12 +539,50 @@ function logStatus(msg) {
534
539
 
535
540
  // Get Claude session file path
536
541
  // Claude uses path with '/' replaced by '-' (not base64)
542
+ // But the exact format may vary - we try multiple strategies
537
543
  function getSessionFilePath() {
538
- const projectPathHash = process.cwd().replace(/\//g, '-');
539
- const claudeDir = path.join(os.homedir(), '.claude', 'projects', projectPathHash);
540
- return path.join(claudeDir, `${sessionId}.jsonl`);
544
+ const cwd = process.cwd();
545
+ const projectsDir = path.join(os.homedir(), '.claude', 'projects');
546
+
547
+ // Strategy 1: Direct replacement (e.g., /home/ubuntu -> -home-ubuntu)
548
+ const directHash = cwd.replace(/\//g, '-');
549
+
550
+ // Strategy 2: Without leading dash (e.g., home-ubuntu)
551
+ const noLeadingDash = directHash.replace(/^-/, '');
552
+
553
+ // Strategy 3: URL-safe encoding variations
554
+ const candidates = [directHash, noLeadingDash];
555
+
556
+ // Try to find existing directory that matches
557
+ if (fs.existsSync(projectsDir)) {
558
+ for (const candidate of candidates) {
559
+ const candidateDir = path.join(projectsDir, candidate);
560
+ if (fs.existsSync(candidateDir)) {
561
+ return path.join(candidateDir, `${sessionId}.jsonl`);
562
+ }
563
+ }
564
+
565
+ // Scan for session file in any project directory
566
+ try {
567
+ const dirs = fs.readdirSync(projectsDir);
568
+ for (const dir of dirs) {
569
+ const sessionFile = path.join(projectsDir, dir, `${sessionId}.jsonl`);
570
+ if (fs.existsSync(sessionFile)) {
571
+ return sessionFile;
572
+ }
573
+ }
574
+ } catch (err) {
575
+ // Ignore scan errors
576
+ }
577
+ }
578
+
579
+ // Fall back to direct hash (will be created by Claude)
580
+ return path.join(projectsDir, directHash, `${sessionId}.jsonl`);
541
581
  }
542
582
 
583
+ // Cache for discovered session file path
584
+ let discoveredSessionFile = null;
585
+
543
586
  // Get local IP
544
587
  function getLocalIP() {
545
588
  const interfaces = os.networkInterfaces();
@@ -910,28 +953,41 @@ function sendToBridge(data) {
910
953
  // ====================
911
954
 
912
955
  function startSessionFileWatcher() {
913
- const sessionFile = getSessionFilePath();
914
- logStatus(`Watching session file: ${sessionFile}`);
956
+ let sessionFile = getSessionFilePath();
957
+ logStatus(`Watching for session file: ${sessionFile}`);
915
958
 
916
- // Also check what files actually exist in the projects directory
917
959
  const projectsDir = path.join(os.homedir(), '.claude', 'projects');
918
- if (fs.existsSync(projectsDir)) {
919
- const dirs = fs.readdirSync(projectsDir);
920
- logStatus(`Found ${dirs.length} project directories in ${projectsDir}`);
921
- // Look for our session in any directory
922
- for (const dir of dirs.slice(0, 5)) { // Check first 5
923
- const fullDir = path.join(projectsDir, dir);
924
- if (fs.statSync(fullDir).isDirectory()) {
925
- const files = fs.readdirSync(fullDir);
926
- const sessionFiles = files.filter(f => f.includes(sessionId.slice(0, 8)));
927
- if (sessionFiles.length > 0) {
928
- logStatus(`Found session files in ${dir}: ${sessionFiles.join(', ')}`);
960
+ let fileCheckCount = 0;
961
+ let lastNotFoundLog = 0;
962
+ let fileFound = false;
963
+
964
+ // Function to scan for session file in all project directories
965
+ function scanForSessionFile() {
966
+ if (!fs.existsSync(projectsDir)) return null;
967
+
968
+ try {
969
+ const dirs = fs.readdirSync(projectsDir);
970
+ // Limit scan to 50 directories to avoid slowdown
971
+ const dirsToScan = dirs.slice(0, 50);
972
+ for (const dir of dirsToScan) {
973
+ const fullDir = path.join(projectsDir, dir);
974
+ try {
975
+ if (fs.statSync(fullDir).isDirectory()) {
976
+ const candidateFile = path.join(fullDir, `${sessionId}.jsonl`);
977
+ if (fs.existsSync(candidateFile)) {
978
+ return candidateFile;
979
+ }
980
+ }
981
+ } catch (e) {
982
+ // Skip inaccessible directories
929
983
  }
930
984
  }
985
+ } catch (err) {
986
+ // Ignore scan errors
931
987
  }
988
+ return null;
932
989
  }
933
990
 
934
- let fileCheckCount = 0;
935
991
  const pollInterval = setInterval(() => {
936
992
  if (!isRunning || isShuttingDown) {
937
993
  clearInterval(pollInterval);
@@ -939,15 +995,46 @@ function startSessionFileWatcher() {
939
995
  }
940
996
 
941
997
  try {
942
- if (!fs.existsSync(sessionFile)) {
943
- // Log occasionally that file doesn't exist yet
944
- if (fileCheckCount++ % 20 === 0) {
945
- logStatus(`Session file not found yet: ${sessionFile}`);
998
+ // If file not found yet, try to discover it
999
+ if (!fileFound) {
1000
+ if (!fs.existsSync(sessionFile)) {
1001
+ // Try scanning for it every 5 seconds
1002
+ fileCheckCount++;
1003
+ if (fileCheckCount % 10 === 0) {
1004
+ const discovered = scanForSessionFile();
1005
+ if (discovered) {
1006
+ sessionFile = discovered;
1007
+ fileFound = true;
1008
+ lastFileSize = 0; // Reset to read from beginning
1009
+ log(`📁 Found session file: ${sessionFile}`, colors.dim);
1010
+ } else {
1011
+ // Only log "not found" every 30 seconds to reduce spam
1012
+ const now = Date.now();
1013
+ if (now - lastNotFoundLog > 30000) {
1014
+ logStatus(`Waiting for session file... (${Math.floor(fileCheckCount / 2)}s)`);
1015
+ lastNotFoundLog = now;
1016
+ }
1017
+ }
1018
+ }
1019
+ return;
946
1020
  }
1021
+ fileFound = true;
1022
+ lastFileSize = 0; // Reset to read from beginning
1023
+ log(`📁 Session file ready: ${sessionFile}`, colors.dim);
1024
+ }
1025
+
1026
+ if (!fs.existsSync(sessionFile)) {
947
1027
  return;
948
1028
  }
949
1029
 
950
1030
  const stats = fs.statSync(sessionFile);
1031
+
1032
+ // Handle file being recreated (size shrunk)
1033
+ if (stats.size < lastFileSize) {
1034
+ logStatus(`Session file was recreated, re-reading from start`);
1035
+ lastFileSize = 0;
1036
+ }
1037
+
951
1038
  if (stats.size > lastFileSize) {
952
1039
  logStatus(`Session file changed: ${lastFileSize} -> ${stats.size} bytes`);
953
1040
  const fd = fs.openSync(sessionFile, 'r');
@@ -1277,6 +1364,12 @@ function startClaude() {
1277
1364
  logStatus(`Starting new Claude session: ${sessionId.slice(0, 8)}...`);
1278
1365
  }
1279
1366
 
1367
+ // Add --dangerously-skip-permissions if requested
1368
+ if (skipPermissions) {
1369
+ claudeArgs.push('--dangerously-skip-permissions');
1370
+ log('⚠️ Running in skip-permissions mode (no prompts)', colors.yellow);
1371
+ }
1372
+
1280
1373
  // Choose PTY wrapper based on platform/flag
1281
1374
  // - Windows: always use node-pty (Python PTY doesn't work)
1282
1375
  // - Unix: use Python by default, node-pty with --node-pty flag
@@ -1443,11 +1536,6 @@ function setupTerminalInput() {
1443
1536
  }
1444
1537
  process.stdin.resume();
1445
1538
  process.stdin.on('data', (data) => {
1446
- // Debug: log what bytes the keyboard sends (only when not in command mode)
1447
- if (data.length <= 4 && !inCommandMode) {
1448
- logStatus(`Keyboard input: ${data.length} bytes, hex: ${data.toString('hex')}`);
1449
- }
1450
-
1451
1539
  const str = data.toString();
1452
1540
 
1453
1541
  // Check for command mode
@@ -1476,17 +1564,36 @@ function setupTerminalInput() {
1476
1564
  }
1477
1565
  }
1478
1566
 
1479
- // Ctrl+C or Escape in command mode - cancel command
1480
- if ((code === 0x03 || code === 0x1b) && inCommandMode) {
1481
- // Clear the echoed text
1482
- for (let i = 0; i < commandBuffer.length; i++) {
1483
- process.stdout.write('\b \b');
1567
+ // Ctrl+C - always forward to Claude (cancel any prompt)
1568
+ if (code === 0x03) {
1569
+ if (inCommandMode) {
1570
+ // Clear the echoed command text
1571
+ for (let i = 0; i < commandBuffer.length; i++) {
1572
+ process.stdout.write('\b \b');
1573
+ }
1574
+ commandBuffer = '';
1575
+ inCommandMode = false;
1576
+ }
1577
+ // Always forward Ctrl+C to Claude
1578
+ if (claudeProcess && isRunning && claudeProcess.stdin && claudeProcess.stdin.writable) {
1579
+ claudeProcess.stdin.write('\x03');
1484
1580
  }
1485
- commandBuffer = '';
1486
- inCommandMode = false;
1487
- if (code === 0x03) {
1488
- // Forward Ctrl+C to Claude if not in command mode
1489
- // (but we just exited, so do nothing)
1581
+ continue;
1582
+ }
1583
+
1584
+ // Escape - exit command mode and forward to Claude
1585
+ if (code === 0x1b) {
1586
+ if (inCommandMode) {
1587
+ // Clear the echoed command text
1588
+ for (let i = 0; i < commandBuffer.length; i++) {
1589
+ process.stdout.write('\b \b');
1590
+ }
1591
+ commandBuffer = '';
1592
+ inCommandMode = false;
1593
+ }
1594
+ // Always forward Escape to Claude (for canceling prompts)
1595
+ if (claudeProcess && isRunning && claudeProcess.stdin && claudeProcess.stdin.writable) {
1596
+ claudeProcess.stdin.write('\x1b');
1490
1597
  }
1491
1598
  continue;
1492
1599
  }
@@ -1515,6 +1622,17 @@ function setupTerminalInput() {
1515
1622
 
1516
1623
  // In command mode - buffer the character
1517
1624
  if (inCommandMode) {
1625
+ // Check for '//' escape - send literal '/' to Claude
1626
+ if (char === '/' && commandBuffer === '/') {
1627
+ // User typed '//' - exit command mode and send '/' to Claude
1628
+ process.stdout.write('\b \b'); // Erase the first '/'
1629
+ inCommandMode = false;
1630
+ commandBuffer = '';
1631
+ if (claudeProcess && isRunning && claudeProcess.stdin && claudeProcess.stdin.writable) {
1632
+ claudeProcess.stdin.write('/');
1633
+ }
1634
+ continue;
1635
+ }
1518
1636
  commandBuffer += char;
1519
1637
  // Echo to terminal
1520
1638
  process.stdout.write(char);
@@ -1593,6 +1711,10 @@ function main() {
1593
1711
  log(` Mode: Local (no bridge - terminal only)`, colors.dim);
1594
1712
  }
1595
1713
 
1714
+ if (skipPermissions) {
1715
+ log(` Perms: SKIPPED (dangerously-skip-permissions)`, colors.yellow);
1716
+ }
1717
+
1596
1718
  log(` Terminal: ${process.cwd()}`, colors.dim);
1597
1719
  log('══════════════════════════════════════', colors.dim);
1598
1720
  console.log('');