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.
- package/GETTING_STARTED.md +241 -0
- package/README.md +213 -0
- package/package.json +4 -2
- package/vibe.js +159 -37
|
@@ -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.
|
|
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
|
|
539
|
-
const
|
|
540
|
-
|
|
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
|
-
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
943
|
-
|
|
944
|
-
if (
|
|
945
|
-
|
|
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
|
|
1480
|
-
if (
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
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('');
|