securenow 5.3.0 → 5.3.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/cli/apps.js CHANGED
@@ -9,7 +9,8 @@ async function list(args, flags) {
9
9
  const s = ui.spinner('Fetching applications');
10
10
 
11
11
  try {
12
- const apps = await api.get('/applications');
12
+ const data = await api.get('/applications');
13
+ const apps = data.applications || [];
13
14
  s.stop(`Found ${apps.length} application${apps.length !== 1 ? 's' : ''}`);
14
15
  console.log('');
15
16
 
@@ -101,7 +102,8 @@ async function info(args, flags) {
101
102
 
102
103
  const s = ui.spinner('Fetching application details');
103
104
  try {
104
- const app = await api.get(`/applications/${id}`);
105
+ const data = await api.get(`/applications/${id}`);
106
+ const app = data.application || data;
105
107
  s.stop('Application details loaded');
106
108
 
107
109
  if (flags.json) {
package/cli/client.js CHANGED
@@ -60,7 +60,9 @@ function request(method, endpoint, { body, query, token, raw } = {}) {
60
60
  }
61
61
  if (res.statusCode >= 400) {
62
62
  const msg = parsed?.error || parsed?.message || `Request failed (HTTP ${res.statusCode})`;
63
- reject(new CLIError(msg, res.statusCode));
63
+ const details = parsed?.details || parsed?.unauthorizedKeys;
64
+ const err = new CLIError(details ? `${msg} — ${details}` : msg, res.statusCode);
65
+ reject(err);
64
66
  return;
65
67
  }
66
68
 
package/cli/monitor.js CHANGED
@@ -21,14 +21,14 @@ async function tracesList(args, flags) {
21
21
  const s = ui.spinner('Fetching traces');
22
22
  try {
23
23
  const query = {
24
- serviceName: appKey,
24
+ appKeys: appKey,
25
25
  limit: flags.limit || 20,
26
26
  };
27
- if (flags.start) query.start = flags.start;
28
- if (flags.end) query.end = flags.end;
27
+ if (flags.start) query.from = flags.start;
28
+ if (flags.end) query.to = flags.end;
29
29
 
30
- const data = await api.get('/traces/recent', { query });
31
- const traces = Array.isArray(data) ? data : data.traces || [];
30
+ const data = await api.get('/traces', { query });
31
+ const traces = data.traces || [];
32
32
  s.stop(`Found ${traces.length} trace${traces.length !== 1 ? 's' : ''}`);
33
33
 
34
34
  if (flags.json) { ui.json(traces); return; }
@@ -37,7 +37,7 @@ async function tracesList(args, flags) {
37
37
  const rows = traces.map(t => [
38
38
  ui.c.dim(ui.truncate(t.traceID || t.traceId || t._id, 16)),
39
39
  t.operationName || t.name || t.serviceName || '—',
40
- ui.httpStatusColor(t.statusCode || t.httpStatusCode || '—'),
40
+ ui.httpStatusColor(t.statusCode || t.httpStatusCode || t.responseStatusCode || '—'),
41
41
  ui.durationColor(t.durationNano ? t.durationNano / 1e6 : t.duration),
42
42
  t.httpMethod || t.method || '—',
43
43
  ui.truncate(t.httpUrl || t.url || t.httpRoute || '', 40),
@@ -62,37 +62,31 @@ async function tracesShow(args, flags) {
62
62
 
63
63
  const s = ui.spinner('Fetching trace details');
64
64
  try {
65
- const data = await api.get(`/traces/${traceId}`);
65
+ const appKey = resolveApp(flags);
66
+ const traceQuery = appKey ? { appKeys: appKey } : {};
67
+ const data = await api.get(`/traces/${traceId}`, { query: traceQuery });
66
68
  s.stop('Trace loaded');
67
69
 
68
70
  if (flags.json) { ui.json(data); return; }
69
71
 
70
- const trace = data.trace || data;
71
- console.log('');
72
- ui.heading(`Trace ${traceId}`);
72
+ const spans = data.spans || [];
73
73
  console.log('');
74
+ ui.heading(`Trace ${data.traceId || traceId}`);
74
75
 
75
- if (trace.spans && trace.spans.length) {
76
- ui.subheading(`Spans (${trace.spans.length})`);
76
+ if (spans.length) {
77
+ ui.subheading(`Spans (${spans.length})`);
77
78
  console.log('');
78
- const rows = trace.spans.map(span => [
79
+ const rows = spans.map(span => [
79
80
  ui.c.dim(ui.truncate(span.spanID || span.spanId, 16)),
80
81
  span.operationName || span.name || '—',
81
- ui.httpStatusColor(span.statusCode || '—'),
82
+ ui.httpStatusColor(span.statusCode || span.responseStatusCode || '—'),
82
83
  ui.durationColor(span.durationNano ? span.durationNano / 1e6 : span.duration),
83
84
  span.kind || '—',
84
85
  ]);
85
86
  ui.table(['Span ID', 'Operation', 'Status', 'Duration', 'Kind'], rows);
86
- }
87
-
88
- if (trace.rootSpan || trace.serviceName) {
87
+ } else {
89
88
  console.log('');
90
- ui.keyValue([
91
- ['Service', trace.serviceName || '—'],
92
- ['Root Operation', trace.rootOperationName || trace.rootSpan?.operationName || '—'],
93
- ['Duration', trace.durationMs ? `${trace.durationMs}ms` : '—'],
94
- ['Timestamp', trace.startTime ? new Date(trace.startTime).toLocaleString() : '—'],
95
- ]);
89
+ ui.info('No spans found for this trace.');
96
90
  }
97
91
  console.log('');
98
92
  } catch (err) {
@@ -116,11 +110,39 @@ async function tracesAnalyze(args, flags) {
116
110
 
117
111
  if (flags.json) { ui.json(result); return; }
118
112
 
113
+ const analysis = result.analysis;
119
114
  console.log('');
120
115
  ui.heading('AI Trace Analysis');
121
116
  console.log('');
122
- console.log(result.analysis || result.message || JSON.stringify(result, null, 2));
123
- console.log('');
117
+
118
+ if (typeof analysis === 'object' && analysis !== null) {
119
+ if (analysis.summary) {
120
+ ui.subheading('Summary');
121
+ console.log(`\n ${analysis.summary}\n`);
122
+ }
123
+ if (analysis.riskLevel) {
124
+ console.log(` ${ui.c.bold('Risk Level:')} ${ui.statusBadge(analysis.riskLevel)}\n`);
125
+ }
126
+ if (analysis.securityIssues?.length) {
127
+ ui.subheading('Security Issues');
128
+ console.log('');
129
+ analysis.securityIssues.forEach((issue, i) => {
130
+ console.log(` ${i + 1}. ${typeof issue === 'string' ? issue : issue.description || JSON.stringify(issue)}`);
131
+ });
132
+ console.log('');
133
+ }
134
+ if (analysis.recommendations?.length) {
135
+ ui.subheading('Recommendations');
136
+ console.log('');
137
+ analysis.recommendations.forEach((rec, i) => {
138
+ console.log(` ${i + 1}. ${typeof rec === 'string' ? rec : rec.description || JSON.stringify(rec)}`);
139
+ });
140
+ console.log('');
141
+ }
142
+ } else {
143
+ console.log(analysis || JSON.stringify(result, null, 2));
144
+ console.log('');
145
+ }
124
146
  } catch (err) {
125
147
  s.fail('Analysis failed');
126
148
  throw err;
@@ -142,15 +164,15 @@ async function logsList(args, flags) {
142
164
  const minutes = parseInt(flags.minutes || '60', 10);
143
165
  const now = Date.now();
144
166
  const query = {
145
- serviceName: appKey,
167
+ appKeys: appKey,
146
168
  limit: flags.limit || 50,
147
- start: flags.start || new Date(now - minutes * 60 * 1000).toISOString(),
148
- end: flags.end || new Date(now).toISOString(),
169
+ from: flags.start || new Date(now - minutes * 60 * 1000).toISOString(),
170
+ to: flags.end || new Date(now).toISOString(),
149
171
  };
150
- if (flags.level) query.level = flags.level;
172
+ if (flags.level) query.severity = flags.level;
151
173
 
152
174
  const data = await api.get('/logs', { query });
153
- const logs = Array.isArray(data) ? data : data.logs || [];
175
+ const logs = data.logs || [];
154
176
  s.stop(`Found ${logs.length} log${logs.length !== 1 ? 's' : ''}`);
155
177
 
156
178
  if (flags.json) { ui.json(logs); return; }
@@ -190,7 +212,7 @@ async function logsTrace(args, flags) {
190
212
  const s = ui.spinner('Fetching logs for trace');
191
213
  try {
192
214
  const data = await api.get(`/logs/trace/${traceId}`);
193
- const logs = Array.isArray(data) ? data : data.logs || [];
215
+ const logs = data.logs || [];
194
216
  s.stop(`Found ${logs.length} log${logs.length !== 1 ? 's' : ''}`);
195
217
 
196
218
  if (flags.json) { ui.json(logs); return; }
@@ -225,10 +247,10 @@ async function issuesList(args, flags) {
225
247
  if (flags.status) query.status = flags.status;
226
248
 
227
249
  const data = await api.get('/issues', { query });
228
- const issues = Array.isArray(data) ? data : data.issues || [];
250
+ const issues = data.issues || [];
229
251
  s.stop(`Found ${issues.length} issue${issues.length !== 1 ? 's' : ''}`);
230
252
 
231
- if (flags.json) { ui.json(issues); return; }
253
+ if (flags.json) { ui.json(data); return; }
232
254
 
233
255
  console.log('');
234
256
  const rows = issues.map(i => [
@@ -242,6 +264,9 @@ async function issuesList(args, flags) {
242
264
  ]);
243
265
 
244
266
  ui.table(['ID', 'Severity', 'Status', 'Title', 'App', 'Count', 'Last Seen'], rows);
267
+ if (data.total != null) {
268
+ console.log(ui.c.dim(` Total: ${data.total}`));
269
+ }
245
270
  console.log('');
246
271
  } catch (err) {
247
272
  s.fail('Failed to fetch issues');
@@ -259,7 +284,8 @@ async function issuesShow(args, flags) {
259
284
 
260
285
  const s = ui.spinner('Fetching issue');
261
286
  try {
262
- const issue = await api.get(`/issues/${id}`);
287
+ const data = await api.get(`/issues/${id}`);
288
+ const issue = data.issue || data;
263
289
  s.stop('Issue loaded');
264
290
 
265
291
  if (flags.json) { ui.json(issue); return; }
@@ -320,8 +346,9 @@ async function notificationsList(args, flags) {
320
346
  try {
321
347
  const query = { limit: flags.limit || 20, page: flags.page || 1 };
322
348
  const data = await api.get('/notifications', { query });
323
- const notifications = Array.isArray(data) ? data : data.notifications || data.data || [];
324
- s.stop(`Found ${notifications.length} notification${notifications.length !== 1 ? 's' : ''}`);
349
+ const notifications = data.notifications || [];
350
+ const pagination = data.pagination;
351
+ s.stop(`Found ${notifications.length} notification${notifications.length !== 1 ? 's' : ''}${pagination ? ` (page ${pagination.page}/${pagination.totalPages})` : ''}`);
325
352
 
326
353
  if (flags.json) { ui.json(data); return; }
327
354
 
@@ -353,7 +380,7 @@ async function notificationsRead(args, flags) {
353
380
 
354
381
  const s = ui.spinner('Marking as read');
355
382
  try {
356
- await api.put(`/notifications/${id}`, { read: true });
383
+ await api.put(`/notifications/${id}/read`);
357
384
  s.stop('Notification marked as read');
358
385
  } catch (err) {
359
386
  s.fail('Failed to mark notification');
@@ -377,7 +404,7 @@ async function notificationsUnread() {
377
404
  requireAuth();
378
405
  try {
379
406
  const data = await api.get('/notifications/unread-count');
380
- const count = data.count ?? data.unreadCount ?? data;
407
+ const count = data.count ?? 0;
381
408
  console.log(`\n ${ui.c.bold(String(count))} unread notification${count !== 1 ? 's' : ''}\n`);
382
409
  } catch (err) {
383
410
  throw err;
@@ -390,11 +417,12 @@ async function status(args, flags) {
390
417
  requireAuth();
391
418
  const s = ui.spinner('Fetching dashboard overview');
392
419
  try {
393
- const [apps, unread] = await Promise.all([
420
+ const [appsData, unreadData] = await Promise.all([
394
421
  api.get('/applications'),
395
422
  api.get('/notifications/unread-count').catch(() => ({ count: 0 })),
396
423
  ]);
397
424
 
425
+ const apps = appsData.applications || [];
398
426
  s.stop('Dashboard loaded');
399
427
 
400
428
  console.log('');
@@ -403,7 +431,7 @@ async function status(args, flags) {
403
431
 
404
432
  ui.keyValue([
405
433
  ['Applications', String(apps.length)],
406
- ['Unread Alerts', String(unread.count ?? unread.unreadCount ?? 0)],
434
+ ['Unread Alerts', String(unreadData.count ?? 0)],
407
435
  ]);
408
436
 
409
437
  if (apps.length > 0) {
@@ -420,16 +448,18 @@ async function status(args, flags) {
420
448
  const appKey = resolveApp(flags);
421
449
  if (appKey) {
422
450
  try {
423
- const protectionData = await api.get('/applications/protection-status', { query: { serviceName: appKey } });
424
- if (protectionData) {
425
- ui.subheading(`Protection Status (${appKey})`);
451
+ const protectionData = await api.get('/applications/protection-status');
452
+ const statuses = protectionData.statuses;
453
+ if (statuses && Object.keys(statuses).length > 0) {
454
+ ui.subheading('Protection Status');
426
455
  console.log('');
427
- const status = protectionData.status || protectionData;
428
- if (typeof status === 'object') {
429
- ui.keyValue(Object.entries(status).map(([k, v]) => [k, String(v)]));
430
- } else {
431
- console.log(` ${status}`);
432
- }
456
+ const rows = Object.entries(statuses).map(([id, s]) => [
457
+ ui.c.dim(ui.truncate(id, 12)),
458
+ s.protected ? ui.c.green('● protected') : ui.c.red('○ unprotected'),
459
+ String(s.traceCount || 0),
460
+ s.lastTrace ? ui.timeAgo(s.lastTrace) : ui.c.dim('—'),
461
+ ]);
462
+ ui.table(['App ID', 'Status', 'Traces (15m)', 'Last Trace'], rows);
433
463
  }
434
464
  } catch {}
435
465
  }
package/cli/security.js CHANGED
@@ -15,7 +15,7 @@ async function alertRulesList(args, flags) {
15
15
  const s = ui.spinner('Fetching alert rules');
16
16
  try {
17
17
  const data = await api.get('/alert-rules');
18
- const rules = Array.isArray(data) ? data : data.rules || [];
18
+ const rules = data.alertRules || [];
19
19
  s.stop(`Found ${rules.length} rule${rules.length !== 1 ? 's' : ''}`);
20
20
 
21
21
  if (flags.json) { ui.json(rules); return; }
@@ -44,7 +44,7 @@ async function alertChannelsList(args, flags) {
44
44
  const s = ui.spinner('Fetching alert channels');
45
45
  try {
46
46
  const data = await api.get('/alert-channels');
47
- const channels = Array.isArray(data) ? data : data.channels || [];
47
+ const channels = data.alertChannels || [];
48
48
  s.stop(`Found ${channels.length} channel${channels.length !== 1 ? 's' : ''}`);
49
49
 
50
50
  if (flags.json) { ui.json(channels); return; }
@@ -73,10 +73,10 @@ async function alertHistoryList(args, flags) {
73
73
  try {
74
74
  const query = { limit: flags.limit || 20 };
75
75
  const data = await api.get('/alert-history', { query });
76
- const history = Array.isArray(data) ? data : data.alerts || data.history || [];
77
- s.stop(`Found ${history.length} alert${history.length !== 1 ? 's' : ''}`);
76
+ const history = data.alerts || [];
77
+ s.stop(`Found ${history.length} alert${history.length !== 1 ? 's' : ''}${data.totalItems ? ` (${data.totalItems} total)` : ''}`);
78
78
 
79
- if (flags.json) { ui.json(history); return; }
79
+ if (flags.json) { ui.json(data); return; }
80
80
 
81
81
  console.log('');
82
82
  const rows = history.map(h => [
@@ -102,10 +102,10 @@ async function blocklistList(args, flags) {
102
102
  const s = ui.spinner('Fetching blocklist');
103
103
  try {
104
104
  const data = await api.get('/blocklist');
105
- const items = Array.isArray(data) ? data : data.blocklist || data.items || [];
106
- s.stop(`Found ${items.length} blocked IP${items.length !== 1 ? 's' : ''}`);
105
+ const items = data.blockedIps || [];
106
+ s.stop(`Found ${items.length} blocked IP${items.length !== 1 ? 's' : ''}${data.total ? ` (${data.total} total)` : ''}`);
107
107
 
108
- if (flags.json) { ui.json(items); return; }
108
+ if (flags.json) { ui.json(data); return; }
109
109
 
110
110
  console.log('');
111
111
  const rows = items.map(b => [
@@ -174,7 +174,8 @@ async function blocklistStats(args, flags) {
174
174
  requireAuth();
175
175
  const s = ui.spinner('Fetching blocklist stats');
176
176
  try {
177
- const stats = await api.get('/blocklist/stats');
177
+ const data = await api.get('/blocklist/stats');
178
+ const stats = data.stats || data;
178
179
  s.stop('Stats loaded');
179
180
 
180
181
  if (flags.json) { ui.json(stats); return; }
@@ -182,8 +183,13 @@ async function blocklistStats(args, flags) {
182
183
  console.log('');
183
184
  ui.heading('Blocklist Statistics');
184
185
  console.log('');
185
- const pairs = Object.entries(stats).map(([k, v]) => [k, String(v)]);
186
- ui.keyValue(pairs);
186
+ ui.keyValue([
187
+ ['Total Active', String(stats.totalActive ?? '—')],
188
+ ['Total Removed', String(stats.totalRemoved ?? '—')],
189
+ ['Manual Blocks', String(stats.manualCount ?? '—')],
190
+ ['Automation Blocks', String(stats.automationCount ?? '—')],
191
+ ['Active Rules', String(stats.activeAutomationRules ?? '—')],
192
+ ]);
187
193
  console.log('');
188
194
  } catch (err) {
189
195
  s.fail('Failed to fetch stats');
@@ -198,7 +204,7 @@ async function trustedList(args, flags) {
198
204
  const s = ui.spinner('Fetching trusted IPs');
199
205
  try {
200
206
  const data = await api.get('/trusted-ips');
201
- const items = Array.isArray(data) ? data : data.trustedIps || data.items || [];
207
+ const items = data.trustedIps || [];
202
208
  s.stop(`Found ${items.length} trusted IP${items.length !== 1 ? 's' : ''}`);
203
209
 
204
210
  if (flags.json) { ui.json(items); return; }
@@ -278,17 +284,40 @@ async function forensicsQuery(args, flags) {
278
284
  const body = { query };
279
285
  if (flags.instance) body.instanceId = flags.instance;
280
286
 
281
- const job = await api.post('/forensics/jobs', body);
282
- const jobId = job.jobId || job._id || job.id;
287
+ const job = await api.post('/forensics/query', body);
288
+ const jobId = job.jobId;
289
+
290
+ if (!jobId) {
291
+ s.stop('Query complete');
292
+ if (flags.json) { ui.json(job); return; }
293
+ if (job.result) {
294
+ console.log('');
295
+ if (job.sqlquery) {
296
+ ui.subheading('Generated SQL');
297
+ console.log(`\n ${ui.c.dim(job.sqlquery)}\n`);
298
+ }
299
+ const data = job.result;
300
+ if (Array.isArray(data) && data.length > 0) {
301
+ const headers = Object.keys(data[0]);
302
+ const rows = data.map(row => headers.map(h => String(row[h] ?? '')));
303
+ ui.table(headers, rows);
304
+ } else {
305
+ ui.json(data);
306
+ }
307
+ console.log('');
308
+ }
309
+ return;
310
+ }
311
+
283
312
  s.update('Processing query...');
284
313
 
285
314
  let result;
286
315
  const maxAttempts = 60;
287
316
  for (let i = 0; i < maxAttempts; i++) {
288
317
  await new Promise(r => setTimeout(r, 2000));
289
- result = await api.get(`/forensics/jobs/${jobId}`);
318
+ result = await api.get(`/forensics/query/status/${jobId}`);
290
319
  if (result.status === 'completed' || result.status === 'failed') break;
291
- s.update(`Processing query... (${i * 2}s)`);
320
+ s.update(`Processing query... (${(i + 1) * 2}s)`);
292
321
  }
293
322
 
294
323
  if (result.status === 'failed') {
@@ -308,14 +337,14 @@ async function forensicsQuery(args, flags) {
308
337
  if (flags.json) { ui.json(result); return; }
309
338
 
310
339
  console.log('');
311
- if (result.sql) {
340
+ if (result.sqlquery) {
312
341
  ui.subheading('Generated SQL');
313
- console.log(`\n ${ui.c.dim(result.sql)}\n`);
342
+ console.log(`\n ${ui.c.dim(result.sqlquery)}\n`);
314
343
  }
315
344
 
316
- if (result.results || result.data) {
317
- const data = result.results || result.data;
318
- ui.subheading(`Results (${Array.isArray(data) ? data.length : '?'} rows)`);
345
+ if (result.result) {
346
+ const data = result.result;
347
+ ui.subheading(`Results (${result.rowCount ?? (Array.isArray(data) ? data.length : '?')} rows)`);
319
348
  console.log('');
320
349
 
321
350
  if (Array.isArray(data) && data.length > 0) {
@@ -326,11 +355,6 @@ async function forensicsQuery(args, flags) {
326
355
  ui.json(data);
327
356
  }
328
357
  }
329
-
330
- if (result.explanation) {
331
- ui.subheading('Explanation');
332
- console.log(`\n ${result.explanation}\n`);
333
- }
334
358
  console.log('');
335
359
  } catch (err) {
336
360
  s.fail('Forensic query failed');
@@ -343,7 +367,7 @@ async function forensicsLibrary(args, flags) {
343
367
  const s = ui.spinner('Fetching query library');
344
368
  try {
345
369
  const data = await api.get('/forensics/query-library');
346
- const queries = Array.isArray(data) ? data : data.queries || [];
370
+ const queries = data.data || [];
347
371
  s.stop(`Found ${queries.length} saved quer${queries.length !== 1 ? 'ies' : 'y'}`);
348
372
 
349
373
  if (flags.json) { ui.json(queries); return; }
@@ -381,46 +405,42 @@ async function ipLookup(args, flags) {
381
405
  if (flags.json) { ui.json(data); return; }
382
406
 
383
407
  console.log('');
384
- ui.heading(`IP Intelligence: ${ip}`);
408
+ ui.heading(`IP Intelligence: ${data.ip || ip}`);
385
409
  console.log('');
386
410
 
387
- const info = data.ipData || data.intel || data;
388
411
  const pairs = [];
389
-
390
- if (info.country) pairs.push(['Country', info.country]);
391
- if (info.city) pairs.push(['City', info.city]);
392
- if (info.region) pairs.push(['Region', info.region]);
393
- if (info.org || info.organization) pairs.push(['Organization', info.org || info.organization]);
394
- if (info.isp) pairs.push(['ISP', info.isp]);
395
- if (info.asn) pairs.push(['ASN', String(info.asn)]);
396
- if (info.abuseScore != null) pairs.push(['Abuse Score', `${info.abuseScore}/100`]);
397
- if (info.isVpn != null) pairs.push(['VPN', info.isVpn ? ui.c.yellow('Yes') : 'No']);
398
- if (info.isTor != null) pairs.push(['Tor', info.isTor ? ui.c.red('Yes') : 'No']);
399
- if (info.isProxy != null) pairs.push(['Proxy', info.isProxy ? ui.c.yellow('Yes') : 'No']);
400
- if (info.isBot != null) pairs.push(['Bot', info.isBot ? ui.c.red('Yes') : 'No']);
401
- if (info.isCrawler != null) pairs.push(['Crawler', info.isCrawler ? ui.c.yellow('Yes') : 'No']);
402
- if (info.threatLevel) pairs.push(['Threat Level', ui.statusBadge(info.threatLevel)]);
403
- if (info.riskLevel) pairs.push(['Risk Level', ui.statusBadge(info.riskLevel)]);
412
+ if (data.countryName || data.countryCode) pairs.push(['Country', `${data.countryName || ''} ${data.countryCode ? `(${data.countryCode})` : ''}`.trim()]);
413
+ if (data.domain) pairs.push(['Domain', data.domain]);
414
+ if (data.isp) pairs.push(['ISP', data.isp]);
415
+ if (data.usageType) pairs.push(['Usage Type', data.usageType]);
416
+ if (data.abuseConfidenceScore != null) pairs.push(['Abuse Score', `${data.abuseConfidenceScore}/100`]);
417
+ if (data.securenowScore != null) pairs.push(['SecureNow Score', String(data.securenowScore)]);
418
+ if (data.verdict) pairs.push(['Verdict', data.verdict]);
419
+ if (data.isMalicious != null) pairs.push(['Malicious', data.isMalicious ? ui.c.red('Yes') : ui.c.green('No')]);
420
+ if (data.isBot != null) pairs.push(['Bot', data.isBot ? ui.c.yellow('Yes') : 'No']);
421
+ if (data.activityType) pairs.push(['Activity', data.activityType]);
422
+ if (data.totalReports != null) pairs.push(['Total Reports', String(data.totalReports)]);
423
+ if (data.lastReportedAt) pairs.push(['Last Reported', new Date(data.lastReportedAt).toLocaleString()]);
404
424
 
405
425
  if (pairs.length) {
406
426
  ui.keyValue(pairs);
407
- } else {
408
- ui.keyValue(Object.entries(info).slice(0, 20).map(([k, v]) => [k, String(v)]));
409
427
  }
410
428
 
411
- if (data.traces || data.recentActivity) {
412
- const activity = data.traces || data.recentActivity;
413
- if (Array.isArray(activity) && activity.length > 0) {
414
- ui.subheading(`Recent Activity (${activity.length})`);
415
- console.log('');
416
- const rows = activity.slice(0, 10).map(a => [
417
- a.method || '—',
418
- ui.httpStatusColor(a.statusCode || '—'),
419
- ui.truncate(a.url || a.path || '', 40),
420
- ui.timeAgo(a.timestamp),
421
- ]);
422
- ui.table(['Method', 'Status', 'URL', 'Time'], rows);
423
- }
429
+ if (data.riskFactors?.length) {
430
+ ui.subheading('Risk Factors');
431
+ console.log('');
432
+ data.riskFactors.forEach(f => console.log(` • ${f}`));
433
+ }
434
+
435
+ if (data.attackTypes?.length) {
436
+ ui.subheading('Attack Types');
437
+ console.log('');
438
+ data.attackTypes.forEach(a => console.log(` • ${a}`));
439
+ }
440
+
441
+ if (data.summary) {
442
+ ui.subheading('Summary');
443
+ console.log(`\n ${data.summary}`);
424
444
  }
425
445
  console.log('');
426
446
  } catch (err) {
@@ -440,16 +460,16 @@ async function ipTraces(args, flags) {
440
460
  const s = ui.spinner(`Fetching traces for ${ip}`);
441
461
  try {
442
462
  const data = await api.get(`/ip/${ip}/traces`);
443
- const traces = Array.isArray(data) ? data : data.traces || [];
463
+ const traces = data.traces || [];
444
464
  s.stop(`Found ${traces.length} trace${traces.length !== 1 ? 's' : ''}`);
445
465
 
446
- if (flags.json) { ui.json(traces); return; }
466
+ if (flags.json) { ui.json(data); return; }
447
467
 
448
468
  console.log('');
449
469
  const rows = traces.map(t => [
450
470
  ui.c.dim(ui.truncate(t.traceID || t.traceId, 16)),
451
471
  t.httpMethod || t.method || '—',
452
- ui.httpStatusColor(t.statusCode || t.httpStatusCode || '—'),
472
+ ui.httpStatusColor(t.statusCode || t.httpStatusCode || t.responseStatusCode || '—'),
453
473
  ui.truncate(t.httpUrl || t.url || '', 40),
454
474
  ui.durationColor(t.durationNano ? t.durationNano / 1e6 : t.duration),
455
475
  ui.timeAgo(t.timestamp),
@@ -469,22 +489,40 @@ async function apiMapList(args, flags) {
469
489
  const s = ui.spinner('Fetching API map');
470
490
  try {
471
491
  const data = await api.get('/api-map');
472
- const endpoints = Array.isArray(data) ? data : data.endpoints || data.apiMap || [];
473
- s.stop(`Found ${endpoints.length} endpoint${endpoints.length !== 1 ? 's' : ''}`);
492
+ const apiMap = data.apiMap;
493
+ s.stop('API map loaded');
474
494
 
475
495
  if (flags.json) { ui.json(data); return; }
476
496
 
497
+ if (!apiMap) {
498
+ console.log('');
499
+ ui.info(data.message || 'No API map discovered yet. Run discovery from the dashboard.');
500
+ console.log('');
501
+ return;
502
+ }
503
+
477
504
  console.log('');
478
- const rows = endpoints.map(e => [
479
- e.method || '—',
480
- e.path || e.route || e.url || '—',
481
- e.serviceName || e.app || '',
482
- e.requestCount != null ? String(e.requestCount) : '—',
483
- e.avgDuration != null ? ui.durationColor(e.avgDuration) : '—',
484
- e.errorRate != null ? (e.errorRate > 5 ? ui.c.red(`${e.errorRate}%`) : `${e.errorRate}%`) : '—',
485
- ]);
486
- ui.table(['Method', 'Path', 'App', 'Requests', 'Avg Duration', 'Error Rate'], rows);
487
- console.log('');
505
+ if (apiMap.apps && typeof apiMap.apps === 'object') {
506
+ for (const [appName, appData] of Object.entries(apiMap.apps)) {
507
+ ui.subheading(appName);
508
+ console.log('');
509
+ const endpoints = appData.endpoints || [];
510
+ if (endpoints.length) {
511
+ const rows = endpoints.map(e => [
512
+ e.method || '—',
513
+ e.path || e.route || '',
514
+ e.requestCount != null ? String(e.requestCount) : '',
515
+ e.description || ui.c.dim('—'),
516
+ ]);
517
+ ui.table(['Method', 'Path', 'Requests', 'Description'], rows);
518
+ } else {
519
+ ui.info('No endpoints discovered for this app.');
520
+ }
521
+ console.log('');
522
+ }
523
+ } else {
524
+ ui.json(apiMap);
525
+ }
488
526
  } catch (err) {
489
527
  s.fail('Failed to fetch API map');
490
528
  throw err;
@@ -495,15 +533,30 @@ async function apiMapStats(args, flags) {
495
533
  requireAuth();
496
534
  const s = ui.spinner('Fetching API map stats');
497
535
  try {
498
- const stats = await api.get('/api-map/stats');
536
+ const data = await api.get('/api-map/stats');
537
+ const stats = data.stats;
499
538
  s.stop('Stats loaded');
500
539
 
501
540
  if (flags.json) { ui.json(stats); return; }
502
541
 
542
+ if (!stats) {
543
+ console.log('');
544
+ ui.info('No API map stats available.');
545
+ console.log('');
546
+ return;
547
+ }
548
+
503
549
  console.log('');
504
550
  ui.heading('API Map Statistics');
505
551
  console.log('');
506
- ui.keyValue(Object.entries(stats).map(([k, v]) => [k, String(v)]));
552
+ ui.keyValue([
553
+ ['Total Apps', String(stats.totalApps ?? '—')],
554
+ ['Total Endpoints', String(stats.totalEndpoints ?? '—')],
555
+ ['Total Requests', String(stats.totalRequests ?? '—')],
556
+ ['Discovery Status', stats.discoveryStatus || '—'],
557
+ ['Last Discovered', stats.lastDiscoveredAt ? new Date(stats.lastDiscoveredAt).toLocaleString() : '—'],
558
+ ['Version', String(stats.version ?? '—')],
559
+ ]);
507
560
  console.log('');
508
561
  } catch (err) {
509
562
  s.fail('Failed to fetch stats');
@@ -518,7 +571,7 @@ async function instancesList(args, flags) {
518
571
  const s = ui.spinner('Fetching instances');
519
572
  try {
520
573
  const data = await api.get('/instances');
521
- const instances = Array.isArray(data) ? data : data.instances || [];
574
+ const instances = data.instances || [];
522
575
  s.stop(`Found ${instances.length} instance${instances.length !== 1 ? 's' : ''}`);
523
576
 
524
577
  if (flags.json) { ui.json(instances); return; }
@@ -529,10 +582,10 @@ async function instancesList(args, flags) {
529
582
  inst.name || inst.host || '—',
530
583
  inst.host || '—',
531
584
  inst.port != null ? String(inst.port) : '—',
532
- ui.statusBadge(inst.status || 'active'),
585
+ inst.linkedApps != null ? String(inst.linkedApps) : '',
533
586
  ui.timeAgo(inst.createdAt),
534
587
  ]);
535
- ui.table(['ID', 'Name', 'Host', 'Port', 'Status', 'Added'], rows);
588
+ ui.table(['ID', 'Name', 'Host', 'Port', 'Linked Apps', 'Added'], rows);
536
589
  console.log('');
537
590
  } catch (err) {
538
591
  s.fail('Failed to fetch instances');
@@ -551,11 +604,10 @@ async function instancesTest(args, flags) {
551
604
  const s = ui.spinner('Testing instance connection');
552
605
  try {
553
606
  const result = await api.post(`/instances/${id}/test`);
554
- if (result.success || result.connected) {
555
- s.stop('Instance connection successful');
607
+ if (result.success) {
608
+ s.stop(`Connection successful${result.storageGb ? ` (${result.storageGb} GB storage)` : ''}`);
556
609
  } else {
557
- s.fail('Instance connection failed');
558
- if (result.error) ui.error(result.error);
610
+ s.fail(result.message || 'Connection failed');
559
611
  }
560
612
 
561
613
  if (flags.json) ui.json(result);
@@ -569,11 +621,10 @@ async function instancesTest(args, flags) {
569
621
 
570
622
  async function analytics(args, flags) {
571
623
  requireAuth();
572
- const appKey = resolveApp(flags);
573
624
  const s = ui.spinner('Fetching analytics');
574
625
  try {
575
626
  const query = {};
576
- if (appKey) query.serviceName = appKey;
627
+ const appKey = resolveApp(flags);
577
628
  if (flags.instance) query.instanceId = flags.instance;
578
629
 
579
630
  const endpoints = ['2xx-responses', '3xx-responses', '4xx-responses', '5xx-responses', '500-errors'];
@@ -596,7 +647,7 @@ async function analytics(args, flags) {
596
647
 
597
648
  const pairs = endpoints.map((ep, i) => {
598
649
  const val = results[i];
599
- const count = val?.count ?? val?.total ?? (typeof val === 'number' ? val : '—');
650
+ const count = val?.meta?.count ?? '—';
600
651
  return [ep, String(count)];
601
652
  });
602
653
  ui.keyValue(pairs);
package/cli.js CHANGED
@@ -377,13 +377,11 @@ async function main() {
377
377
  }
378
378
 
379
379
  main().catch((err) => {
380
- if (err.name === 'CLIError') {
381
- ui.error(err.message);
382
- } else {
380
+ if (err.name !== 'CLIError') {
383
381
  ui.error(err.message || 'An unexpected error occurred');
384
- if (process.env.SECURENOW_DEBUG) {
385
- console.error(err.stack);
386
- }
382
+ }
383
+ if (process.env.SECURENOW_DEBUG) {
384
+ console.error(err.stack || err);
387
385
  }
388
386
  process.exit(1);
389
387
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "5.3.0",
3
+ "version": "5.3.1",
4
4
  "description": "OpenTelemetry instrumentation for Node.js and Next.js - Send traces and logs to any OTLP-compatible backend",
5
5
  "type": "commonjs",
6
6
  "main": "register.js",