vibex-sh 0.9.9 → 0.10.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.
Files changed (3) hide show
  1. package/README.md +10 -29
  2. package/index.js +155 -57
  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
@@ -56,28 +56,111 @@ function normalizeSessionId(sessionId) {
56
56
  return sessionId;
57
57
  }
58
58
 
59
- function deriveSocketUrl(webUrl) {
60
- const url = new URL(webUrl);
61
-
62
- // For localhost, use Workers dev server
63
- if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
64
- return 'ws://localhost:8787';
65
- }
66
- // For vibex.sh domains, use Workers WebSocket endpoint
67
- else if (url.hostname.includes('vibex.sh')) {
68
- // Use Cloudflare Workers WebSocket endpoint
69
- const workerUrl = process.env.VIBEX_WORKER_URL || 'https://ingest.vibex.sh';
70
- return workerUrl.replace('https://', 'wss://').replace('http://', 'ws://');
71
- }
72
- // For other domains, derive from web URL
73
- else {
74
- const workerUrl = process.env.VIBEX_WORKER_URL || webUrl.replace(url.hostname, `ingest.${url.hostname}`);
75
- return workerUrl.replace('https://', 'wss://').replace('http://', 'ws://');
59
+ /**
60
+ * Phase 1.8: Log Normalization for CLI
61
+ * Intelligently transforms log data into hybrid JSON structure
62
+ */
63
+
64
+ function normalizeLevel(level) {
65
+ if (!level) return 'debug';
66
+ const levelStr = String(level).toLowerCase();
67
+ if (['debug', 'dbg', 'trace'].includes(levelStr)) return 'debug';
68
+ if (['info', 'information', 'log'].includes(levelStr)) return 'info';
69
+ if (['warn', 'warning', 'wrn'].includes(levelStr)) return 'warn';
70
+ if (['error', 'err', 'exception', 'fatal', 'critical'].includes(levelStr)) return 'error';
71
+ return 'debug';
72
+ }
73
+
74
+ function extractMetrics(payload) {
75
+ const metrics = {};
76
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
77
+ return metrics;
78
+ }
79
+ if (payload.metrics && typeof payload.metrics === 'object' && !Array.isArray(payload.metrics)) {
80
+ for (const [key, value] of Object.entries(payload.metrics)) {
81
+ if (typeof value === 'number') {
82
+ metrics[key] = value;
83
+ }
84
+ }
85
+ return metrics;
86
+ }
87
+ const knownContextFields = new Set([
88
+ 'trace_id', 'traceId', 'user_id', 'userId', 'request_id', 'requestId',
89
+ 'correlation_id', 'correlationId', 'span_id', 'spanId', 'session_id', 'sessionId',
90
+ 'id', 'pid', 'port', 'year', 'timestamp', 'time', 'date', 'createdAt', 'updatedAt',
91
+ 'datetime', 'ts', 'utc', 'iso', 'exc_info', 'exception', 'error', 'message', 'msg', 'level', 'severity', 'log_level'
92
+ ]);
93
+ for (const [key, value] of Object.entries(payload)) {
94
+ const keyLower = key.toLowerCase();
95
+ if (knownContextFields.has(keyLower)) continue;
96
+ if (keyLower.includes('timestamp') || keyLower.includes('time') || keyLower.includes('date')) continue;
97
+ if (typeof value === 'number') {
98
+ if (key.endsWith('_ms') || key.endsWith('_count') || key.endsWith('_size') ||
99
+ key.endsWith('Ms') || key.endsWith('Count') || key.endsWith('Size') ||
100
+ ['cpu', 'memory', 'latency', 'response_time', 'duration'].some(pattern => keyLower.includes(pattern))) {
101
+ metrics[key] = value;
102
+ }
103
+ }
104
+ }
105
+ return metrics;
106
+ }
107
+
108
+ function extractContext(payload) {
109
+ const context = {};
110
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
111
+ return context;
112
+ }
113
+ if (payload.context && typeof payload.context === 'object' && !Array.isArray(payload.context)) {
114
+ return payload.context;
115
+ }
116
+ const knownContextFields = {
117
+ 'trace_id': 'trace_id', 'traceId': 'trace_id',
118
+ 'user_id': 'user_id', 'userId': 'user_id',
119
+ 'request_id': 'request_id', 'requestId': 'request_id',
120
+ 'correlation_id': 'correlation_id', 'correlationId': 'correlation_id',
121
+ 'span_id': 'span_id', 'spanId': 'span_id',
122
+ 'session_id': 'session_id', 'sessionId': 'session_id',
123
+ };
124
+ for (const [field, normalizedKey] of Object.entries(knownContextFields)) {
125
+ if (payload[field] !== undefined) {
126
+ context[normalizedKey] = payload[field];
127
+ }
128
+ }
129
+ return context;
130
+ }
131
+
132
+ function normalizeToHybrid(message, level, payload) {
133
+ const merged = { ...(payload || {}) };
134
+ let normalizedMessage = message;
135
+ if (!normalizedMessage && merged.message) normalizedMessage = merged.message;
136
+ if (!normalizedMessage && merged.msg) normalizedMessage = merged.msg;
137
+ const normalizedLevel = normalizeLevel(level || merged.level || merged.severity || merged.log_level);
138
+ const metrics = extractMetrics(merged);
139
+ const context = extractContext(merged);
140
+ const annotation = merged._annotation;
141
+ const hybrid = {
142
+ message: normalizedMessage, // Can be null/undefined
143
+ level: normalizedLevel,
144
+ metrics: metrics,
145
+ context: context,
146
+ };
147
+ if (annotation) hybrid._annotation = annotation;
148
+ for (const [key, value] of Object.entries(merged)) {
149
+ if (!['message', 'msg', 'level', 'severity', 'log_level', 'metrics', 'context', '_annotation'].includes(key)) {
150
+ if (!(key in hybrid)) hybrid[key] = value;
151
+ }
76
152
  }
153
+ return hybrid;
154
+ }
155
+
156
+ function deriveSocketUrl(webUrl) {
157
+ // Always use production worker WebSocket endpoint
158
+ const workerUrl = process.env.VIBEX_WORKER_URL || 'https://ingest.vibex.sh';
159
+ return workerUrl.replace('https://', 'wss://').replace('http://', 'ws://');
77
160
  }
78
161
 
79
162
  function getUrls(options) {
80
- const { local, web, socket, server } = options;
163
+ const { web, socket, server } = options;
81
164
 
82
165
  // Priority 1: Explicit --web and --socket flags (highest priority)
83
166
  if (web) {
@@ -95,15 +178,7 @@ function getUrls(options) {
95
178
  };
96
179
  }
97
180
 
98
- // Priority 3: --local flag
99
- if (local) {
100
- return {
101
- webUrl: process.env.VIBEX_WEB_URL || 'http://localhost:3000',
102
- socketUrl: process.env.VIBEX_SOCKET_URL || socket || 'ws://localhost:8787',
103
- };
104
- }
105
-
106
- // Priority 4: Environment variables
181
+ // Priority 3: Environment variables
107
182
  if (process.env.VIBEX_WEB_URL) {
108
183
  return {
109
184
  webUrl: process.env.VIBEX_WEB_URL,
@@ -111,8 +186,8 @@ function getUrls(options) {
111
186
  };
112
187
  }
113
188
 
114
- // Priority 5: Production defaults
115
- // Use Worker WebSocket endpoint instead of old Socket.io server
189
+ // Priority 4: Production defaults
190
+ // Always use production worker WebSocket endpoint
116
191
  const defaultWorkerUrl = process.env.VIBEX_WORKER_URL || 'https://ingest.vibex.sh';
117
192
  return {
118
193
  webUrl: 'https://vibex.sh',
@@ -342,7 +417,6 @@ async function main() {
342
417
  // Create a separate command instance for login
343
418
  const loginCmd = new Command();
344
419
  loginCmd
345
- .option('-l, --local', 'Use localhost')
346
420
  .option('--web <url>', 'Web server URL')
347
421
  .option('--server <url>', 'Shorthand for --web');
348
422
 
@@ -362,9 +436,8 @@ async function main() {
362
436
  program
363
437
  .version(cliVersion, '-v, --version', 'Display version number')
364
438
  .option('-s, --session-id <id>', 'Reuse existing session ID')
365
- .option('-l, --local', 'Use localhost (web: 3000, socket: 3001)')
366
- .option('--web <url>', 'Web server URL (e.g., http://localhost:3000)')
367
- .option('--socket <url>', 'Socket server URL (e.g., http://localhost:3001)')
439
+ .option('--web <url>', 'Web server URL')
440
+ .option('--socket <url>', 'Socket server URL')
368
441
  .option('--server <url>', 'Shorthand for --web (auto-derives socket URL)')
369
442
  .option('--token <token>', 'Authentication token (or use VIBEX_TOKEN env var)')
370
443
  .parse();
@@ -419,10 +492,8 @@ async function main() {
419
492
 
420
493
  // Print banner for new session
421
494
  printBanner(sessionId, webUrl, authCode);
422
- const localFlag = webUrl.includes('localhost') ? ' --local' : '';
423
- const sessionSlug = sessionId.replace(/^vibex-/, ''); // Remove prefix for example
424
495
  console.log(' 💡 Tip: Use -s to send more logs to this session');
425
- console.log(` Example: echo '{"cpu": 45, "memory": 78}' | npx vibex-sh -s ${sessionSlug}${localFlag}\n`);
496
+ console.log(` Example: echo '{"cpu": 45, "memory": 78}' | npx vibex-sh -s ${sessionId}\n`);
426
497
  } catch (error) {
427
498
  console.error(` ✗ Error creating session: ${error.message}`);
428
499
  process.exit(1);
@@ -505,12 +576,27 @@ async function main() {
505
576
  return;
506
577
  }
507
578
 
579
+ // Clear any existing reconnect timeout to prevent duplicate connections
580
+ if (reconnectTimeout) {
581
+ clearTimeout(reconnectTimeout);
582
+ reconnectTimeout = null;
583
+ }
584
+
508
585
  connectionLock = true;
509
586
  connectionState = 'connecting';
510
587
  connectionStartTime = Date.now();
511
588
  console.log(' 🔄 Connecting to WebSocket...');
512
589
 
513
590
  try {
591
+ // Close existing socket if any (cleanup)
592
+ if (socket && socket.readyState !== WebSocket.CLOSED) {
593
+ try {
594
+ socket.close();
595
+ } catch (e) {
596
+ // Ignore errors when closing
597
+ }
598
+ }
599
+
514
600
  socket = new WebSocket(`${socketUrl}?sessionId=${sessionId}`);
515
601
 
516
602
  socket.onopen = () => {
@@ -711,24 +797,14 @@ async function main() {
711
797
  };
712
798
 
713
799
  // Send logs via HTTP POST (non-blocking, same as SDKs)
714
- // Use Cloudflare Worker endpoint (port 8787 for local, or Worker URL for production)
800
+ // Always use production Cloudflare Worker endpoint
715
801
  // Token is optional - anonymous sessions can send logs without authentication
716
802
  // HTTP POST works independently of WebSocket - don't wait for WebSocket connection
717
803
  const sendLogViaHTTP = async (logData) => {
718
804
  try {
719
- // Determine ingest URL
720
- let ingestUrl;
721
- if (webUrl.includes('localhost') || webUrl.includes('127.0.0.1')) {
722
- // Local development - use Workers dev server
723
- ingestUrl = 'http://localhost:8787/api/v1/ingest';
724
- } else if (process.env.VIBEX_WORKER_URL) {
725
- // Use explicit Worker URL if set
726
- ingestUrl = `${process.env.VIBEX_WORKER_URL}/api/v1/ingest`;
727
- } else {
728
- // Production default - use Worker URL (not web URL)
729
- const defaultWorkerUrl = 'https://ingest.vibex.sh';
730
- ingestUrl = `${defaultWorkerUrl}/api/v1/ingest`;
731
- }
805
+ // Always use production worker URL
806
+ const workerUrl = process.env.VIBEX_WORKER_URL || 'https://ingest.vibex.sh';
807
+ const ingestUrl = `${workerUrl}/api/v1/ingest`;
732
808
 
733
809
  // Build headers - only include Authorization if token exists
734
810
  const headers = {
@@ -855,15 +931,37 @@ async function main() {
855
931
  let logData;
856
932
  try {
857
933
  const parsed = JSON.parse(trimmedLine);
858
- logData = {
859
- type: 'json',
860
- payload: parsed,
861
- timestamp: Date.now(),
862
- };
934
+ // Phase 1.8: Normalize JSON to hybrid structure
935
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
936
+ const hybrid = normalizeToHybrid(null, null, parsed);
937
+ logData = {
938
+ type: 'json',
939
+ payload: hybrid,
940
+ timestamp: Date.now(),
941
+ };
942
+ } else {
943
+ // Parsed but not an object - treat as text
944
+ logData = {
945
+ type: 'json',
946
+ payload: {
947
+ message: trimmedLine,
948
+ level: 'debug',
949
+ metrics: {},
950
+ context: {},
951
+ },
952
+ timestamp: Date.now(),
953
+ };
954
+ }
863
955
  } catch (e) {
956
+ // Phase 1.8: Text logs are now valuable - send as message field
864
957
  logData = {
865
- type: 'text',
866
- payload: trimmedLine,
958
+ type: 'json',
959
+ payload: {
960
+ message: trimmedLine,
961
+ level: 'debug',
962
+ metrics: {},
963
+ context: {},
964
+ },
867
965
  timestamp: Date.now(),
868
966
  };
869
967
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibex-sh",
3
- "version": "0.9.9",
3
+ "version": "0.10.1",
4
4
  "description": "Zero-config observability CLI - pipe logs and visualize instantly",
5
5
  "type": "module",
6
6
  "bin": {