minivibe 0.1.0 → 0.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/README.md +180 -0
- package/package.json +1 -1
- package/vibe.js +118 -22
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# vibe-cli
|
|
2
|
+
|
|
3
|
+
CLI wrapper for Claude Code with mobile remote control via MiniVibe.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Remote control Claude Code from your iOS device
|
|
8
|
+
- Session management with resume capability
|
|
9
|
+
- Permission handling from mobile
|
|
10
|
+
- Token usage tracking
|
|
11
|
+
- Headless authentication for servers (EC2, etc.)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Clone the repository
|
|
17
|
+
git clone <repo-url>
|
|
18
|
+
cd vibe-cli
|
|
19
|
+
|
|
20
|
+
# Install dependencies
|
|
21
|
+
npm install
|
|
22
|
+
|
|
23
|
+
# Link globally (optional)
|
|
24
|
+
npm link
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Global Installation (EC2/Servers)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Install to /opt for all users
|
|
31
|
+
sudo git clone <repo-url> /opt/neng
|
|
32
|
+
cd /opt/neng/vibe-cli
|
|
33
|
+
sudo npm install
|
|
34
|
+
sudo npm link
|
|
35
|
+
|
|
36
|
+
# Or add to PATH
|
|
37
|
+
echo 'export PATH="/opt/neng/vibe-cli:$PATH"' | sudo tee /etc/profile.d/vibe.sh
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Authentication
|
|
41
|
+
|
|
42
|
+
### Browser Login (Desktop)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
vibe --login
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Opens browser for Google sign-in. Token is saved to `~/.vibe/auth.json`.
|
|
49
|
+
|
|
50
|
+
### Headless Login (Servers/EC2)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
vibe --login --headless
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Displays a device code. Visit the URL on any device to authenticate.
|
|
57
|
+
|
|
58
|
+
### Manual Token
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
vibe --token <firebase-token>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Get token from MiniVibe iOS app Settings > Copy Token for CLI.
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Basic Usage
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Interactive session
|
|
72
|
+
vibe
|
|
73
|
+
|
|
74
|
+
# With initial prompt
|
|
75
|
+
vibe "Fix the bug in main.js"
|
|
76
|
+
|
|
77
|
+
# Connect to bridge server
|
|
78
|
+
vibe --bridge wss://ws.neng.ai
|
|
79
|
+
|
|
80
|
+
# With prompt and bridge
|
|
81
|
+
vibe --bridge wss://ws.neng.ai "Deploy the application"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Session Management
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Name your session (visible in iOS app)
|
|
88
|
+
vibe --name "Backend Refactor" --bridge wss://ws.neng.ai
|
|
89
|
+
|
|
90
|
+
# Resume a previous session
|
|
91
|
+
vibe --resume <session-id> --bridge wss://ws.neng.ai
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Skip Permissions Mode
|
|
95
|
+
|
|
96
|
+
For automated/headless environments where you trust the execution context:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
vibe --dangerously-skip-permissions --bridge wss://ws.neng.ai
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Warning:** This mode auto-approves ALL tool executions (commands, file writes, etc.) without prompting. Only use in trusted/sandboxed environments.
|
|
103
|
+
|
|
104
|
+
### Local Agent Mode
|
|
105
|
+
|
|
106
|
+
Connect via a local vibe-agent for managed sessions:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Auto-discover agent
|
|
110
|
+
vibe --agent
|
|
111
|
+
|
|
112
|
+
# Specify agent URL
|
|
113
|
+
vibe --agent ws://localhost:9999
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Options
|
|
117
|
+
|
|
118
|
+
| Option | Description |
|
|
119
|
+
|--------|-------------|
|
|
120
|
+
| `--bridge <url>` | Connect to bridge server (e.g., `wss://ws.neng.ai`) |
|
|
121
|
+
| `--agent [url]` | Connect via local vibe-agent |
|
|
122
|
+
| `--name <name>` | Name this session (shown in mobile app) |
|
|
123
|
+
| `--resume <id>` | Resume a previous session |
|
|
124
|
+
| `--login` | Sign in with Google (browser) |
|
|
125
|
+
| `--headless` | Use device code flow for headless environments |
|
|
126
|
+
| `--token <token>` | Set Firebase auth token manually |
|
|
127
|
+
| `--logout` | Remove stored auth token |
|
|
128
|
+
| `--dangerously-skip-permissions` | Auto-approve all tool executions |
|
|
129
|
+
| `--node-pty` | Use Node.js PTY wrapper (required for Windows) |
|
|
130
|
+
| `--help, -h` | Show help message |
|
|
131
|
+
|
|
132
|
+
## In-Session Commands
|
|
133
|
+
|
|
134
|
+
| Command | Description |
|
|
135
|
+
|---------|-------------|
|
|
136
|
+
| `/name <name>` | Rename the current session |
|
|
137
|
+
|
|
138
|
+
## Requirements
|
|
139
|
+
|
|
140
|
+
- Node.js >= 18.0.0
|
|
141
|
+
- Claude Code CLI installed and in PATH
|
|
142
|
+
- Python 3 (for Unix PTY wrapper) or node-pty (for Windows)
|
|
143
|
+
|
|
144
|
+
## Platform Notes
|
|
145
|
+
|
|
146
|
+
### macOS/Linux
|
|
147
|
+
|
|
148
|
+
Uses Python PTY wrapper by default. Requires `python3`.
|
|
149
|
+
|
|
150
|
+
### Windows
|
|
151
|
+
|
|
152
|
+
Requires `node-pty`:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npm install node-pty
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
May also need Visual Studio Build Tools and Python for native compilation.
|
|
159
|
+
|
|
160
|
+
## Files
|
|
161
|
+
|
|
162
|
+
- `~/.vibe/auth.json` - Stored authentication (token + refresh token)
|
|
163
|
+
- `~/.vibe/token` - Legacy token file (backwards compatibility)
|
|
164
|
+
|
|
165
|
+
## Architecture
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
169
|
+
│ MiniVibe │────▶│ Bridge │◀────│ vibe-cli │
|
|
170
|
+
│ iOS App │ │ Server │ │ + Claude │
|
|
171
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
1. **vibe-cli** wraps Claude Code and connects to the bridge server
|
|
175
|
+
2. **Bridge server** relays messages between mobile and CLI
|
|
176
|
+
3. **MiniVibe iOS** provides remote control, permission approval, and session monitoring
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
package/package.json
CHANGED
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,6 +408,7 @@ 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:
|
|
@@ -466,6 +468,8 @@ Examples:
|
|
|
466
468
|
process.exit(0);
|
|
467
469
|
} else if (args[i] === '--node-pty') {
|
|
468
470
|
useNodePty = true;
|
|
471
|
+
} else if (args[i] === '--dangerously-skip-permissions') {
|
|
472
|
+
skipPermissions = true;
|
|
469
473
|
} else if (!args[i].startsWith('--')) {
|
|
470
474
|
initialPrompt = args[i];
|
|
471
475
|
}
|
|
@@ -534,12 +538,50 @@ function logStatus(msg) {
|
|
|
534
538
|
|
|
535
539
|
// Get Claude session file path
|
|
536
540
|
// Claude uses path with '/' replaced by '-' (not base64)
|
|
541
|
+
// But the exact format may vary - we try multiple strategies
|
|
537
542
|
function getSessionFilePath() {
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
|
|
543
|
+
const cwd = process.cwd();
|
|
544
|
+
const projectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
545
|
+
|
|
546
|
+
// Strategy 1: Direct replacement (e.g., /home/ubuntu -> -home-ubuntu)
|
|
547
|
+
const directHash = cwd.replace(/\//g, '-');
|
|
548
|
+
|
|
549
|
+
// Strategy 2: Without leading dash (e.g., home-ubuntu)
|
|
550
|
+
const noLeadingDash = directHash.replace(/^-/, '');
|
|
551
|
+
|
|
552
|
+
// Strategy 3: URL-safe encoding variations
|
|
553
|
+
const candidates = [directHash, noLeadingDash];
|
|
554
|
+
|
|
555
|
+
// Try to find existing directory that matches
|
|
556
|
+
if (fs.existsSync(projectsDir)) {
|
|
557
|
+
for (const candidate of candidates) {
|
|
558
|
+
const candidateDir = path.join(projectsDir, candidate);
|
|
559
|
+
if (fs.existsSync(candidateDir)) {
|
|
560
|
+
return path.join(candidateDir, `${sessionId}.jsonl`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Scan for session file in any project directory
|
|
565
|
+
try {
|
|
566
|
+
const dirs = fs.readdirSync(projectsDir);
|
|
567
|
+
for (const dir of dirs) {
|
|
568
|
+
const sessionFile = path.join(projectsDir, dir, `${sessionId}.jsonl`);
|
|
569
|
+
if (fs.existsSync(sessionFile)) {
|
|
570
|
+
return sessionFile;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
} catch (err) {
|
|
574
|
+
// Ignore scan errors
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Fall back to direct hash (will be created by Claude)
|
|
579
|
+
return path.join(projectsDir, directHash, `${sessionId}.jsonl`);
|
|
541
580
|
}
|
|
542
581
|
|
|
582
|
+
// Cache for discovered session file path
|
|
583
|
+
let discoveredSessionFile = null;
|
|
584
|
+
|
|
543
585
|
// Get local IP
|
|
544
586
|
function getLocalIP() {
|
|
545
587
|
const interfaces = os.networkInterfaces();
|
|
@@ -910,28 +952,41 @@ function sendToBridge(data) {
|
|
|
910
952
|
// ====================
|
|
911
953
|
|
|
912
954
|
function startSessionFileWatcher() {
|
|
913
|
-
|
|
914
|
-
logStatus(`Watching session file: ${sessionFile}`);
|
|
955
|
+
let sessionFile = getSessionFilePath();
|
|
956
|
+
logStatus(`Watching for session file: ${sessionFile}`);
|
|
915
957
|
|
|
916
|
-
// Also check what files actually exist in the projects directory
|
|
917
958
|
const projectsDir = path.join(os.homedir(), '.claude', 'projects');
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
959
|
+
let fileCheckCount = 0;
|
|
960
|
+
let lastNotFoundLog = 0;
|
|
961
|
+
let fileFound = false;
|
|
962
|
+
|
|
963
|
+
// Function to scan for session file in all project directories
|
|
964
|
+
function scanForSessionFile() {
|
|
965
|
+
if (!fs.existsSync(projectsDir)) return null;
|
|
966
|
+
|
|
967
|
+
try {
|
|
968
|
+
const dirs = fs.readdirSync(projectsDir);
|
|
969
|
+
// Limit scan to 50 directories to avoid slowdown
|
|
970
|
+
const dirsToScan = dirs.slice(0, 50);
|
|
971
|
+
for (const dir of dirsToScan) {
|
|
972
|
+
const fullDir = path.join(projectsDir, dir);
|
|
973
|
+
try {
|
|
974
|
+
if (fs.statSync(fullDir).isDirectory()) {
|
|
975
|
+
const candidateFile = path.join(fullDir, `${sessionId}.jsonl`);
|
|
976
|
+
if (fs.existsSync(candidateFile)) {
|
|
977
|
+
return candidateFile;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
} catch (e) {
|
|
981
|
+
// Skip inaccessible directories
|
|
929
982
|
}
|
|
930
983
|
}
|
|
984
|
+
} catch (err) {
|
|
985
|
+
// Ignore scan errors
|
|
931
986
|
}
|
|
987
|
+
return null;
|
|
932
988
|
}
|
|
933
989
|
|
|
934
|
-
let fileCheckCount = 0;
|
|
935
990
|
const pollInterval = setInterval(() => {
|
|
936
991
|
if (!isRunning || isShuttingDown) {
|
|
937
992
|
clearInterval(pollInterval);
|
|
@@ -939,15 +994,46 @@ function startSessionFileWatcher() {
|
|
|
939
994
|
}
|
|
940
995
|
|
|
941
996
|
try {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
if (
|
|
945
|
-
|
|
997
|
+
// If file not found yet, try to discover it
|
|
998
|
+
if (!fileFound) {
|
|
999
|
+
if (!fs.existsSync(sessionFile)) {
|
|
1000
|
+
// Try scanning for it every 5 seconds
|
|
1001
|
+
fileCheckCount++;
|
|
1002
|
+
if (fileCheckCount % 10 === 0) {
|
|
1003
|
+
const discovered = scanForSessionFile();
|
|
1004
|
+
if (discovered) {
|
|
1005
|
+
sessionFile = discovered;
|
|
1006
|
+
fileFound = true;
|
|
1007
|
+
lastFileSize = 0; // Reset to read from beginning
|
|
1008
|
+
log(`📁 Found session file: ${sessionFile}`, colors.dim);
|
|
1009
|
+
} else {
|
|
1010
|
+
// Only log "not found" every 30 seconds to reduce spam
|
|
1011
|
+
const now = Date.now();
|
|
1012
|
+
if (now - lastNotFoundLog > 30000) {
|
|
1013
|
+
logStatus(`Waiting for session file... (${Math.floor(fileCheckCount / 2)}s)`);
|
|
1014
|
+
lastNotFoundLog = now;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return;
|
|
946
1019
|
}
|
|
1020
|
+
fileFound = true;
|
|
1021
|
+
lastFileSize = 0; // Reset to read from beginning
|
|
1022
|
+
log(`📁 Session file ready: ${sessionFile}`, colors.dim);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (!fs.existsSync(sessionFile)) {
|
|
947
1026
|
return;
|
|
948
1027
|
}
|
|
949
1028
|
|
|
950
1029
|
const stats = fs.statSync(sessionFile);
|
|
1030
|
+
|
|
1031
|
+
// Handle file being recreated (size shrunk)
|
|
1032
|
+
if (stats.size < lastFileSize) {
|
|
1033
|
+
logStatus(`Session file was recreated, re-reading from start`);
|
|
1034
|
+
lastFileSize = 0;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
951
1037
|
if (stats.size > lastFileSize) {
|
|
952
1038
|
logStatus(`Session file changed: ${lastFileSize} -> ${stats.size} bytes`);
|
|
953
1039
|
const fd = fs.openSync(sessionFile, 'r');
|
|
@@ -1277,6 +1363,12 @@ function startClaude() {
|
|
|
1277
1363
|
logStatus(`Starting new Claude session: ${sessionId.slice(0, 8)}...`);
|
|
1278
1364
|
}
|
|
1279
1365
|
|
|
1366
|
+
// Add --dangerously-skip-permissions if requested
|
|
1367
|
+
if (skipPermissions) {
|
|
1368
|
+
claudeArgs.push('--dangerously-skip-permissions');
|
|
1369
|
+
log('⚠️ Running in skip-permissions mode (no prompts)', colors.yellow);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1280
1372
|
// Choose PTY wrapper based on platform/flag
|
|
1281
1373
|
// - Windows: always use node-pty (Python PTY doesn't work)
|
|
1282
1374
|
// - Unix: use Python by default, node-pty with --node-pty flag
|
|
@@ -1593,6 +1685,10 @@ function main() {
|
|
|
1593
1685
|
log(` Mode: Local (no bridge - terminal only)`, colors.dim);
|
|
1594
1686
|
}
|
|
1595
1687
|
|
|
1688
|
+
if (skipPermissions) {
|
|
1689
|
+
log(` Perms: SKIPPED (dangerously-skip-permissions)`, colors.yellow);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1596
1692
|
log(` Terminal: ${process.cwd()}`, colors.dim);
|
|
1597
1693
|
log('══════════════════════════════════════', colors.dim);
|
|
1598
1694
|
console.log('');
|