vibex-sh 0.10.1 → 0.11.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/index.js +360 -147
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -11,6 +11,14 @@ 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
|
+
const PIPED_INPUT_DELAY_MS = parseInt(process.env.VIBEX_PIPE_DELAY_MS || '300', 10);
|
|
21
|
+
|
|
14
22
|
// Get version from package.json
|
|
15
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
24
|
const __dirname = dirname(__filename);
|
|
@@ -153,45 +161,16 @@ function normalizeToHybrid(message, level, payload) {
|
|
|
153
161
|
return hybrid;
|
|
154
162
|
}
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
function getUrls(options) {
|
|
163
|
-
const { web, socket, server } = options;
|
|
164
|
-
|
|
165
|
-
// Priority 1: Explicit --web and --socket flags (highest priority)
|
|
166
|
-
if (web) {
|
|
167
|
-
return {
|
|
168
|
-
webUrl: web,
|
|
169
|
-
socketUrl: socket || deriveSocketUrl(web),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Priority 2: --server flag (shorthand for --web)
|
|
174
|
-
if (server) {
|
|
175
|
-
return {
|
|
176
|
-
webUrl: server,
|
|
177
|
-
socketUrl: socket || deriveSocketUrl(server),
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Priority 3: Environment variables
|
|
182
|
-
if (process.env.VIBEX_WEB_URL) {
|
|
183
|
-
return {
|
|
184
|
-
webUrl: process.env.VIBEX_WEB_URL,
|
|
185
|
-
socketUrl: process.env.VIBEX_SOCKET_URL || socket || deriveSocketUrl(process.env.VIBEX_WEB_URL),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Priority 4: Production defaults
|
|
164
|
+
/**
|
|
165
|
+
* Get production URLs
|
|
166
|
+
* CLI only supports production server
|
|
167
|
+
*/
|
|
168
|
+
function getProductionUrls() {
|
|
190
169
|
// Always use production worker WebSocket endpoint
|
|
191
|
-
const
|
|
170
|
+
const workerUrl = process.env.VIBEX_WORKER_URL || DEFAULT_WORKER_URL;
|
|
192
171
|
return {
|
|
193
|
-
webUrl:
|
|
194
|
-
socketUrl:
|
|
172
|
+
webUrl: DEFAULT_WEB_URL,
|
|
173
|
+
socketUrl: workerUrl.replace('https://', 'wss://').replace('http://', 'ws://'),
|
|
195
174
|
};
|
|
196
175
|
}
|
|
197
176
|
|
|
@@ -230,7 +209,7 @@ function getStoredConfig() {
|
|
|
230
209
|
return null;
|
|
231
210
|
}
|
|
232
211
|
|
|
233
|
-
async function storeToken(token
|
|
212
|
+
async function storeToken(token) {
|
|
234
213
|
try {
|
|
235
214
|
const configPath = getConfigPath();
|
|
236
215
|
const configDir = join(homedir(), '.vibex');
|
|
@@ -240,7 +219,6 @@ async function storeToken(token, webUrl = null) {
|
|
|
240
219
|
|
|
241
220
|
const config = {
|
|
242
221
|
token,
|
|
243
|
-
...(webUrl && { webUrl }), // Store webUrl if provided
|
|
244
222
|
updatedAt: new Date().toISOString(),
|
|
245
223
|
};
|
|
246
224
|
|
|
@@ -252,9 +230,10 @@ async function storeToken(token, webUrl = null) {
|
|
|
252
230
|
}
|
|
253
231
|
}
|
|
254
232
|
|
|
255
|
-
async function handleLogin(
|
|
233
|
+
async function handleLogin() {
|
|
256
234
|
const configPath = getConfigPath();
|
|
257
235
|
const existingConfig = getStoredConfig();
|
|
236
|
+
const { webUrl } = getProductionUrls();
|
|
258
237
|
|
|
259
238
|
console.log('\n 🔐 vibex.sh CLI Authentication\n');
|
|
260
239
|
console.log(` 📁 Config location: ${configPath}`);
|
|
@@ -263,8 +242,9 @@ async function handleLogin(webUrl) {
|
|
|
263
242
|
console.log(` ⚠️ You already have a token stored. This will replace it.\n`);
|
|
264
243
|
}
|
|
265
244
|
|
|
266
|
-
|
|
267
|
-
const
|
|
245
|
+
// Generate unique state (nonce) for OAuth flow
|
|
246
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
247
|
+
const authUrl = `${webUrl}/api/cli-auth?state=${state}`;
|
|
268
248
|
|
|
269
249
|
console.log(' Opening browser for authentication...\n');
|
|
270
250
|
console.log(` If browser doesn't open, visit: ${authUrl}\n`);
|
|
@@ -284,21 +264,20 @@ async function handleLogin(webUrl) {
|
|
|
284
264
|
|
|
285
265
|
// Poll for token
|
|
286
266
|
console.log(' Waiting for authentication...');
|
|
287
|
-
const maxAttempts = 60; // 60 seconds
|
|
288
267
|
let attempts = 0;
|
|
289
268
|
|
|
290
|
-
while (attempts <
|
|
291
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
269
|
+
while (attempts < MAX_POLL_ATTEMPTS) {
|
|
270
|
+
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
292
271
|
attempts++;
|
|
293
272
|
|
|
294
273
|
try {
|
|
295
|
-
const response = await httpRequest(`${webUrl}/api/cli-auth?
|
|
274
|
+
const response = await httpRequest(`${webUrl}/api/cli-auth?state=${state}`, {
|
|
296
275
|
method: 'GET',
|
|
297
276
|
});
|
|
298
277
|
if (response.ok) {
|
|
299
278
|
const data = await response.json();
|
|
300
279
|
if (data.success && data.token) {
|
|
301
|
-
await storeToken(data.token
|
|
280
|
+
await storeToken(data.token);
|
|
302
281
|
const configPath = getConfigPath();
|
|
303
282
|
console.log('\n ✅ Authentication successful!');
|
|
304
283
|
console.log(` 📁 Token saved to: ${configPath}`);
|
|
@@ -342,39 +321,16 @@ function httpRequest(url, options) {
|
|
|
342
321
|
});
|
|
343
322
|
}
|
|
344
323
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
// Normalize session ID before claiming
|
|
350
|
-
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
351
|
-
const response = await httpRequest(`${webUrl}/api/auth/claim-session-with-token`, {
|
|
352
|
-
method: 'POST',
|
|
353
|
-
headers: { 'Content-Type': 'application/json' },
|
|
354
|
-
body: JSON.stringify({
|
|
355
|
-
sessionId: normalizedSessionId,
|
|
356
|
-
token,
|
|
357
|
-
}),
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
if (response.ok) {
|
|
361
|
-
// Parse response to get auth code
|
|
362
|
-
const responseData = await response.json();
|
|
363
|
-
return responseData.authCode || null;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return null;
|
|
367
|
-
} catch (error) {
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
324
|
+
// claimSession function removed - session claiming is no longer supported
|
|
325
|
+
// All sessions must be created with authentication
|
|
371
326
|
|
|
372
327
|
// Removed getSessionAuthCode - auth codes should only come from:
|
|
373
328
|
// 1. claim-session-with-token response (for claimed sessions)
|
|
374
329
|
// 2. socket.io session-auth-code event (for unclaimed sessions)
|
|
375
330
|
// Never fetch auth codes via public API endpoint - security vulnerability
|
|
376
331
|
|
|
377
|
-
function printBanner(sessionId,
|
|
332
|
+
function printBanner(sessionId, authCode = null) {
|
|
333
|
+
const { webUrl } = getProductionUrls();
|
|
378
334
|
const dashboardUrl = authCode
|
|
379
335
|
? `${webUrl}/${sessionId}?auth=${authCode}`
|
|
380
336
|
: `${webUrl}/${sessionId}`;
|
|
@@ -392,88 +348,273 @@ function printBanner(sessionId, webUrl, authCode = null) {
|
|
|
392
348
|
console.log('\n');
|
|
393
349
|
}
|
|
394
350
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Handle init command - create a new session with parser selection
|
|
353
|
+
*/
|
|
354
|
+
async function handleInit(options) {
|
|
355
|
+
const { webUrl } = getProductionUrls();
|
|
399
356
|
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
process.
|
|
404
|
-
}
|
|
357
|
+
// Interactive prompt for parser selection
|
|
358
|
+
const rl = readline.createInterface({
|
|
359
|
+
input: process.stdin,
|
|
360
|
+
output: process.stdout,
|
|
361
|
+
});
|
|
405
362
|
|
|
406
|
-
|
|
407
|
-
// Check process.argv directly - look for 'login' as a standalone argument
|
|
408
|
-
// This must happen FIRST, before any commander parsing
|
|
409
|
-
// Check if 'login' appears anywhere in process.argv (works with npx too)
|
|
410
|
-
const hasLogin = allArgs.includes('login') || args.includes('login');
|
|
363
|
+
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
411
364
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
365
|
+
try {
|
|
366
|
+
console.log('\n 🔧 vibex.sh Session Initialization\n');
|
|
367
|
+
|
|
368
|
+
// Fetch available parsers from public API
|
|
369
|
+
let availableParsers = [];
|
|
370
|
+
try {
|
|
371
|
+
const parsersResponse = await httpRequest(`${webUrl}/api/parsers`, {
|
|
372
|
+
method: 'GET',
|
|
373
|
+
});
|
|
374
|
+
if (parsersResponse.ok) {
|
|
375
|
+
availableParsers = await parsersResponse.json();
|
|
376
|
+
} else {
|
|
377
|
+
console.warn(' ⚠️ Failed to fetch parsers from API, using fallback list');
|
|
378
|
+
}
|
|
379
|
+
} catch (e) {
|
|
380
|
+
console.warn(' ⚠️ Failed to fetch parsers from API, using fallback list');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Fallback to hardcoded list if API fails or returns empty
|
|
384
|
+
if (!availableParsers || availableParsers.length === 0) {
|
|
385
|
+
availableParsers = [
|
|
386
|
+
{ id: 'nginx', name: 'Nginx Access Log', category: 'web' },
|
|
387
|
+
{ id: 'apache', name: 'Apache Access Log', category: 'web' },
|
|
388
|
+
{ id: 'docker', name: 'Docker Container Logs', category: 'system' },
|
|
389
|
+
{ id: 'kubernetes', name: 'Kubernetes Pod/Container Logs', category: 'system' },
|
|
390
|
+
];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Mandatory parsers that are always included
|
|
394
|
+
const mandatoryParserIds = ['json-in-text', 'raw', 'keyvalue', 'stacktrace', 'smart-pattern'];
|
|
395
|
+
|
|
396
|
+
// Filter out mandatory parsers for selection (only optional parsers can be selected)
|
|
397
|
+
const selectableParsers = availableParsers.filter(p => !mandatoryParserIds.includes(p.id) && !p.isMandatory);
|
|
398
|
+
|
|
399
|
+
// Pre-select mandatory parsers
|
|
400
|
+
let enabledParsers = [...mandatoryParserIds];
|
|
401
|
+
|
|
402
|
+
console.log(' Mandatory parsers are automatically included:');
|
|
403
|
+
mandatoryParserIds.forEach(id => {
|
|
404
|
+
const parser = availableParsers.find(p => p.id === id);
|
|
405
|
+
if (parser) {
|
|
406
|
+
console.log(` ✓ ${parser.name}`);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
console.log('');
|
|
410
|
+
|
|
411
|
+
console.log(' Select additional optional parsers (leave empty for mandatory only):');
|
|
412
|
+
if (selectableParsers.length > 0) {
|
|
413
|
+
console.log(' Available optional parsers:');
|
|
414
|
+
selectableParsers.forEach((p, i) => {
|
|
415
|
+
console.log(` ${i + 1}. ${p.name} (${p.id})`);
|
|
416
|
+
});
|
|
417
|
+
console.log(' (Leave empty for mandatory parsers only)\n');
|
|
426
418
|
} else {
|
|
427
|
-
|
|
419
|
+
console.log(' No additional optional parsers available.\n');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const answer = await question(' Enter comma-separated numbers or parser IDs (e.g., 1,2 or docker,kubernetes): ');
|
|
423
|
+
rl.close();
|
|
424
|
+
|
|
425
|
+
if (answer.trim()) {
|
|
426
|
+
const selections = answer.split(',').map(s => s.trim());
|
|
427
|
+
selections.forEach(sel => {
|
|
428
|
+
// Check if it's a number
|
|
429
|
+
const num = parseInt(sel, 10);
|
|
430
|
+
if (!isNaN(num) && num > 0 && num <= selectableParsers.length) {
|
|
431
|
+
enabledParsers.push(selectableParsers[num - 1].id);
|
|
432
|
+
} else if (selectableParsers.find(p => p.id === sel)) {
|
|
433
|
+
enabledParsers.push(sel);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Use parser flag if provided, otherwise use interactive selection
|
|
439
|
+
const parserFlag = options.parser || options.parsers;
|
|
440
|
+
if (parserFlag) {
|
|
441
|
+
if (typeof parserFlag === 'string') {
|
|
442
|
+
// Add optional parsers from flag, but always include mandatory
|
|
443
|
+
const flagParsers = parserFlag.split(',').map(p => p.trim());
|
|
444
|
+
enabledParsers = [...new Set([...mandatoryParserIds, ...flagParsers])];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Get token - required for authenticated session creation
|
|
449
|
+
let token = process.env.VIBEX_TOKEN || await getStoredToken();
|
|
450
|
+
if (!token) {
|
|
451
|
+
console.error('\n ✗ Authentication required');
|
|
452
|
+
console.error(' 💡 Run: npx vibex-sh login');
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Create session with enabledParsers (authenticated)
|
|
457
|
+
const createUrl = `${webUrl}/api/sessions/create`;
|
|
458
|
+
const response = await httpRequest(createUrl, {
|
|
459
|
+
method: 'POST',
|
|
460
|
+
headers: {
|
|
461
|
+
'Content-Type': 'application/json',
|
|
462
|
+
'Authorization': `Bearer ${token}`,
|
|
463
|
+
},
|
|
464
|
+
body: JSON.stringify({
|
|
465
|
+
enabledParsers: enabledParsers.length > 0 ? enabledParsers : undefined,
|
|
466
|
+
}),
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (!response.ok) {
|
|
470
|
+
const errorData = await response.json();
|
|
471
|
+
if (response.status === 401 || response.status === 403) {
|
|
472
|
+
console.error(`\n ✗ Authentication failed: ${errorData.message || 'Invalid token'}`);
|
|
473
|
+
console.error(' 💡 Run: npx vibex-sh login');
|
|
474
|
+
} else {
|
|
475
|
+
console.error(`\n ✗ Failed to create session: ${errorData.message || 'Unknown error'}`);
|
|
476
|
+
}
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const data = await response.json();
|
|
481
|
+
const createdSessionId = data.sessionId;
|
|
482
|
+
const createdAuthCode = data.authCode;
|
|
483
|
+
|
|
484
|
+
console.log('\n ✅ Session created successfully!\n');
|
|
485
|
+
printBanner(createdSessionId, createdAuthCode);
|
|
486
|
+
|
|
487
|
+
// Separate mandatory and optional parsers for display
|
|
488
|
+
const mandatoryIncluded = enabledParsers.filter(id => mandatoryParserIds.includes(id));
|
|
489
|
+
const optionalIncluded = enabledParsers.filter(id => !mandatoryParserIds.includes(id));
|
|
490
|
+
|
|
491
|
+
if (mandatoryIncluded.length > 0) {
|
|
492
|
+
console.log(` 📋 Mandatory parsers: ${mandatoryIncluded.join(', ')}`);
|
|
493
|
+
}
|
|
494
|
+
if (optionalIncluded.length > 0) {
|
|
495
|
+
console.log(` 📋 Optional parsers: ${optionalIncluded.join(', ')}`);
|
|
496
|
+
} else if (mandatoryIncluded.length === mandatoryParserIds.length) {
|
|
497
|
+
console.log(' 📋 Using mandatory parsers only');
|
|
428
498
|
}
|
|
499
|
+
console.log(`\n 💡 Use this session ID: ${createdSessionId}`);
|
|
500
|
+
console.log(` Example: echo '{"cpu": 45}' | npx vibex-sh -s ${createdSessionId}\n`);
|
|
429
501
|
|
|
430
|
-
const options = loginCmd.opts();
|
|
431
|
-
const { webUrl } = getUrls(options);
|
|
432
|
-
await handleLogin(webUrl);
|
|
433
502
|
process.exit(0);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
rl.close();
|
|
505
|
+
console.error(`\n ✗ Error: ${error.message}`);
|
|
506
|
+
process.exit(1);
|
|
434
507
|
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function main() {
|
|
511
|
+
// Configure main program
|
|
512
|
+
program
|
|
513
|
+
.name('vibex')
|
|
514
|
+
.description('vibex.sh CLI - Send logs to vibex.sh for real-time analysis')
|
|
515
|
+
.version(cliVersion, '-v, --version', 'Display version number');
|
|
516
|
+
|
|
517
|
+
// Login command
|
|
518
|
+
program
|
|
519
|
+
.command('login')
|
|
520
|
+
.description('Authenticate with vibex.sh and save your token')
|
|
521
|
+
.action(async () => {
|
|
522
|
+
await handleLogin();
|
|
523
|
+
process.exit(0);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Init command
|
|
527
|
+
program
|
|
528
|
+
.command('init')
|
|
529
|
+
.description('Create a new session with parser selection')
|
|
530
|
+
.option('--parser <parsers>', 'Comma-separated list of parser IDs (e.g., nginx,postgres)')
|
|
531
|
+
.option('--parsers <parsers>', 'Alias for --parser')
|
|
532
|
+
.action(async (options) => {
|
|
533
|
+
await handleInit(options);
|
|
534
|
+
});
|
|
435
535
|
|
|
536
|
+
// Main command (default) - send logs
|
|
436
537
|
program
|
|
437
|
-
.version(cliVersion, '-v, --version', 'Display version number')
|
|
438
538
|
.option('-s, --session-id <id>', 'Reuse existing session ID')
|
|
439
|
-
.option('--web <url>', 'Web server URL')
|
|
440
|
-
.option('--socket <url>', 'Socket server URL')
|
|
441
|
-
.option('--server <url>', 'Shorthand for --web (auto-derives socket URL)')
|
|
442
539
|
.option('--token <token>', 'Authentication token (or use VIBEX_TOKEN env var)')
|
|
443
|
-
.
|
|
540
|
+
.option('--parser <parsers>', 'Comma-separated list of parser IDs (e.g., nginx,postgres)')
|
|
541
|
+
.option('--parsers <parsers>', 'Alias for --parser')
|
|
542
|
+
.action(async (options) => {
|
|
543
|
+
await handleSendLogs(options);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Parse arguments
|
|
547
|
+
program.parse();
|
|
548
|
+
}
|
|
444
549
|
|
|
445
|
-
|
|
446
|
-
|
|
550
|
+
/**
|
|
551
|
+
* Handle send logs command (default/main command)
|
|
552
|
+
*/
|
|
553
|
+
async function handleSendLogs(options) {
|
|
554
|
+
const { webUrl, socketUrl } = getProductionUrls();
|
|
447
555
|
|
|
448
|
-
// Get token
|
|
556
|
+
// Get token - REQUIRED for all operations
|
|
449
557
|
let token = options.token || process.env.VIBEX_TOKEN || await getStoredToken();
|
|
558
|
+
if (!token) {
|
|
559
|
+
console.error('\n ✗ Authentication required');
|
|
560
|
+
console.error(' 💡 Run: npx vibex-sh login to authenticate');
|
|
561
|
+
console.error(' 💡 Or set VIBEX_TOKEN environment variable\n');
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
450
564
|
|
|
451
565
|
let sessionId;
|
|
452
566
|
let authCode = null;
|
|
453
567
|
|
|
568
|
+
// Check if stdin is available (piped input)
|
|
569
|
+
const isTTY = process.stdin.isTTY;
|
|
570
|
+
const hasStdin = !isTTY;
|
|
571
|
+
|
|
572
|
+
// If no session ID and no stdin, show usage and exit
|
|
573
|
+
if (!options.sessionId && !hasStdin) {
|
|
574
|
+
program.help();
|
|
575
|
+
process.exit(0);
|
|
576
|
+
}
|
|
577
|
+
|
|
454
578
|
// If session ID is provided, use it (existing session)
|
|
455
579
|
if (options.sessionId) {
|
|
456
580
|
sessionId = normalizeSessionId(options.sessionId);
|
|
457
581
|
|
|
458
|
-
// If token is available, try to claim the session
|
|
459
|
-
if (token) {
|
|
460
|
-
authCode = await claimSession(sessionId, token, webUrl);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
582
|
// When reusing a session, show minimal info
|
|
464
583
|
console.log(` 🔍 Sending logs to session: ${sessionId}\n`);
|
|
465
584
|
} else {
|
|
466
|
-
// No session ID provided - create a new
|
|
585
|
+
// No session ID provided - create a new authenticated session
|
|
586
|
+
// Check for --parser or --parsers flag for parser selection
|
|
587
|
+
let enabledParsers = [];
|
|
588
|
+
if (options.parser || options.parsers) {
|
|
589
|
+
const parserList = options.parser || options.parsers;
|
|
590
|
+
if (Array.isArray(parserList)) {
|
|
591
|
+
enabledParsers = parserList;
|
|
592
|
+
} else if (typeof parserList === 'string') {
|
|
593
|
+
enabledParsers = parserList.split(',').map(p => p.trim());
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
467
597
|
try {
|
|
468
|
-
const createUrl = `${webUrl}/api/sessions/create
|
|
598
|
+
const createUrl = `${webUrl}/api/sessions/create`;
|
|
469
599
|
const response = await httpRequest(createUrl, {
|
|
470
600
|
method: 'POST',
|
|
471
|
-
headers: {
|
|
601
|
+
headers: {
|
|
602
|
+
'Content-Type': 'application/json',
|
|
603
|
+
'Authorization': `Bearer ${token}`,
|
|
604
|
+
},
|
|
605
|
+
body: JSON.stringify({
|
|
606
|
+
enabledParsers: enabledParsers.length > 0 ? enabledParsers : undefined,
|
|
607
|
+
}),
|
|
472
608
|
});
|
|
473
609
|
|
|
474
610
|
if (!response.ok) {
|
|
475
611
|
const errorData = await response.json();
|
|
476
|
-
|
|
612
|
+
if (response.status === 401 || response.status === 403) {
|
|
613
|
+
console.error(` ✗ Authentication failed: ${errorData.message || 'Invalid token'}`);
|
|
614
|
+
console.error(' 💡 Run: npx vibex-sh login');
|
|
615
|
+
} else {
|
|
616
|
+
console.error(` ✗ Failed to create session: ${errorData.message || 'Unknown error'}`);
|
|
617
|
+
}
|
|
477
618
|
process.exit(1);
|
|
478
619
|
}
|
|
479
620
|
|
|
@@ -481,17 +622,13 @@ async function main() {
|
|
|
481
622
|
sessionId = data.sessionId; // Server-generated unique session ID
|
|
482
623
|
authCode = data.authCode; // Server-generated auth code
|
|
483
624
|
|
|
484
|
-
// If token is available, claim the session
|
|
485
|
-
if (token) {
|
|
486
|
-
const claimAuthCode = await claimSession(sessionId, token, webUrl);
|
|
487
|
-
if (claimAuthCode) {
|
|
488
|
-
authCode = claimAuthCode;
|
|
489
|
-
console.log(' ✓ Session automatically claimed to your account\n');
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
625
|
// Print banner for new session
|
|
494
|
-
printBanner(sessionId,
|
|
626
|
+
printBanner(sessionId, authCode);
|
|
627
|
+
if (enabledParsers.length > 0) {
|
|
628
|
+
console.log(` 📋 Log Types: ${enabledParsers.join(', ')}`);
|
|
629
|
+
} else {
|
|
630
|
+
console.log(' 📋 Log Types: Auto-detection (default parsers)');
|
|
631
|
+
}
|
|
495
632
|
console.log(' 💡 Tip: Use -s to send more logs to this session');
|
|
496
633
|
console.log(` Example: echo '{"cpu": 45, "memory": 78}' | npx vibex-sh -s ${sessionId}\n`);
|
|
497
634
|
} catch (error) {
|
|
@@ -639,6 +776,7 @@ async function main() {
|
|
|
639
776
|
if (!receivedAuthCode || receivedAuthCode !== message.data.authCode) {
|
|
640
777
|
receivedAuthCode = message.data.authCode;
|
|
641
778
|
if (isNewSession) {
|
|
779
|
+
const { webUrl } = getProductionUrls();
|
|
642
780
|
console.log(` 🔑 Auth Code: ${receivedAuthCode}`);
|
|
643
781
|
console.log(` 📋 Dashboard: ${webUrl}/${sessionId}?auth=${receivedAuthCode}\n`);
|
|
644
782
|
}
|
|
@@ -798,21 +936,19 @@ async function main() {
|
|
|
798
936
|
|
|
799
937
|
// Send logs via HTTP POST (non-blocking, same as SDKs)
|
|
800
938
|
// Always use production Cloudflare Worker endpoint
|
|
801
|
-
// Token is
|
|
939
|
+
// Token is REQUIRED - all sessions must be authenticated
|
|
802
940
|
// HTTP POST works independently of WebSocket - don't wait for WebSocket connection
|
|
803
941
|
const sendLogViaHTTP = async (logData) => {
|
|
804
942
|
try {
|
|
805
943
|
// Always use production worker URL
|
|
806
|
-
const workerUrl = process.env.VIBEX_WORKER_URL ||
|
|
944
|
+
const workerUrl = process.env.VIBEX_WORKER_URL || DEFAULT_WORKER_URL;
|
|
807
945
|
const ingestUrl = `${workerUrl}/api/v1/ingest`;
|
|
808
946
|
|
|
809
|
-
// Build headers -
|
|
947
|
+
// Build headers - Authorization is REQUIRED
|
|
810
948
|
const headers = {
|
|
811
949
|
'Content-Type': 'application/json',
|
|
950
|
+
'Authorization': `Bearer ${token}`,
|
|
812
951
|
};
|
|
813
|
-
if (token) {
|
|
814
|
-
headers['Authorization'] = `Bearer ${token}`;
|
|
815
|
-
}
|
|
816
952
|
|
|
817
953
|
const response = await fetch(ingestUrl, {
|
|
818
954
|
method: 'POST',
|
|
@@ -913,8 +1049,67 @@ async function main() {
|
|
|
913
1049
|
}
|
|
914
1050
|
};
|
|
915
1051
|
|
|
916
|
-
//
|
|
917
|
-
|
|
1052
|
+
// Only start WebSocket connection if we have stdin (piped input)
|
|
1053
|
+
// Don't connect WebSocket when run without parameters
|
|
1054
|
+
if (hasStdin) {
|
|
1055
|
+
connectWebSocket();
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Only read from stdin if we have piped input
|
|
1059
|
+
if (!hasStdin) {
|
|
1060
|
+
// No stdin - exit after showing session info
|
|
1061
|
+
process.exit(0);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Rate limiting queue for piped input
|
|
1065
|
+
const pipedLogQueue = [];
|
|
1066
|
+
let isProcessingQueue = false;
|
|
1067
|
+
let queueProcessingTimeout = null;
|
|
1068
|
+
|
|
1069
|
+
const processPipedLogQueue = async () => {
|
|
1070
|
+
if (isProcessingQueue) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (pipedLogQueue.length === 0) {
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
isProcessingQueue = true;
|
|
1079
|
+
|
|
1080
|
+
try {
|
|
1081
|
+
while (pipedLogQueue.length > 0) {
|
|
1082
|
+
const logData = pipedLogQueue.shift();
|
|
1083
|
+
await sendLogViaHTTP(logData);
|
|
1084
|
+
|
|
1085
|
+
// Wait before sending the next log (only if there are more logs in queue)
|
|
1086
|
+
if (pipedLogQueue.length > 0) {
|
|
1087
|
+
await new Promise(resolve => setTimeout(resolve, PIPED_INPUT_DELAY_MS));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
} finally {
|
|
1091
|
+
isProcessingQueue = false;
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
const queuePipedLog = (logData) => {
|
|
1096
|
+
pipedLogQueue.push(logData);
|
|
1097
|
+
|
|
1098
|
+
// Start processing if not already processing
|
|
1099
|
+
if (!isProcessingQueue) {
|
|
1100
|
+
// Clear any existing timeout
|
|
1101
|
+
if (queueProcessingTimeout) {
|
|
1102
|
+
clearTimeout(queueProcessingTimeout);
|
|
1103
|
+
queueProcessingTimeout = null;
|
|
1104
|
+
}
|
|
1105
|
+
// Process queue with a small initial delay to batch rapid inputs
|
|
1106
|
+
queueProcessingTimeout = setTimeout(() => {
|
|
1107
|
+
processPipedLogQueue().catch((error) => {
|
|
1108
|
+
console.error(' ✗ Error processing log queue:', error.message);
|
|
1109
|
+
});
|
|
1110
|
+
}, 10);
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
918
1113
|
|
|
919
1114
|
const rl = readline.createInterface({
|
|
920
1115
|
input: process.stdin,
|
|
@@ -966,13 +1161,29 @@ async function main() {
|
|
|
966
1161
|
};
|
|
967
1162
|
}
|
|
968
1163
|
|
|
969
|
-
//
|
|
970
|
-
//
|
|
971
|
-
|
|
1164
|
+
// Queue logs for rate-limited sending when input is piped
|
|
1165
|
+
// This prevents overwhelming rate limits when piping large files
|
|
1166
|
+
queuePipedLog(logData);
|
|
972
1167
|
});
|
|
973
1168
|
|
|
974
1169
|
rl.on('close', async () => {
|
|
975
|
-
//
|
|
1170
|
+
// Clear any pending queue processing timeout
|
|
1171
|
+
if (queueProcessingTimeout) {
|
|
1172
|
+
clearTimeout(queueProcessingTimeout);
|
|
1173
|
+
queueProcessingTimeout = null;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Process any remaining logs in the piped queue
|
|
1177
|
+
// Keep processing until queue is empty and not currently processing
|
|
1178
|
+
while (pipedLogQueue.length > 0 || isProcessingQueue) {
|
|
1179
|
+
await processPipedLogQueue();
|
|
1180
|
+
// Small delay to allow processing to complete
|
|
1181
|
+
if (pipedLogQueue.length > 0 || isProcessingQueue) {
|
|
1182
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Wait for queued logs to be sent (WebSocket queue)
|
|
976
1187
|
const waitForQueue = () => {
|
|
977
1188
|
return new Promise((resolve) => {
|
|
978
1189
|
if (logQueue.length === 0) {
|
|
@@ -993,8 +1204,10 @@ async function main() {
|
|
|
993
1204
|
reconnectTimeout = null;
|
|
994
1205
|
}
|
|
995
1206
|
|
|
996
|
-
// Graceful shutdown - wait for close handshake
|
|
997
|
-
|
|
1207
|
+
// Graceful shutdown - wait for close handshake (only if WebSocket was connected)
|
|
1208
|
+
if (hasStdin && socket) {
|
|
1209
|
+
await closeWebSocket();
|
|
1210
|
+
}
|
|
998
1211
|
|
|
999
1212
|
// Give a moment for any final cleanup
|
|
1000
1213
|
setTimeout(() => process.exit(0), 100);
|