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.
- package/README.md +10 -29
- package/index.js +268 -182
- 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
|
-
|
|
|
47
|
-
| `--
|
|
48
|
-
| `--
|
|
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
|
-
#
|
|
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
|
|
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`, `--
|
|
58
|
+
1. **Flags** (`--web`, `--socket`, `--server`)
|
|
72
59
|
2. **Environment variables** (`VIBEX_WEB_URL`, `VIBEX_SOCKET_URL`)
|
|
73
|
-
3. **Production defaults** (`https://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=
|
|
79
|
-
export VIBEX_SOCKET_URL=
|
|
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
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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:
|
|
216
|
-
socketUrl:
|
|
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
|
|
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(
|
|
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
|
-
|
|
289
|
-
const
|
|
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 <
|
|
313
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
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?
|
|
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
|
|
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
|
-
|
|
368
|
-
|
|
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,
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
//
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
process.
|
|
426
|
-
}
|
|
356
|
+
// Interactive prompt for parser selection
|
|
357
|
+
const rl = readline.createInterface({
|
|
358
|
+
input: process.stdin,
|
|
359
|
+
output: process.stdout,
|
|
360
|
+
});
|
|
427
361
|
|
|
428
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
//
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
470
|
-
|
|
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
|
|
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
|
|
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
|
|
573
|
+
const createUrl = `${webUrl}/api/sessions/create`;
|
|
493
574
|
const response = await httpRequest(createUrl, {
|
|
494
575
|
method: 'POST',
|
|
495
|
-
headers: {
|
|
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
|
-
|
|
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,
|
|
519
|
-
|
|
520
|
-
|
|
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 ${
|
|
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
|
-
//
|
|
827
|
-
// Token is
|
|
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
|
-
//
|
|
832
|
-
|
|
833
|
-
|
|
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 -
|
|
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
|
-
//
|
|
953
|
-
|
|
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
|
-
|
|
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);
|