minivibe 0.2.3 → 0.2.9

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 CHANGED
@@ -20,7 +20,7 @@ CLI wrapper for Claude Code with mobile remote control via MiniVibe iOS app.
20
20
  npm install -g minivibe
21
21
 
22
22
  # Login (one-time)
23
- vibe --login
23
+ vibe login
24
24
 
25
25
  # Start coding with remote control!
26
26
  vibe
@@ -55,7 +55,7 @@ npm link
55
55
  ### Browser Login (Desktop)
56
56
 
57
57
  ```bash
58
- vibe --login
58
+ vibe login
59
59
  ```
60
60
 
61
61
  Opens browser for Google sign-in. Token is saved to `~/.vibe/auth.json`.
@@ -63,7 +63,7 @@ Opens browser for Google sign-in. Token is saved to `~/.vibe/auth.json`.
63
63
  ### Headless Login (Servers/EC2)
64
64
 
65
65
  ```bash
66
- vibe --login --headless
66
+ vibe login --headless
67
67
  ```
68
68
 
69
69
  Displays a device code. Visit the URL on any device to authenticate.
@@ -94,7 +94,7 @@ Use a local agent to manage multiple sessions:
94
94
 
95
95
  ```bash
96
96
  # Terminal 1: Start the agent (runs continuously)
97
- vibe-agent --login # First time only
97
+ vibe-agent login # First time only
98
98
  vibe-agent # Start daemon
99
99
 
100
100
  # Terminal 2+: Create sessions via agent
package/agent/agent.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * - Stop running sessions
10
10
  *
11
11
  * Usage:
12
- * vibe-agent --login Sign in (one-time)
12
+ * vibe-agent login Sign in (one-time)
13
13
  * vibe-agent Start agent daemon
14
14
  */
15
15
 
@@ -25,9 +25,13 @@ const os = require('os');
25
25
  // Configuration
26
26
  // ====================
27
27
 
28
+ // Shared auth directory (same as vibe CLI)
29
+ const SHARED_AUTH_DIR = path.join(os.homedir(), '.vibe');
30
+ const AUTH_FILE = path.join(SHARED_AUTH_DIR, 'auth.json');
31
+
32
+ // Agent-specific config directory
28
33
  const CONFIG_DIR = path.join(os.homedir(), '.vibe-agent');
29
34
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
30
- const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
31
35
  const SESSION_HISTORY_FILE = path.join(CONFIG_DIR, 'session-history.json');
32
36
 
33
37
  const RECONNECT_DELAY_MS = 5000;
@@ -36,6 +40,7 @@ const LOCAL_SERVER_PORT = 9999;
36
40
  const PORT_FILE = path.join(os.homedir(), '.vibe-agent', 'port');
37
41
  const MAX_SESSION_HISTORY_AGE_DAYS = 30;
38
42
  const DEFAULT_BRIDGE_URL = 'wss://ws.minivibeapp.com';
43
+ const PAIRING_URL = 'https://minivibeapp.com/pair';
39
44
 
40
45
  // Show welcome message for first-time users (no auth)
41
46
  function showWelcomeMessage() {
@@ -46,9 +51,10 @@ vibe-agent lets you manage Claude Code sessions from your iPhone.
46
51
 
47
52
  To get started:
48
53
  1. Download MiniVibe from the App Store
49
- 2. Run: vibe-agent --login
54
+ 2. Run: vibe-agent login
55
+ (or 'vibe login' - auth is shared between vibe and vibe-agent)
50
56
 
51
- For help: vibe-agent --help
57
+ For help: vibe-agent help
52
58
  `);
53
59
  }
54
60
 
@@ -111,8 +117,8 @@ function loadAuth() {
111
117
 
112
118
  function saveAuth(idToken, refreshToken = null) {
113
119
  try {
114
- if (!fs.existsSync(CONFIG_DIR)) {
115
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
120
+ if (!fs.existsSync(SHARED_AUTH_DIR)) {
121
+ fs.mkdirSync(SHARED_AUTH_DIR, { recursive: true });
116
122
  }
117
123
  const data = { idToken, refreshToken, updatedAt: new Date().toISOString() };
118
124
  fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), 'utf8');
@@ -730,7 +736,7 @@ function handleMessage(msg) {
730
736
  authToken = newToken;
731
737
  send({ type: 'authenticate', token: newToken });
732
738
  } else {
733
- log('Please re-login: vibe-agent --login', colors.red);
739
+ log('Please re-login: vibe-agent login', colors.red);
734
740
  }
735
741
  })();
736
742
  break;
@@ -1282,7 +1288,7 @@ ${'='.repeat(40)}
1282
1288
 
1283
1289
  const { deviceId, code, expiresIn } = await codeRes.json();
1284
1290
 
1285
- console.log(` Visit: ${bridgeHttpUrl}/device`);
1291
+ console.log(` Visit: ${PAIRING_URL}`);
1286
1292
  console.log(` Code: ${colors.bold}${code}${colors.reset}`);
1287
1293
  console.log('');
1288
1294
  console.log(` Code expires in ${Math.floor(expiresIn / 60)} minutes.`);
@@ -1339,30 +1345,44 @@ ${colors.cyan}${colors.bold}vibe-agent${colors.reset} - Persistent daemon for re
1339
1345
 
1340
1346
  ${colors.bold}Usage:${colors.reset}
1341
1347
  vibe-agent Start agent daemon
1342
- vibe-agent --login Sign in with Google (one-time)
1343
- vibe-agent --logout Sign out and clear credentials
1344
- vibe-agent --status Show agent status
1348
+ vibe-agent login Sign in (one-time)
1349
+ vibe-agent logout Sign out and clear credentials
1350
+ vibe-agent status Show agent status
1351
+
1352
+ ${colors.bold}Commands:${colors.reset}
1353
+ login Sign in via device code flow
1354
+ logout Sign out and clear saved credentials
1355
+ status Show current status and exit
1356
+ help Show this help
1345
1357
 
1346
1358
  ${colors.bold}Options:${colors.reset}
1347
- --login Sign in via device code flow
1348
- --logout Sign out and clear saved credentials
1349
1359
  --name <name> Set host display name
1350
- --status Show current status and exit
1351
- --help, -h Show this help
1352
-
1353
- ${colors.bold}Advanced:${colors.reset}
1354
1360
  --bridge <url> Override bridge URL (default: wss://ws.minivibeapp.com)
1355
1361
  --token <token> Use specific Firebase token
1356
1362
 
1357
1363
  ${colors.bold}Examples:${colors.reset}
1358
- vibe-agent --login Sign in (one-time setup)
1364
+ vibe-agent login Sign in (one-time setup)
1359
1365
  vibe-agent Start agent
1360
1366
  vibe-agent --name "EC2" Start with custom name
1361
1367
  `);
1362
1368
  }
1363
1369
 
1364
1370
  function parseArgs() {
1365
- const args = process.argv.slice(2);
1371
+ const rawArgs = process.argv.slice(2);
1372
+
1373
+ // Support subcommand style (vibe-agent login) alongside flag style (vibe-agent --login)
1374
+ const subcommands = {
1375
+ 'login': '--login',
1376
+ 'logout': '--logout',
1377
+ 'status': '--status',
1378
+ 'help': '--help'
1379
+ };
1380
+
1381
+ // Transform first arg if it's a subcommand
1382
+ const args = rawArgs.length > 0 && subcommands[rawArgs[0]]
1383
+ ? [subcommands[rawArgs[0]], ...rawArgs.slice(1)]
1384
+ : rawArgs;
1385
+
1366
1386
  const options = {
1367
1387
  bridge: null,
1368
1388
  token: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minivibe",
3
- "version": "0.2.3",
3
+ "version": "0.2.9",
4
4
  "description": "CLI wrapper for Claude Code with mobile remote control via MiniVibe iOS app",
5
5
  "author": "MiniVibe <hello@minivibeapp.com>",
6
6
  "homepage": "https://github.com/minivibeapp/minivibe",
package/vibe.js CHANGED
@@ -38,9 +38,9 @@ MiniVibe lets you control Claude Code from your iPhone.
38
38
 
39
39
  To get started:
40
40
  1. Download MiniVibe from the App Store
41
- 2. Run: vibe --login
41
+ 2. Run: vibe login
42
42
 
43
- For help: vibe --help
43
+ For help: vibe help
44
44
  `);
45
45
  }
46
46
 
@@ -55,7 +55,7 @@ Install Claude Code:
55
55
  https://claude.ai/download
56
56
 
57
57
  After installing, run:
58
- vibe --login
58
+ vibe login
59
59
  `);
60
60
  }
61
61
 
@@ -326,7 +326,21 @@ async function startHeadlessLogin() {
326
326
  }
327
327
 
328
328
  // Parse arguments
329
- const args = process.argv.slice(2);
329
+ const rawArgs = process.argv.slice(2);
330
+
331
+ // Support subcommand style (vibe login) alongside flag style (vibe --login)
332
+ const subcommands = {
333
+ 'login': '--login',
334
+ 'logout': '--logout',
335
+ 'status': '--status',
336
+ 'help': '--help'
337
+ };
338
+
339
+ // Transform first arg if it's a subcommand
340
+ const args = rawArgs.length > 0 && subcommands[rawArgs[0]]
341
+ ? [subcommands[rawArgs[0]], ...rawArgs.slice(1)]
342
+ : rawArgs;
343
+
330
344
  let initialPrompt = null;
331
345
  let resumeSessionId = null;
332
346
  let bridgeUrl = null;
@@ -339,6 +353,7 @@ let skipPermissions = false; // --dangerously-skip-permissions mode
339
353
  let listSessions = false; // --list mode: list running sessions
340
354
  let remoteAttachMode = false; // --remote with --bridge: pure remote control (no local Claude)
341
355
  let e2eEnabled = false; // --e2e mode: enable end-to-end encryption
356
+ let verboseMode = false; // --verbose mode: show [vibe] debug messages
342
357
 
343
358
  for (let i = 0; i < args.length; i++) {
344
359
  if (args[i] === '--help' || args[i] === '-h') {
@@ -348,16 +363,21 @@ vibe - Claude Code with mobile remote control
348
363
  Usage:
349
364
  vibe Start session (connects to bridge)
350
365
  vibe "prompt" Start with initial prompt
351
- vibe --login Sign in with Google
352
- vibe --agent Connect via local vibe-agent
366
+ vibe login Sign in (Apple or Google)
367
+ vibe logout Sign out
368
+
369
+ Commands:
370
+ login Sign in via minivibeapp.com (opens browser)
371
+ logout Sign out and remove stored auth
372
+ help Show this help message
353
373
 
354
374
  Options:
355
- --login Sign in via minivibeapp.com (opens browser)
356
375
  --headless Use device code flow for servers (no browser)
357
376
  --agent [url] Connect via local vibe-agent (default: auto-discover)
358
377
  --name <name> Name this session (shown in mobile app)
359
378
  --resume <id> Resume a previous session
360
379
  --e2e Enable end-to-end encryption
380
+ --verbose, -v Show detailed [vibe] status messages
361
381
 
362
382
  Advanced:
363
383
  --bridge <url> Override bridge URL (default: wss://ws.minivibeapp.com)
@@ -365,13 +385,12 @@ Advanced:
365
385
  --remote <id> Remote control session via bridge (no local Claude needed)
366
386
  --list List running sessions on local agent
367
387
  --token <token> Set Firebase auth token manually
368
- --logout Remove stored auth token
369
388
  --node-pty Use Node.js PTY wrapper (required for Windows)
370
389
  --dangerously-skip-permissions Auto-approve all tool executions
371
390
  --help, -h Show this help message
372
391
 
373
392
  Examples:
374
- vibe --login Sign in (one-time setup)
393
+ vibe login Sign in (one-time setup)
375
394
  vibe Start session
376
395
  vibe "Fix the bug" Start with prompt
377
396
  vibe --e2e Enable encryption
@@ -382,6 +401,8 @@ For local-only use without remote control, run 'claude' directly.
382
401
  process.exit(0);
383
402
  } else if (args[i] === '--headless') {
384
403
  headlessMode = true;
404
+ } else if (args[i] === '--verbose' || args[i] === '-v') {
405
+ verboseMode = true;
385
406
  } else if (args[i] === '--login') {
386
407
  // Defer login handling until we know if --headless is also present
387
408
  // Will be handled after the loop
@@ -541,7 +562,11 @@ if (e2eEnabled) {
541
562
  // Note: E2E key exchange now works automatically since bridge is defaulted when authenticated
542
563
 
543
564
  // Session state
565
+ // Support VIBE_SESSION_ID env var for fixed session IDs (e.g., playground)
566
+ // Bridge session ID can be any string, but Claude needs a valid UUID
567
+ const bridgeSessionId = process.env.VIBE_SESSION_ID;
544
568
  const sessionId = resumeSessionId || uuidv4();
569
+ const effectiveSessionId = bridgeSessionId || sessionId; // Use for bridge registration
545
570
  let claudeProcess = null;
546
571
  let isRunning = false;
547
572
  let isShuttingDown = false;
@@ -583,6 +608,9 @@ function logStderr(msg, color = '') {
583
608
  }
584
609
 
585
610
  function logStatus(msg) {
611
+ // Only log status messages in verbose mode
612
+ if (!verboseMode) return;
613
+
586
614
  // Use stderr when Claude is running to avoid corrupting PTY output
587
615
  if (isRunning) {
588
616
  logStderr(`[vibe] ${msg}`, colors.dim);
@@ -835,7 +863,7 @@ function connectToBridge() {
835
863
  Cannot connect to vibe-agent at ${targetUrl}
836
864
 
837
865
  Make sure vibe-agent is running:
838
- vibe-agent --login # First time
866
+ vibe-agent login # First time
839
867
  vibe-agent # Start daemon
840
868
  `);
841
869
  process.exit(1);
@@ -911,7 +939,7 @@ function handleBridgeMessage(msg) {
911
939
  // This is required before E2E key exchange so our keys can reach viewers
912
940
  bridgeSocket.send(JSON.stringify({
913
941
  type: 'register_session',
914
- sessionId,
942
+ sessionId: effectiveSessionId, // Use VIBE_SESSION_ID if set for fixed playground IDs
915
943
  path: process.cwd(),
916
944
  name: sessionName || path.basename(process.cwd()), // Use --name or fallback to directory name
917
945
  e2e: e2eEnabled // Indicate if this session has E2E enabled
@@ -925,7 +953,7 @@ function handleBridgeMessage(msg) {
925
953
  // Must be AFTER register_session so our type is 'provider' and keys reach viewers
926
954
  if (e2eEnabled && e2e.isEnabled()) {
927
955
  const keyExchangeMsg = e2e.createKeyExchangeMessage(true); // needsResponse=true
928
- keyExchangeMsg.sessionId = sessionId; // Include sessionId for routing
956
+ keyExchangeMsg.sessionId = effectiveSessionId; // Include sessionId for routing
929
957
  bridgeSocket.send(JSON.stringify(keyExchangeMsg));
930
958
  log('[E2E] Sent public key to bridge (waiting for peer)', colors.cyan);
931
959
  }
@@ -964,7 +992,7 @@ function handleBridgeMessage(msg) {
964
992
  log('║ Your token is invalid or expired. ║', colors.red);
965
993
  log('║ Please re-login: ║', colors.red);
966
994
  log('║ ║', colors.red);
967
- log('║ vibe --login ║', colors.red);
995
+ log('║ vibe login ║', colors.red);
968
996
  log('║ ║', colors.red);
969
997
  log('╚════════════════════════════════════════════╝', colors.red);
970
998
  log('', colors.red);
@@ -1025,7 +1053,7 @@ function handleBridgeMessage(msg) {
1025
1053
  if (!msg.data || msg.data.length === 0 || !msg.fileName) {
1026
1054
  sendToBridge({
1027
1055
  type: 'error',
1028
- sessionId: sessionId,
1056
+ sessionId: effectiveSessionId,
1029
1057
  message: 'Invalid file upload: missing data or fileName'
1030
1058
  });
1031
1059
  break;
@@ -1036,7 +1064,7 @@ function handleBridgeMessage(msg) {
1036
1064
  if (msg.data.length > MAX_BASE64_SIZE) {
1037
1065
  sendToBridge({
1038
1066
  type: 'error',
1039
- sessionId: sessionId,
1067
+ sessionId: effectiveSessionId,
1040
1068
  message: 'File too large (max 10MB)'
1041
1069
  });
1042
1070
  break;
@@ -1130,7 +1158,7 @@ function handleBridgeMessage(msg) {
1130
1158
  // Echo the message back using claude_message type (matches iOS handler)
1131
1159
  sendToBridge({
1132
1160
  type: 'claude_message',
1133
- sessionId: sessionId,
1161
+ sessionId: effectiveSessionId,
1134
1162
  message: {
1135
1163
  id: uuidv4(),
1136
1164
  sender: 'user',
@@ -1150,7 +1178,7 @@ function handleBridgeMessage(msg) {
1150
1178
  // Also send file_received confirmation
1151
1179
  sendToBridge({
1152
1180
  type: 'file_received',
1153
- sessionId: sessionId,
1181
+ sessionId: effectiveSessionId,
1154
1182
  fileName: safeName,
1155
1183
  localPath: filePath,
1156
1184
  size: fileBuffer.length
@@ -1160,7 +1188,7 @@ function handleBridgeMessage(msg) {
1160
1188
  log(`❌ Failed to save file: ${err.message}`, colors.red);
1161
1189
  sendToBridge({
1162
1190
  type: 'error',
1163
- sessionId: sessionId,
1191
+ sessionId: effectiveSessionId,
1164
1192
  message: `Failed to upload file: ${err.message}`
1165
1193
  });
1166
1194
  }
@@ -1175,7 +1203,7 @@ function handleBridgeMessage(msg) {
1175
1203
  if (!msg.images || !Array.isArray(msg.images) || msg.images.length === 0) {
1176
1204
  sendToBridge({
1177
1205
  type: 'error',
1178
- sessionId: sessionId,
1206
+ sessionId: effectiveSessionId,
1179
1207
  message: 'Invalid file upload: missing or empty images array'
1180
1208
  });
1181
1209
  break;
@@ -1187,7 +1215,7 @@ function handleBridgeMessage(msg) {
1187
1215
  if (totalBase64Size > MAX_TOTAL_BASE64_SIZE) {
1188
1216
  sendToBridge({
1189
1217
  type: 'error',
1190
- sessionId: sessionId,
1218
+ sessionId: effectiveSessionId,
1191
1219
  message: 'Total file size too large (max 50MB combined)'
1192
1220
  });
1193
1221
  break;
@@ -1302,7 +1330,7 @@ function handleBridgeMessage(msg) {
1302
1330
  // Echo the message back with attachments array
1303
1331
  sendToBridge({
1304
1332
  type: 'claude_message',
1305
- sessionId: sessionId,
1333
+ sessionId: effectiveSessionId,
1306
1334
  message: {
1307
1335
  id: uuidv4(),
1308
1336
  sender: 'user',
@@ -1315,7 +1343,7 @@ function handleBridgeMessage(msg) {
1315
1343
  // Send confirmation
1316
1344
  sendToBridge({
1317
1345
  type: 'files_received',
1318
- sessionId: sessionId,
1346
+ sessionId: effectiveSessionId,
1319
1347
  count: savedFiles.length,
1320
1348
  files: savedFiles.map(f => ({ fileName: f.name, localPath: f.path }))
1321
1349
  });
@@ -1326,7 +1354,7 @@ function handleBridgeMessage(msg) {
1326
1354
  log(`❌ Failed to process files: ${err.message}`, colors.red);
1327
1355
  sendToBridge({
1328
1356
  type: 'error',
1329
- sessionId: sessionId,
1357
+ sessionId: effectiveSessionId,
1330
1358
  message: `Failed to upload files: ${err.message}`
1331
1359
  });
1332
1360
  }
@@ -1455,7 +1483,7 @@ function handleBridgeMessage(msg) {
1455
1483
  log(`║ ${(msg.message || 'Invalid token').padEnd(42)}║`, colors.red);
1456
1484
  log('║ ║', colors.red);
1457
1485
  log('║ Please re-login: ║', colors.red);
1458
- log('║ vibe --login ║', colors.red);
1486
+ log('║ vibe login ║', colors.red);
1459
1487
  log('║ ║', colors.red);
1460
1488
  log('╚════════════════════════════════════════════╝', colors.red);
1461
1489
  log('', colors.red);
@@ -1850,7 +1878,7 @@ function processSessionMessage(msg) {
1850
1878
 
1851
1879
  sendToBridge({
1852
1880
  type: 'permission_request',
1853
- sessionId: sessionId,
1881
+ sessionId: effectiveSessionId,
1854
1882
  requestId: tool.id,
1855
1883
  command: tool.name,
1856
1884
  question: question,
@@ -1941,7 +1969,7 @@ function processSessionMessage(msg) {
1941
1969
  const usage = msg.message.usage;
1942
1970
  sendToBridge({
1943
1971
  type: 'token_usage',
1944
- sessionId: sessionId,
1972
+ sessionId: effectiveSessionId,
1945
1973
  model: msg.message.model || null,
1946
1974
  usage: {
1947
1975
  input_tokens: usage.input_tokens || 0,
@@ -2283,7 +2311,7 @@ function handleRemoteAttachMessage(msg) {
2283
2311
 
2284
2312
  case 'auth_error':
2285
2313
  log(`✗ Authentication failed: ${msg.message}`, colors.red);
2286
- log(' Run: vibe --login --bridge ' + bridgeUrl, colors.dim);
2314
+ log(' Run: vibe login --bridge ' + bridgeUrl, colors.dim);
2287
2315
  process.exit(1);
2288
2316
  break;
2289
2317
 
@@ -2438,7 +2466,7 @@ function setupRemoteAttachInput() {
2438
2466
  if (pendingPermission) {
2439
2467
  sendRemoteCommand({
2440
2468
  type: 'approve_permission',
2441
- sessionId: sessionId,
2469
+ sessionId: effectiveSessionId,
2442
2470
  requestId: pendingPermission.id
2443
2471
  });
2444
2472
  log('Sent approval', colors.green);
@@ -2450,7 +2478,7 @@ function setupRemoteAttachInput() {
2450
2478
  if (pendingPermission) {
2451
2479
  sendRemoteCommand({
2452
2480
  type: 'deny_permission',
2453
- sessionId: sessionId,
2481
+ sessionId: effectiveSessionId,
2454
2482
  requestId: pendingPermission.id
2455
2483
  });
2456
2484
  log('Sent denial', colors.yellow);
@@ -2486,7 +2514,7 @@ function setupRemoteAttachInput() {
2486
2514
  // Send message to Claude
2487
2515
  sendRemoteCommand({
2488
2516
  type: 'send_message',
2489
- sessionId: sessionId,
2517
+ sessionId: effectiveSessionId,
2490
2518
  content: input
2491
2519
  });
2492
2520
  log(`📤 Sent: ${truncateText(input, 50)}`, colors.dim);