vibex-sh 0.10.0 → 0.11.0

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.
Files changed (3) hide show
  1. package/README.md +10 -29
  2. package/index.js +268 -182
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,12 +7,6 @@ Zero-config observability CLI - pipe logs and visualize instantly.
7
7
  ```bash
8
8
  # Production (default)
9
9
  echo '{"cpu": 45, "memory": 78}' | vibex
10
-
11
- # Local development
12
- echo '{"test": 123}' | vibex --local
13
-
14
- # Custom ports
15
- echo '{"data": 123}' | vibex --web http://localhost:3000 --socket http://localhost:8080
16
10
  ```
17
11
 
18
12
  ## Installation
@@ -43,40 +37,33 @@ echo '{"more": "data"}' | vibex --session-id vibex-abc123
43
37
  | Flag | Description | Example |
44
38
  |------|-------------|---------|
45
39
  | `-s, --session-id <id>` | Reuse existing session | `vibex --session-id vibex-abc123` |
46
- | `-l, --local` | Use localhost (web: 3000, socket: 3001) | `vibex --local` |
47
- | `--web <url>` | Web server URL | `vibex --web http://localhost:3000` |
48
- | `--socket <url>` | Socket server URL | `vibex --socket http://localhost:8080` |
49
- | `--server <url>` | Shorthand for `--web` (auto-derives socket) | `vibex --server http://localhost:3000` |
40
+ | `--web <url>` | Web server URL | `vibex --web https://vibex.sh` |
41
+ | `--socket <url>` | Socket server URL | `vibex --socket wss://ingest.vibex.sh` |
42
+ | `--server <url>` | Shorthand for `--web` (auto-derives socket) | `vibex --server https://vibex.sh` |
50
43
 
51
44
  ## Server Configuration
52
45
 
53
46
  The CLI automatically derives the socket URL from the web URL, but you can override it:
54
47
 
55
48
  ```bash
56
- # Auto-derive socket (localhost:3000 → localhost:3001)
57
- vibex --web http://localhost:3000
58
-
59
- # Explicit socket URL
60
- vibex --web http://localhost:3000 --socket http://localhost:8080
61
-
62
- # Production (auto-derives socket.vibex.sh)
49
+ # Production (auto-derives socket URL)
63
50
  vibex --server https://vibex.sh
64
51
 
65
52
  # Custom domain
66
- vibex --web https://staging.vibex.sh --socket https://socket-staging.vibex.sh
53
+ vibex --web https://staging.vibex.sh --socket wss://ingest-staging.vibex.sh
67
54
  ```
68
55
 
69
56
  ## Priority Order
70
57
 
71
- 1. **Flags** (`--web`, `--socket`, `--local`, `--server`)
58
+ 1. **Flags** (`--web`, `--socket`, `--server`)
72
59
  2. **Environment variables** (`VIBEX_WEB_URL`, `VIBEX_SOCKET_URL`)
73
- 3. **Production defaults** (`https://vibex.sh`, `https://socket.vibex.sh`)
60
+ 3. **Production defaults** (`https://vibex.sh`, `wss://ingest.vibex.sh`)
74
61
 
75
62
  ## Environment Variables
76
63
 
77
64
  ```bash
78
- export VIBEX_WEB_URL=http://localhost:3000
79
- export VIBEX_SOCKET_URL=http://localhost:8080
65
+ export VIBEX_WEB_URL=https://vibex.sh
66
+ export VIBEX_SOCKET_URL=wss://ingest.vibex.sh
80
67
  ```
81
68
 
82
69
  ## Examples
@@ -85,14 +72,8 @@ export VIBEX_SOCKET_URL=http://localhost:8080
85
72
  # Production (default)
86
73
  echo '{"data": 123}' | vibex
87
74
 
88
- # Quick localhost
89
- echo '{"data": 123}' | vibex --local
90
-
91
75
  # Custom web server, auto socket
92
- echo '{"data": 123}' | vibex --server http://localhost:3000
93
-
94
- # Both custom
95
- echo '{"data": 123}' | vibex --web http://localhost:3000 --socket http://localhost:8080
76
+ echo '{"data": 123}' | vibex --server https://vibex.sh
96
77
 
97
78
  # Staging
98
79
  echo '{"data": 123}' | vibex --server https://staging.vibex.sh
package/index.js CHANGED
@@ -11,6 +11,13 @@ import { fileURLToPath } from 'url';
11
11
  import WebSocket from 'ws';
12
12
  import crypto from 'crypto';
13
13
 
14
+ // Constants
15
+ const POLL_INTERVAL_MS = 1000;
16
+ const MAX_POLL_ATTEMPTS = 60;
17
+ const WEBSOCKET_CLOSE_TIMEOUT_MS = 2000;
18
+ const DEFAULT_WORKER_URL = 'https://ingest.vibex.sh';
19
+ const DEFAULT_WEB_URL = 'https://vibex.sh';
20
+
14
21
  // Get version from package.json
15
22
  const __filename = fileURLToPath(import.meta.url);
16
23
  const __dirname = dirname(__filename);
@@ -153,67 +160,16 @@ function normalizeToHybrid(message, level, payload) {
153
160
  return hybrid;
154
161
  }
155
162
 
156
- function deriveSocketUrl(webUrl) {
157
- const url = new URL(webUrl);
158
-
159
- // For localhost, use Workers dev server
160
- if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
161
- return 'ws://localhost:8787';
162
- }
163
- // For vibex.sh domains, use Workers WebSocket endpoint
164
- else if (url.hostname.includes('vibex.sh')) {
165
- // Use Cloudflare Workers WebSocket endpoint
166
- const workerUrl = process.env.VIBEX_WORKER_URL || 'https://ingest.vibex.sh';
167
- return workerUrl.replace('https://', 'wss://').replace('http://', 'ws://');
168
- }
169
- // For other domains, derive from web URL
170
- else {
171
- const workerUrl = process.env.VIBEX_WORKER_URL || webUrl.replace(url.hostname, `ingest.${url.hostname}`);
172
- return workerUrl.replace('https://', 'wss://').replace('http://', 'ws://');
173
- }
174
- }
175
-
176
- function getUrls(options) {
177
- const { local, web, socket, server } = options;
178
-
179
- // Priority 1: Explicit --web and --socket flags (highest priority)
180
- if (web) {
181
- return {
182
- webUrl: web,
183
- socketUrl: socket || deriveSocketUrl(web),
184
- };
185
- }
186
-
187
- // Priority 2: --server flag (shorthand for --web)
188
- if (server) {
189
- return {
190
- webUrl: server,
191
- socketUrl: socket || deriveSocketUrl(server),
192
- };
193
- }
194
-
195
- // Priority 3: --local flag
196
- if (local) {
197
- return {
198
- webUrl: process.env.VIBEX_WEB_URL || 'http://localhost:3000',
199
- socketUrl: process.env.VIBEX_SOCKET_URL || socket || 'ws://localhost:8787',
200
- };
201
- }
202
-
203
- // Priority 4: Environment variables
204
- if (process.env.VIBEX_WEB_URL) {
205
- return {
206
- webUrl: process.env.VIBEX_WEB_URL,
207
- socketUrl: process.env.VIBEX_SOCKET_URL || socket || deriveSocketUrl(process.env.VIBEX_WEB_URL),
208
- };
209
- }
210
-
211
- // Priority 5: Production defaults
212
- // Use Worker WebSocket endpoint instead of old Socket.io server
213
- const defaultWorkerUrl = process.env.VIBEX_WORKER_URL || 'https://ingest.vibex.sh';
163
+ /**
164
+ * Get production URLs
165
+ * CLI only supports production server
166
+ */
167
+ function getProductionUrls() {
168
+ // Always use production worker WebSocket endpoint
169
+ const workerUrl = process.env.VIBEX_WORKER_URL || DEFAULT_WORKER_URL;
214
170
  return {
215
- webUrl: 'https://vibex.sh',
216
- socketUrl: socket || defaultWorkerUrl.replace('https://', 'wss://').replace('http://', 'ws://'),
171
+ webUrl: DEFAULT_WEB_URL,
172
+ socketUrl: workerUrl.replace('https://', 'wss://').replace('http://', 'ws://'),
217
173
  };
218
174
  }
219
175
 
@@ -252,7 +208,7 @@ function getStoredConfig() {
252
208
  return null;
253
209
  }
254
210
 
255
- async function storeToken(token, webUrl = null) {
211
+ async function storeToken(token) {
256
212
  try {
257
213
  const configPath = getConfigPath();
258
214
  const configDir = join(homedir(), '.vibex');
@@ -262,7 +218,6 @@ async function storeToken(token, webUrl = null) {
262
218
 
263
219
  const config = {
264
220
  token,
265
- ...(webUrl && { webUrl }), // Store webUrl if provided
266
221
  updatedAt: new Date().toISOString(),
267
222
  };
268
223
 
@@ -274,9 +229,10 @@ async function storeToken(token, webUrl = null) {
274
229
  }
275
230
  }
276
231
 
277
- async function handleLogin(webUrl) {
232
+ async function handleLogin() {
278
233
  const configPath = getConfigPath();
279
234
  const existingConfig = getStoredConfig();
235
+ const { webUrl } = getProductionUrls();
280
236
 
281
237
  console.log('\n 🔐 vibex.sh CLI Authentication\n');
282
238
  console.log(` 📁 Config location: ${configPath}`);
@@ -285,8 +241,9 @@ async function handleLogin(webUrl) {
285
241
  console.log(` ⚠️ You already have a token stored. This will replace it.\n`);
286
242
  }
287
243
 
288
- const tempToken = `temp_${Date.now()}_${Math.random().toString(36).substring(7)}`;
289
- const authUrl = `${webUrl}/api/cli-auth?token=${tempToken}`;
244
+ // Generate unique state (nonce) for OAuth flow
245
+ const state = crypto.randomBytes(16).toString('hex');
246
+ const authUrl = `${webUrl}/api/cli-auth?state=${state}`;
290
247
 
291
248
  console.log(' Opening browser for authentication...\n');
292
249
  console.log(` If browser doesn't open, visit: ${authUrl}\n`);
@@ -306,21 +263,20 @@ async function handleLogin(webUrl) {
306
263
 
307
264
  // Poll for token
308
265
  console.log(' Waiting for authentication...');
309
- const maxAttempts = 60; // 60 seconds
310
266
  let attempts = 0;
311
267
 
312
- while (attempts < maxAttempts) {
313
- await new Promise(resolve => setTimeout(resolve, 1000));
268
+ while (attempts < MAX_POLL_ATTEMPTS) {
269
+ await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
314
270
  attempts++;
315
271
 
316
272
  try {
317
- const response = await httpRequest(`${webUrl}/api/cli-auth?token=${tempToken}`, {
273
+ const response = await httpRequest(`${webUrl}/api/cli-auth?state=${state}`, {
318
274
  method: 'GET',
319
275
  });
320
276
  if (response.ok) {
321
277
  const data = await response.json();
322
278
  if (data.success && data.token) {
323
- await storeToken(data.token, webUrl);
279
+ await storeToken(data.token);
324
280
  const configPath = getConfigPath();
325
281
  console.log('\n ✅ Authentication successful!');
326
282
  console.log(` 📁 Token saved to: ${configPath}`);
@@ -364,39 +320,16 @@ function httpRequest(url, options) {
364
320
  });
365
321
  }
366
322
 
367
- async function claimSession(sessionId, token, webUrl) {
368
- if (!token) return null; // Return null instead of false to indicate no claim attempted
369
-
370
- try {
371
- // Normalize session ID before claiming
372
- const normalizedSessionId = normalizeSessionId(sessionId);
373
- const response = await httpRequest(`${webUrl}/api/auth/claim-session-with-token`, {
374
- method: 'POST',
375
- headers: { 'Content-Type': 'application/json' },
376
- body: JSON.stringify({
377
- sessionId: normalizedSessionId,
378
- token,
379
- }),
380
- });
381
-
382
- if (response.ok) {
383
- // Parse response to get auth code
384
- const responseData = await response.json();
385
- return responseData.authCode || null;
386
- }
387
-
388
- return null;
389
- } catch (error) {
390
- return null;
391
- }
392
- }
323
+ // claimSession function removed - session claiming is no longer supported
324
+ // All sessions must be created with authentication
393
325
 
394
326
  // Removed getSessionAuthCode - auth codes should only come from:
395
327
  // 1. claim-session-with-token response (for claimed sessions)
396
328
  // 2. socket.io session-auth-code event (for unclaimed sessions)
397
329
  // Never fetch auth codes via public API endpoint - security vulnerability
398
330
 
399
- function printBanner(sessionId, webUrl, authCode = null) {
331
+ function printBanner(sessionId, authCode = null) {
332
+ const { webUrl } = getProductionUrls();
400
333
  const dashboardUrl = authCode
401
334
  ? `${webUrl}/${sessionId}?auth=${authCode}`
402
335
  : `${webUrl}/${sessionId}`;
@@ -414,90 +347,249 @@ function printBanner(sessionId, webUrl, authCode = null) {
414
347
  console.log('\n');
415
348
  }
416
349
 
417
- async function main() {
418
- // Handle --version flag early (before commander parses)
419
- const allArgs = process.argv;
420
- const args = process.argv.slice(2);
350
+ /**
351
+ * Handle init command - create a new session with parser selection
352
+ */
353
+ async function handleInit(options) {
354
+ const { webUrl } = getProductionUrls();
421
355
 
422
- // Check for --version or -V flag
423
- if (allArgs.includes('--version') || allArgs.includes('-V') || args.includes('--version') || args.includes('-V')) {
424
- console.log(cliVersion);
425
- process.exit(0);
426
- }
356
+ // Interactive prompt for parser selection
357
+ const rl = readline.createInterface({
358
+ input: process.stdin,
359
+ output: process.stdout,
360
+ });
427
361
 
428
- // Handle login command separately - check BEFORE commander parses
429
- // Check process.argv directly - look for 'login' as a standalone argument
430
- // This must happen FIRST, before any commander parsing
431
- // Check if 'login' appears anywhere in process.argv (works with npx too)
432
- const hasLogin = allArgs.includes('login') || args.includes('login');
362
+ const question = (query) => new Promise((resolve) => rl.question(query, resolve));
433
363
 
434
- if (hasLogin) {
435
- // Find login position to get args after it
436
- const loginIndex = args.indexOf('login');
437
- const loginArgs = loginIndex !== -1 ? args.slice(loginIndex + 1) : [];
364
+ try {
365
+ console.log('\n 🔧 vibex.sh Session Initialization\n');
366
+
367
+ // Fetch available parsers from public API
368
+ let availableParsers = [];
369
+ try {
370
+ const parsersResponse = await httpRequest(`${webUrl}/api/parsers`, {
371
+ method: 'GET',
372
+ });
373
+ if (parsersResponse.ok) {
374
+ availableParsers = await parsersResponse.json();
375
+ } else {
376
+ console.warn(' ⚠️ Failed to fetch parsers from API, using fallback list');
377
+ }
378
+ } catch (e) {
379
+ console.warn(' ⚠️ Failed to fetch parsers from API, using fallback list');
380
+ }
381
+
382
+ // Fallback to hardcoded list if API fails or returns empty
383
+ if (!availableParsers || availableParsers.length === 0) {
384
+ availableParsers = [
385
+ { id: 'nginx', name: 'Nginx Access Log', category: 'web' },
386
+ { id: 'apache', name: 'Apache Access Log', category: 'web' },
387
+ { id: 'docker', name: 'Docker Container Logs', category: 'system' },
388
+ { id: 'kubernetes', name: 'Kubernetes Pod/Container Logs', category: 'system' },
389
+ ];
390
+ }
391
+
392
+ // Filter out mandatory parsers for selection (if any)
393
+ const selectableParsers = availableParsers.filter(p => !p.isMandatory);
394
+
395
+ console.log(' What kind of logs are these? (Optional - leave empty for auto-detection)');
396
+ if (selectableParsers.length > 0) {
397
+ console.log(' Available log types:');
398
+ selectableParsers.forEach((p, i) => {
399
+ console.log(` ${i + 1}. ${p.name} (${p.id})`);
400
+ });
401
+ console.log(' (Leave empty for auto-detection)\n');
402
+ } else {
403
+ console.log(' Available log types: (Leave empty for auto-detection)\n');
404
+ }
405
+
406
+ const answer = await question(' Enter comma-separated numbers or parser IDs (e.g., 1,2 or nginx,apache): ');
407
+ rl.close();
408
+
409
+ let enabledParsers = [];
410
+ if (answer.trim()) {
411
+ const selections = answer.split(',').map(s => s.trim());
412
+ selections.forEach(sel => {
413
+ // Check if it's a number
414
+ const num = parseInt(sel, 10);
415
+ if (!isNaN(num) && num > 0 && num <= selectableParsers.length) {
416
+ enabledParsers.push(selectableParsers[num - 1].id);
417
+ } else if (selectableParsers.find(p => p.id === sel)) {
418
+ enabledParsers.push(sel);
419
+ }
420
+ });
421
+ }
438
422
 
439
- // Create a separate command instance for login
440
- const loginCmd = new Command();
441
- loginCmd
442
- .option('-l, --local', 'Use localhost')
443
- .option('--web <url>', 'Web server URL')
444
- .option('--server <url>', 'Shorthand for --web');
423
+ // Use parser flag if provided, otherwise use interactive selection
424
+ const parserFlag = options.parser || options.parsers;
425
+ if (parserFlag) {
426
+ if (typeof parserFlag === 'string') {
427
+ enabledParsers = parserFlag.split(',').map(p => p.trim());
428
+ }
429
+ }
430
+
431
+ // Get token - required for authenticated session creation
432
+ let token = process.env.VIBEX_TOKEN || await getStoredToken();
433
+ if (!token) {
434
+ console.error('\n ✗ Authentication required');
435
+ console.error(' 💡 Run: npx vibex-sh login');
436
+ process.exit(1);
437
+ }
438
+
439
+ // Create session with enabledParsers (authenticated)
440
+ const createUrl = `${webUrl}/api/sessions/create`;
441
+ const response = await httpRequest(createUrl, {
442
+ method: 'POST',
443
+ headers: {
444
+ 'Content-Type': 'application/json',
445
+ 'Authorization': `Bearer ${token}`,
446
+ },
447
+ body: JSON.stringify({
448
+ enabledParsers: enabledParsers.length > 0 ? enabledParsers : undefined,
449
+ }),
450
+ });
451
+
452
+ if (!response.ok) {
453
+ const errorData = await response.json();
454
+ if (response.status === 401 || response.status === 403) {
455
+ console.error(`\n ✗ Authentication failed: ${errorData.message || 'Invalid token'}`);
456
+ console.error(' 💡 Run: npx vibex-sh login');
457
+ } else {
458
+ console.error(`\n ✗ Failed to create session: ${errorData.message || 'Unknown error'}`);
459
+ }
460
+ process.exit(1);
461
+ }
445
462
 
446
- // Parse only the options (args after 'login')
447
- if (loginArgs.length > 0) {
448
- loginCmd.parse(['node', 'vibex', ...loginArgs], { from: 'user' });
463
+ const data = await response.json();
464
+ const createdSessionId = data.sessionId;
465
+ const createdAuthCode = data.authCode;
466
+
467
+ console.log('\n ✅ Session created successfully!\n');
468
+ printBanner(createdSessionId, createdAuthCode);
469
+ if (enabledParsers.length > 0) {
470
+ console.log(` 📋 Log Types: ${enabledParsers.join(', ')}`);
449
471
  } else {
450
- loginCmd.parse(['node', 'vibex'], { from: 'user' });
472
+ console.log(' 📋 Log Types: Auto-detection (default parsers)');
451
473
  }
474
+ console.log(`\n 💡 Use this session ID: ${createdSessionId}`);
475
+ console.log(` Example: echo '{"cpu": 45}' | npx vibex-sh -s ${createdSessionId}\n`);
452
476
 
453
- const options = loginCmd.opts();
454
- const { webUrl } = getUrls(options);
455
- await handleLogin(webUrl);
456
477
  process.exit(0);
478
+ } catch (error) {
479
+ rl.close();
480
+ console.error(`\n ✗ Error: ${error.message}`);
481
+ process.exit(1);
457
482
  }
483
+ }
484
+
485
+ async function main() {
486
+ // Configure main program
487
+ program
488
+ .name('vibex')
489
+ .description('vibex.sh CLI - Send logs to vibex.sh for real-time analysis')
490
+ .version(cliVersion, '-v, --version', 'Display version number');
458
491
 
492
+ // Login command
493
+ program
494
+ .command('login')
495
+ .description('Authenticate with vibex.sh and save your token')
496
+ .action(async () => {
497
+ await handleLogin();
498
+ process.exit(0);
499
+ });
500
+
501
+ // Init command
502
+ program
503
+ .command('init')
504
+ .description('Create a new session with parser selection')
505
+ .option('--parser <parsers>', 'Comma-separated list of parser IDs (e.g., nginx,postgres)')
506
+ .option('--parsers <parsers>', 'Alias for --parser')
507
+ .action(async (options) => {
508
+ await handleInit(options);
509
+ });
510
+
511
+ // Main command (default) - send logs
459
512
  program
460
- .version(cliVersion, '-v, --version', 'Display version number')
461
513
  .option('-s, --session-id <id>', 'Reuse existing session ID')
462
- .option('-l, --local', 'Use localhost (web: 3000, socket: 3001)')
463
- .option('--web <url>', 'Web server URL (e.g., http://localhost:3000)')
464
- .option('--socket <url>', 'Socket server URL (e.g., http://localhost:3001)')
465
- .option('--server <url>', 'Shorthand for --web (auto-derives socket URL)')
466
514
  .option('--token <token>', 'Authentication token (or use VIBEX_TOKEN env var)')
467
- .parse();
515
+ .option('--parser <parsers>', 'Comma-separated list of parser IDs (e.g., nginx,postgres)')
516
+ .option('--parsers <parsers>', 'Alias for --parser')
517
+ .action(async (options) => {
518
+ await handleSendLogs(options);
519
+ });
520
+
521
+ // Parse arguments
522
+ program.parse();
523
+ }
468
524
 
469
- const options = program.opts();
470
- const { webUrl, socketUrl } = getUrls(options);
525
+ /**
526
+ * Handle send logs command (default/main command)
527
+ */
528
+ async function handleSendLogs(options) {
529
+ const { webUrl, socketUrl } = getProductionUrls();
471
530
 
472
- // Get token from flag, env var, or stored config
531
+ // Get token - REQUIRED for all operations
473
532
  let token = options.token || process.env.VIBEX_TOKEN || await getStoredToken();
533
+ if (!token) {
534
+ console.error('\n ✗ Authentication required');
535
+ console.error(' 💡 Run: npx vibex-sh login to authenticate');
536
+ console.error(' 💡 Or set VIBEX_TOKEN environment variable\n');
537
+ process.exit(1);
538
+ }
474
539
 
475
540
  let sessionId;
476
541
  let authCode = null;
477
542
 
543
+ // Check if stdin is available (piped input)
544
+ const isTTY = process.stdin.isTTY;
545
+ const hasStdin = !isTTY;
546
+
547
+ // If no session ID and no stdin, show usage and exit
548
+ if (!options.sessionId && !hasStdin) {
549
+ program.help();
550
+ process.exit(0);
551
+ }
552
+
478
553
  // If session ID is provided, use it (existing session)
479
554
  if (options.sessionId) {
480
555
  sessionId = normalizeSessionId(options.sessionId);
481
556
 
482
- // If token is available, try to claim the session
483
- if (token) {
484
- authCode = await claimSession(sessionId, token, webUrl);
485
- }
486
-
487
557
  // When reusing a session, show minimal info
488
558
  console.log(` 🔍 Sending logs to session: ${sessionId}\n`);
489
559
  } else {
490
- // No session ID provided - create a new anonymous session
560
+ // No session ID provided - create a new authenticated session
561
+ // Check for --parser or --parsers flag for parser selection
562
+ let enabledParsers = [];
563
+ if (options.parser || options.parsers) {
564
+ const parserList = options.parser || options.parsers;
565
+ if (Array.isArray(parserList)) {
566
+ enabledParsers = parserList;
567
+ } else if (typeof parserList === 'string') {
568
+ enabledParsers = parserList.split(',').map(p => p.trim());
569
+ }
570
+ }
571
+
491
572
  try {
492
- const createUrl = `${webUrl}/api/sessions/create-anonymous`;
573
+ const createUrl = `${webUrl}/api/sessions/create`;
493
574
  const response = await httpRequest(createUrl, {
494
575
  method: 'POST',
495
- headers: { 'Content-Type': 'application/json' },
576
+ headers: {
577
+ 'Content-Type': 'application/json',
578
+ 'Authorization': `Bearer ${token}`,
579
+ },
580
+ body: JSON.stringify({
581
+ enabledParsers: enabledParsers.length > 0 ? enabledParsers : undefined,
582
+ }),
496
583
  });
497
584
 
498
585
  if (!response.ok) {
499
586
  const errorData = await response.json();
500
- console.error(` ✗ Failed to create session: ${errorData.message || 'Unknown error'}`);
587
+ if (response.status === 401 || response.status === 403) {
588
+ console.error(` ✗ Authentication failed: ${errorData.message || 'Invalid token'}`);
589
+ console.error(' 💡 Run: npx vibex-sh login');
590
+ } else {
591
+ console.error(` ✗ Failed to create session: ${errorData.message || 'Unknown error'}`);
592
+ }
501
593
  process.exit(1);
502
594
  }
503
595
 
@@ -505,21 +597,15 @@ async function main() {
505
597
  sessionId = data.sessionId; // Server-generated unique session ID
506
598
  authCode = data.authCode; // Server-generated auth code
507
599
 
508
- // If token is available, claim the session
509
- if (token) {
510
- const claimAuthCode = await claimSession(sessionId, token, webUrl);
511
- if (claimAuthCode) {
512
- authCode = claimAuthCode;
513
- console.log(' ✓ Session automatically claimed to your account\n');
514
- }
515
- }
516
-
517
600
  // Print banner for new session
518
- printBanner(sessionId, webUrl, authCode);
519
- const localFlag = webUrl.includes('localhost') ? ' --local' : '';
520
- const sessionSlug = sessionId.replace(/^vibex-/, ''); // Remove prefix for example
601
+ printBanner(sessionId, authCode);
602
+ if (enabledParsers.length > 0) {
603
+ console.log(` 📋 Log Types: ${enabledParsers.join(', ')}`);
604
+ } else {
605
+ console.log(' 📋 Log Types: Auto-detection (default parsers)');
606
+ }
521
607
  console.log(' 💡 Tip: Use -s to send more logs to this session');
522
- console.log(` Example: echo '{"cpu": 45, "memory": 78}' | npx vibex-sh -s ${sessionSlug}${localFlag}\n`);
608
+ console.log(` Example: echo '{"cpu": 45, "memory": 78}' | npx vibex-sh -s ${sessionId}\n`);
523
609
  } catch (error) {
524
610
  console.error(` ✗ Error creating session: ${error.message}`);
525
611
  process.exit(1);
@@ -665,6 +751,7 @@ async function main() {
665
751
  if (!receivedAuthCode || receivedAuthCode !== message.data.authCode) {
666
752
  receivedAuthCode = message.data.authCode;
667
753
  if (isNewSession) {
754
+ const { webUrl } = getProductionUrls();
668
755
  console.log(` 🔑 Auth Code: ${receivedAuthCode}`);
669
756
  console.log(` 📋 Dashboard: ${webUrl}/${sessionId}?auth=${receivedAuthCode}\n`);
670
757
  }
@@ -823,32 +910,20 @@ async function main() {
823
910
  };
824
911
 
825
912
  // Send logs via HTTP POST (non-blocking, same as SDKs)
826
- // Use Cloudflare Worker endpoint (port 8787 for local, or Worker URL for production)
827
- // Token is optional - anonymous sessions can send logs without authentication
913
+ // Always use production Cloudflare Worker endpoint
914
+ // Token is REQUIRED - all sessions must be authenticated
828
915
  // HTTP POST works independently of WebSocket - don't wait for WebSocket connection
829
916
  const sendLogViaHTTP = async (logData) => {
830
917
  try {
831
- // Determine ingest URL
832
- let ingestUrl;
833
- if (webUrl.includes('localhost') || webUrl.includes('127.0.0.1')) {
834
- // Local development - use Workers dev server
835
- ingestUrl = 'http://localhost:8787/api/v1/ingest';
836
- } else if (process.env.VIBEX_WORKER_URL) {
837
- // Use explicit Worker URL if set
838
- ingestUrl = `${process.env.VIBEX_WORKER_URL}/api/v1/ingest`;
839
- } else {
840
- // Production default - use Worker URL (not web URL)
841
- const defaultWorkerUrl = 'https://ingest.vibex.sh';
842
- ingestUrl = `${defaultWorkerUrl}/api/v1/ingest`;
843
- }
918
+ // Always use production worker URL
919
+ const workerUrl = process.env.VIBEX_WORKER_URL || DEFAULT_WORKER_URL;
920
+ const ingestUrl = `${workerUrl}/api/v1/ingest`;
844
921
 
845
- // Build headers - only include Authorization if token exists
922
+ // Build headers - Authorization is REQUIRED
846
923
  const headers = {
847
924
  'Content-Type': 'application/json',
925
+ 'Authorization': `Bearer ${token}`,
848
926
  };
849
- if (token) {
850
- headers['Authorization'] = `Bearer ${token}`;
851
- }
852
927
 
853
928
  const response = await fetch(ingestUrl, {
854
929
  method: 'POST',
@@ -949,8 +1024,17 @@ async function main() {
949
1024
  }
950
1025
  };
951
1026
 
952
- // Start WebSocket connection
953
- connectWebSocket();
1027
+ // Only start WebSocket connection if we have stdin (piped input)
1028
+ // Don't connect WebSocket when run without parameters
1029
+ if (hasStdin) {
1030
+ connectWebSocket();
1031
+ }
1032
+
1033
+ // Only read from stdin if we have piped input
1034
+ if (!hasStdin) {
1035
+ // No stdin - exit after showing session info
1036
+ process.exit(0);
1037
+ }
954
1038
 
955
1039
  const rl = readline.createInterface({
956
1040
  input: process.stdin,
@@ -1029,8 +1113,10 @@ async function main() {
1029
1113
  reconnectTimeout = null;
1030
1114
  }
1031
1115
 
1032
- // Graceful shutdown - wait for close handshake
1033
- await closeWebSocket();
1116
+ // Graceful shutdown - wait for close handshake (only if WebSocket was connected)
1117
+ if (hasStdin && socket) {
1118
+ await closeWebSocket();
1119
+ }
1034
1120
 
1035
1121
  // Give a moment for any final cleanup
1036
1122
  setTimeout(() => process.exit(0), 100);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibex-sh",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Zero-config observability CLI - pipe logs and visualize instantly",
5
5
  "type": "module",
6
6
  "bin": {