vibex-sh 0.9.8 → 0.10.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 (2) hide show
  1. package/index.js +144 -10
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -56,6 +56,103 @@ function normalizeSessionId(sessionId) {
56
56
  return sessionId;
57
57
  }
58
58
 
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
+ }
152
+ }
153
+ return hybrid;
154
+ }
155
+
59
156
  function deriveSocketUrl(webUrl) {
60
157
  const url = new URL(webUrl);
61
158
 
@@ -66,7 +163,7 @@ function deriveSocketUrl(webUrl) {
66
163
  // For vibex.sh domains, use Workers WebSocket endpoint
67
164
  else if (url.hostname.includes('vibex.sh')) {
68
165
  // Use Cloudflare Workers WebSocket endpoint
69
- const workerUrl = process.env.VIBEX_WORKER_URL || 'https://vibex-ingest.prop.workers.dev';
166
+ const workerUrl = process.env.VIBEX_WORKER_URL || 'https://ingest.vibex.sh';
70
167
  return workerUrl.replace('https://', 'wss://').replace('http://', 'ws://');
71
168
  }
72
169
  // For other domains, derive from web URL
@@ -113,7 +210,7 @@ function getUrls(options) {
113
210
 
114
211
  // Priority 5: Production defaults
115
212
  // Use Worker WebSocket endpoint instead of old Socket.io server
116
- const defaultWorkerUrl = process.env.VIBEX_WORKER_URL || 'https://vibex-ingest.prop.workers.dev';
213
+ const defaultWorkerUrl = process.env.VIBEX_WORKER_URL || 'https://ingest.vibex.sh';
117
214
  return {
118
215
  webUrl: 'https://vibex.sh',
119
216
  socketUrl: socket || defaultWorkerUrl.replace('https://', 'wss://').replace('http://', 'ws://'),
@@ -505,12 +602,27 @@ async function main() {
505
602
  return;
506
603
  }
507
604
 
605
+ // Clear any existing reconnect timeout to prevent duplicate connections
606
+ if (reconnectTimeout) {
607
+ clearTimeout(reconnectTimeout);
608
+ reconnectTimeout = null;
609
+ }
610
+
508
611
  connectionLock = true;
509
612
  connectionState = 'connecting';
510
613
  connectionStartTime = Date.now();
511
614
  console.log(' 🔄 Connecting to WebSocket...');
512
615
 
513
616
  try {
617
+ // Close existing socket if any (cleanup)
618
+ if (socket && socket.readyState !== WebSocket.CLOSED) {
619
+ try {
620
+ socket.close();
621
+ } catch (e) {
622
+ // Ignore errors when closing
623
+ }
624
+ }
625
+
514
626
  socket = new WebSocket(`${socketUrl}?sessionId=${sessionId}`);
515
627
 
516
628
  socket.onopen = () => {
@@ -726,7 +838,7 @@ async function main() {
726
838
  ingestUrl = `${process.env.VIBEX_WORKER_URL}/api/v1/ingest`;
727
839
  } else {
728
840
  // Production default - use Worker URL (not web URL)
729
- const defaultWorkerUrl = 'https://vibex-ingest.prop.workers.dev';
841
+ const defaultWorkerUrl = 'https://ingest.vibex.sh';
730
842
  ingestUrl = `${defaultWorkerUrl}/api/v1/ingest`;
731
843
  }
732
844
 
@@ -855,15 +967,37 @@ async function main() {
855
967
  let logData;
856
968
  try {
857
969
  const parsed = JSON.parse(trimmedLine);
858
- logData = {
859
- type: 'json',
860
- payload: parsed,
861
- timestamp: Date.now(),
862
- };
970
+ // Phase 1.8: Normalize JSON to hybrid structure
971
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
972
+ const hybrid = normalizeToHybrid(null, null, parsed);
973
+ logData = {
974
+ type: 'json',
975
+ payload: hybrid,
976
+ timestamp: Date.now(),
977
+ };
978
+ } else {
979
+ // Parsed but not an object - treat as text
980
+ logData = {
981
+ type: 'json',
982
+ payload: {
983
+ message: trimmedLine,
984
+ level: 'debug',
985
+ metrics: {},
986
+ context: {},
987
+ },
988
+ timestamp: Date.now(),
989
+ };
990
+ }
863
991
  } catch (e) {
992
+ // Phase 1.8: Text logs are now valuable - send as message field
864
993
  logData = {
865
- type: 'text',
866
- payload: trimmedLine,
994
+ type: 'json',
995
+ payload: {
996
+ message: trimmedLine,
997
+ level: 'debug',
998
+ metrics: {},
999
+ context: {},
1000
+ },
867
1001
  timestamp: Date.now(),
868
1002
  };
869
1003
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibex-sh",
3
- "version": "0.9.8",
3
+ "version": "0.10.0",
4
4
  "description": "Zero-config observability CLI - pipe logs and visualize instantly",
5
5
  "type": "module",
6
6
  "bin": {