securenow 5.17.0 → 5.18.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/NPM_README.md +3 -28
- package/README.md +1 -5
- package/SKILL-CLI.md +1 -11
- package/cli/config.js +9 -3
- package/cli/monitor.js +54 -114
- package/cli/ui.js +31 -3
- package/cli.js +3 -13
- package/package.json +1 -1
package/NPM_README.md
CHANGED
|
@@ -211,20 +211,6 @@ npx securenow logs --app <key> --minutes 30 --level error
|
|
|
211
211
|
npx securenow logs trace <traceId>
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
### Security Issues
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
# List all issues
|
|
218
|
-
npx securenow issues
|
|
219
|
-
npx securenow issues --status open
|
|
220
|
-
|
|
221
|
-
# Show issue details with AI analysis
|
|
222
|
-
npx securenow issues show <issue-id>
|
|
223
|
-
|
|
224
|
-
# Resolve an issue
|
|
225
|
-
npx securenow issues resolve <issue-id>
|
|
226
|
-
```
|
|
227
|
-
|
|
228
214
|
### Notifications
|
|
229
215
|
|
|
230
216
|
```bash
|
|
@@ -382,21 +368,13 @@ You can also use the `SECURENOW_TOKEN` env var for per-terminal sessions without
|
|
|
382
368
|
|
|
383
369
|
```bash
|
|
384
370
|
# Authenticate with a token in CI (env var — no file needed)
|
|
385
|
-
SECURENOW_TOKEN=$MY_SECRET npx securenow
|
|
371
|
+
SECURENOW_TOKEN=$MY_SECRET npx securenow logs --json
|
|
386
372
|
|
|
387
373
|
# Or use login with explicit token
|
|
388
374
|
npx securenow login --token $SECURENOW_TOKEN
|
|
389
375
|
|
|
390
376
|
# Use --json for machine-readable output
|
|
391
|
-
npx securenow
|
|
392
|
-
|
|
393
|
-
# Check for critical issues in a pipeline
|
|
394
|
-
ISSUES=$(npx securenow issues --json --status open)
|
|
395
|
-
CRITICAL=$(echo "$ISSUES" | jq '[.[] | select(.severity == "critical")] | length')
|
|
396
|
-
if [ "$CRITICAL" -gt "0" ]; then
|
|
397
|
-
echo "Found $CRITICAL critical issues!"
|
|
398
|
-
exit 1
|
|
399
|
-
fi
|
|
377
|
+
npx securenow logs --json --level error | jq '.logs'
|
|
400
378
|
```
|
|
401
379
|
|
|
402
380
|
### Complete Command Reference
|
|
@@ -419,10 +397,7 @@ fi
|
|
|
419
397
|
| | `logs trace <id>` | Logs for a trace |
|
|
420
398
|
| | `analytics` | Response analytics |
|
|
421
399
|
| | `status` | Dashboard overview |
|
|
422
|
-
| **Detect** | `
|
|
423
|
-
| | `issues show <id>` | Issue details |
|
|
424
|
-
| | `issues resolve <id>` | Resolve issue |
|
|
425
|
-
| | `notifications` | List notifications |
|
|
400
|
+
| **Detect** | `notifications` | List notifications |
|
|
426
401
|
| | `notifications unread` | Unread count |
|
|
427
402
|
| | `notifications read <id>` | Mark read |
|
|
428
403
|
| | `notifications read-all` | Mark all read |
|
package/README.md
CHANGED
|
@@ -117,10 +117,9 @@ npx securenow apps create my-app
|
|
|
117
117
|
# Set it as default so you don't need --app every time
|
|
118
118
|
npx securenow config set defaultApp <key>
|
|
119
119
|
|
|
120
|
-
# View traces, logs
|
|
120
|
+
# View traces, logs
|
|
121
121
|
npx securenow traces
|
|
122
122
|
npx securenow logs
|
|
123
|
-
npx securenow issues
|
|
124
123
|
|
|
125
124
|
# IP intelligence, forensic queries, blocklist
|
|
126
125
|
npx securenow ip 1.2.3.4
|
|
@@ -295,9 +294,6 @@ Most users won't need this — just add `-r securenow/register` to your existing
|
|
|
295
294
|
|
|
296
295
|
| Command | Description |
|
|
297
296
|
|---------|-------------|
|
|
298
|
-
| `securenow issues` | List security issues |
|
|
299
|
-
| `securenow issues show <id>` | Show issue details and AI analysis |
|
|
300
|
-
| `securenow issues resolve <id>` | Mark an issue as resolved |
|
|
301
297
|
| `securenow notifications` | List notifications |
|
|
302
298
|
| `securenow notifications unread` | Show unread count |
|
|
303
299
|
| `securenow notifications read <id>` | Mark notification as read |
|
package/SKILL-CLI.md
CHANGED
|
@@ -159,15 +159,6 @@ securenow logs list --app my-app --minutes 30 --level error
|
|
|
159
159
|
securenow logs trace <traceId> # logs correlated to a specific trace
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
### Issues
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
securenow issues [--app <key>] [--status open|resolved]
|
|
166
|
-
securenow issues list --status open
|
|
167
|
-
securenow issues show <id> # full issue details
|
|
168
|
-
securenow issues resolve <id> # mark as resolved
|
|
169
|
-
```
|
|
170
|
-
|
|
171
162
|
### Notifications
|
|
172
163
|
|
|
173
164
|
```bash
|
|
@@ -342,7 +333,7 @@ securenow ip traces <attacker-ip> --json
|
|
|
342
333
|
securenow traces show <trace-id> --json
|
|
343
334
|
securenow traces analyze <trace-id> --json
|
|
344
335
|
# Decision: block the IP
|
|
345
|
-
securenow blocklist add <attacker-ip> --reason "Automated: SQL injection
|
|
336
|
+
securenow blocklist add <attacker-ip> --reason "Automated: SQL injection detected"
|
|
346
337
|
```
|
|
347
338
|
|
|
348
339
|
#### Notification IP Investigation Statuses
|
|
@@ -387,7 +378,6 @@ securenow status --json
|
|
|
387
378
|
securenow firewall status --json
|
|
388
379
|
securenow blocklist stats --json
|
|
389
380
|
securenow api-map stats --json
|
|
390
|
-
securenow issues list --status open --json
|
|
391
381
|
securenow forensics "summarize all attacks in the last 7 days"
|
|
392
382
|
```
|
|
393
383
|
|
package/cli/config.js
CHANGED
|
@@ -62,12 +62,18 @@ function getAuthSource() {
|
|
|
62
62
|
function loadConfig() {
|
|
63
63
|
const global = loadJSON(CONFIG_FILE);
|
|
64
64
|
const local = fs.existsSync(LOCAL_CONFIG_FILE) ? loadJSON(LOCAL_CONFIG_FILE) : {};
|
|
65
|
+
if (hasLocalCredentials()) {
|
|
66
|
+
const { defaultApp: _ignored, ...globalWithoutAccountScoped } = global;
|
|
67
|
+
return { ...DEFAULTS, ...globalWithoutAccountScoped, ...local };
|
|
68
|
+
}
|
|
65
69
|
return { ...DEFAULTS, ...global, ...local };
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
function saveConfig(config) {
|
|
69
|
-
const
|
|
70
|
-
|
|
72
|
+
function saveConfig(config, { local } = {}) {
|
|
73
|
+
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
74
|
+
const targetFile = useLocal ? LOCAL_CONFIG_FILE : CONFIG_FILE;
|
|
75
|
+
const existing = loadJSON(targetFile);
|
|
76
|
+
saveJSON(targetFile, { ...existing, ...config });
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
function getConfigValue(key) {
|
package/cli/monitor.js
CHANGED
|
@@ -163,6 +163,24 @@ async function tracesAnalyze(args, flags) {
|
|
|
163
163
|
|
|
164
164
|
// ── Logs ──
|
|
165
165
|
|
|
166
|
+
// Parse a duration string like "6h", "30m", "2d", "90s" into minutes.
|
|
167
|
+
// Returns null on unparseable input so the caller can error out clearly
|
|
168
|
+
// instead of silently falling through to a default.
|
|
169
|
+
function parseDurationToMinutes(value) {
|
|
170
|
+
if (value == null || value === '') return null;
|
|
171
|
+
const m = String(value).trim().match(/^(\d+)\s*(s|m|h|d)?$/i);
|
|
172
|
+
if (!m) return null;
|
|
173
|
+
const n = parseInt(m[1], 10);
|
|
174
|
+
const unit = (m[2] || 'm').toLowerCase();
|
|
175
|
+
if (unit === 's') return Math.max(1, Math.round(n / 60));
|
|
176
|
+
if (unit === 'm') return n;
|
|
177
|
+
if (unit === 'h') return n * 60;
|
|
178
|
+
if (unit === 'd') return n * 60 * 24;
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const LOG_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'WARNING', 'ERROR', 'FATAL'];
|
|
183
|
+
|
|
166
184
|
async function logsList(args, flags) {
|
|
167
185
|
requireAuth();
|
|
168
186
|
const appKey = resolveApp(flags);
|
|
@@ -171,17 +189,42 @@ async function logsList(args, flags) {
|
|
|
171
189
|
process.exit(1);
|
|
172
190
|
}
|
|
173
191
|
|
|
192
|
+
let minutes = 60;
|
|
193
|
+
if (flags.since != null) {
|
|
194
|
+
const parsed = parseDurationToMinutes(flags.since);
|
|
195
|
+
if (parsed == null) {
|
|
196
|
+
ui.error(`Invalid --since value "${flags.since}". Expected e.g. 30m, 6h, 2d.`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
minutes = parsed;
|
|
200
|
+
} else if (flags.minutes != null) {
|
|
201
|
+
const n = parseInt(flags.minutes, 10);
|
|
202
|
+
if (isNaN(n) || n <= 0) {
|
|
203
|
+
ui.error(`Invalid --minutes value "${flags.minutes}".`);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
minutes = n;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let severity = null;
|
|
210
|
+
if (flags.level) {
|
|
211
|
+
severity = String(flags.level).toUpperCase();
|
|
212
|
+
if (!LOG_LEVELS.includes(severity)) {
|
|
213
|
+
ui.error(`Invalid --level "${flags.level}". Expected one of: ${LOG_LEVELS.join(', ').toLowerCase()}.`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
174
218
|
const s = ui.spinner('Fetching logs');
|
|
175
219
|
try {
|
|
176
|
-
const minutes = parseInt(flags.minutes || '60', 10);
|
|
177
220
|
const now = Date.now();
|
|
178
221
|
const query = {
|
|
179
222
|
appKeys: appKey,
|
|
180
|
-
limit: flags.limit ||
|
|
223
|
+
limit: flags.limit || 200,
|
|
181
224
|
from: flags.start || new Date(now - minutes * 60 * 1000).toISOString(),
|
|
182
225
|
to: flags.end || new Date(now).toISOString(),
|
|
183
226
|
};
|
|
184
|
-
if (
|
|
227
|
+
if (severity) query.severity = severity;
|
|
185
228
|
|
|
186
229
|
const data = await api.get('/logs', { query });
|
|
187
230
|
const logs = data.logs || [];
|
|
@@ -193,11 +236,12 @@ async function logsList(args, flags) {
|
|
|
193
236
|
for (const log of logs) {
|
|
194
237
|
const level = (log.severityText || log.level || 'INFO').toUpperCase();
|
|
195
238
|
const levelColor = {
|
|
196
|
-
ERROR: ui.c.red, WARN: ui.c.yellow, WARNING: ui.c.yellow,
|
|
197
|
-
INFO: ui.c.cyan, DEBUG: ui.c.dim,
|
|
239
|
+
ERROR: ui.c.red, FATAL: ui.c.red, WARN: ui.c.yellow, WARNING: ui.c.yellow,
|
|
240
|
+
INFO: ui.c.cyan, DEBUG: ui.c.dim, TRACE: ui.c.dim,
|
|
198
241
|
}[level] || ui.c.white;
|
|
199
242
|
|
|
200
|
-
const
|
|
243
|
+
const parsedTime = ui.parseTimestamp(log.timestamp);
|
|
244
|
+
const time = ui.c.dim(parsedTime ? parsedTime.toLocaleTimeString() : '');
|
|
201
245
|
const body = log.body || log.message || log.severityText || '';
|
|
202
246
|
|
|
203
247
|
console.log(` ${time} ${levelColor(level.padEnd(7))} ${body}`);
|
|
@@ -233,11 +277,12 @@ async function logsTrace(args, flags) {
|
|
|
233
277
|
for (const log of logs) {
|
|
234
278
|
const level = (log.severityText || log.level || 'INFO').toUpperCase();
|
|
235
279
|
const levelColor = {
|
|
236
|
-
ERROR: ui.c.red, WARN: ui.c.yellow, WARNING: ui.c.yellow,
|
|
237
|
-
INFO: ui.c.cyan, DEBUG: ui.c.dim,
|
|
280
|
+
ERROR: ui.c.red, FATAL: ui.c.red, WARN: ui.c.yellow, WARNING: ui.c.yellow,
|
|
281
|
+
INFO: ui.c.cyan, DEBUG: ui.c.dim, TRACE: ui.c.dim,
|
|
238
282
|
}[level] || ui.c.white;
|
|
239
283
|
|
|
240
|
-
const
|
|
284
|
+
const parsedTime = ui.parseTimestamp(log.timestamp);
|
|
285
|
+
const time = ui.c.dim(parsedTime ? parsedTime.toLocaleTimeString() : '');
|
|
241
286
|
console.log(` ${time} ${levelColor(level.padEnd(7))} ${log.body || log.message || ''}`);
|
|
242
287
|
}
|
|
243
288
|
console.log('');
|
|
@@ -247,108 +292,6 @@ async function logsTrace(args, flags) {
|
|
|
247
292
|
}
|
|
248
293
|
}
|
|
249
294
|
|
|
250
|
-
// ── Issues ──
|
|
251
|
-
|
|
252
|
-
async function issuesList(args, flags) {
|
|
253
|
-
requireAuth();
|
|
254
|
-
const s = ui.spinner('Fetching issues');
|
|
255
|
-
try {
|
|
256
|
-
const query = {};
|
|
257
|
-
const appKey = resolveApp(flags);
|
|
258
|
-
if (appKey) query.serviceName = appKey;
|
|
259
|
-
if (flags.status) query.status = flags.status;
|
|
260
|
-
|
|
261
|
-
const data = await api.get('/issues', { query });
|
|
262
|
-
const issues = data.issues || [];
|
|
263
|
-
s.stop(`Found ${issues.length} issue${issues.length !== 1 ? 's' : ''}`);
|
|
264
|
-
|
|
265
|
-
if (flags.json) { ui.json(data); return; }
|
|
266
|
-
|
|
267
|
-
console.log('');
|
|
268
|
-
const rows = issues.map(i => [
|
|
269
|
-
ui.c.dim(ui.truncate(i._id, 12)),
|
|
270
|
-
ui.statusBadge(i.severity || i.level || 'medium'),
|
|
271
|
-
ui.statusBadge(i.status || 'open'),
|
|
272
|
-
ui.truncate(i.title || i.message || i.type || '', 50),
|
|
273
|
-
i.serviceName || '—',
|
|
274
|
-
i.count != null ? String(i.count) : '—',
|
|
275
|
-
ui.timeAgo(i.lastSeen || i.createdAt),
|
|
276
|
-
]);
|
|
277
|
-
|
|
278
|
-
ui.table(['ID', 'Severity', 'Status', 'Title', 'App', 'Count', 'Last Seen'], rows);
|
|
279
|
-
if (data.total != null) {
|
|
280
|
-
console.log(ui.c.dim(` Total: ${data.total}`));
|
|
281
|
-
}
|
|
282
|
-
console.log('');
|
|
283
|
-
} catch (err) {
|
|
284
|
-
s.fail('Failed to fetch issues');
|
|
285
|
-
throw err;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async function issuesShow(args, flags) {
|
|
290
|
-
requireAuth();
|
|
291
|
-
const id = args[0];
|
|
292
|
-
if (!id) {
|
|
293
|
-
ui.error('Issue ID required. Usage: securenow issues show <id>');
|
|
294
|
-
process.exit(1);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const s = ui.spinner('Fetching issue');
|
|
298
|
-
try {
|
|
299
|
-
const data = await api.get(`/issues/${id}`);
|
|
300
|
-
const issue = data.issue || data;
|
|
301
|
-
s.stop('Issue loaded');
|
|
302
|
-
|
|
303
|
-
if (flags.json) { ui.json(issue); return; }
|
|
304
|
-
|
|
305
|
-
console.log('');
|
|
306
|
-
ui.heading(issue.title || issue.type || `Issue ${id}`);
|
|
307
|
-
console.log('');
|
|
308
|
-
ui.keyValue([
|
|
309
|
-
['ID', issue._id],
|
|
310
|
-
['Status', ui.statusBadge(issue.status || 'open')],
|
|
311
|
-
['Severity', ui.statusBadge(issue.severity || issue.level || 'medium')],
|
|
312
|
-
['App', issue.serviceName || '—'],
|
|
313
|
-
['Type', issue.type || '—'],
|
|
314
|
-
['Count', issue.count != null ? String(issue.count) : '—'],
|
|
315
|
-
['First Seen', issue.firstSeen ? new Date(issue.firstSeen).toLocaleString() : '—'],
|
|
316
|
-
['Last Seen', issue.lastSeen ? new Date(issue.lastSeen).toLocaleString() : '—'],
|
|
317
|
-
]);
|
|
318
|
-
|
|
319
|
-
if (issue.message || issue.description) {
|
|
320
|
-
ui.subheading('Description');
|
|
321
|
-
console.log(`\n ${issue.message || issue.description}\n`);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (issue.analysis) {
|
|
325
|
-
ui.subheading('AI Analysis');
|
|
326
|
-
console.log(`\n ${issue.analysis}\n`);
|
|
327
|
-
}
|
|
328
|
-
console.log('');
|
|
329
|
-
} catch (err) {
|
|
330
|
-
s.fail('Failed to fetch issue');
|
|
331
|
-
throw err;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async function issuesResolve(args, flags) {
|
|
336
|
-
requireAuth();
|
|
337
|
-
const id = args[0];
|
|
338
|
-
if (!id) {
|
|
339
|
-
ui.error('Issue ID required. Usage: securenow issues resolve <id>');
|
|
340
|
-
process.exit(1);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const s = ui.spinner('Resolving issue');
|
|
344
|
-
try {
|
|
345
|
-
await api.patch(`/issues/${id}`, { status: 'resolved' });
|
|
346
|
-
s.stop('Issue resolved');
|
|
347
|
-
} catch (err) {
|
|
348
|
-
s.fail('Failed to resolve issue');
|
|
349
|
-
throw err;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
295
|
|
|
353
296
|
// ── Notifications ──
|
|
354
297
|
|
|
@@ -489,9 +432,6 @@ module.exports = {
|
|
|
489
432
|
tracesAnalyze,
|
|
490
433
|
logsList,
|
|
491
434
|
logsTrace,
|
|
492
|
-
issuesList,
|
|
493
|
-
issuesShow,
|
|
494
|
-
issuesResolve,
|
|
495
435
|
notificationsList,
|
|
496
436
|
notificationsRead,
|
|
497
437
|
notificationsReadAll,
|
package/cli/ui.js
CHANGED
|
@@ -266,9 +266,36 @@ function hr() {
|
|
|
266
266
|
console.log(c.dim('─'.repeat(width)));
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
// Parses timestamps the backend returns in multiple formats:
|
|
270
|
+
// - UInt64 nanoseconds as a string (signoz logs) → "1775856777891000000"
|
|
271
|
+
// - ClickHouse DateTime64 string (signoz traces) → "2026-04-10 21:34:51.133000000" (UTC, no Z)
|
|
272
|
+
// - ISO 8601 → "2026-04-10T21:34:51.133Z"
|
|
273
|
+
// - number (ms) / Date → passthrough
|
|
274
|
+
function parseTimestamp(ts) {
|
|
275
|
+
if (ts == null || ts === '') return null;
|
|
276
|
+
if (ts instanceof Date) return isNaN(ts.getTime()) ? null : ts;
|
|
277
|
+
if (typeof ts === 'number') {
|
|
278
|
+
const d = new Date(ts);
|
|
279
|
+
return isNaN(d.getTime()) ? null : d;
|
|
280
|
+
}
|
|
281
|
+
const s = String(ts).trim();
|
|
282
|
+
if (/^\d{16,}$/.test(s)) {
|
|
283
|
+
const d = new Date(Number(s) / 1e6);
|
|
284
|
+
return isNaN(d.getTime()) ? null : d;
|
|
285
|
+
}
|
|
286
|
+
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(s) && !/[zZ]|[+-]\d{2}:?\d{2}$/.test(s)) {
|
|
287
|
+
const isoish = s.replace(' ', 'T').replace(/\.(\d{3})\d*$/, '.$1') + 'Z';
|
|
288
|
+
const d = new Date(isoish);
|
|
289
|
+
if (!isNaN(d.getTime())) return d;
|
|
290
|
+
}
|
|
291
|
+
const d = new Date(s);
|
|
292
|
+
return isNaN(d.getTime()) ? null : d;
|
|
293
|
+
}
|
|
294
|
+
|
|
269
295
|
function timeAgo(dateStr) {
|
|
270
|
-
|
|
271
|
-
|
|
296
|
+
const date = parseTimestamp(dateStr);
|
|
297
|
+
if (!date) return '—';
|
|
298
|
+
const diff = Date.now() - date.getTime();
|
|
272
299
|
const seconds = Math.floor(diff / 1000);
|
|
273
300
|
if (seconds < 60) return 'just now';
|
|
274
301
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -277,7 +304,7 @@ function timeAgo(dateStr) {
|
|
|
277
304
|
if (hours < 24) return `${hours}h ago`;
|
|
278
305
|
const days = Math.floor(hours / 24);
|
|
279
306
|
if (days < 30) return `${days}d ago`;
|
|
280
|
-
return
|
|
307
|
+
return date.toLocaleDateString();
|
|
281
308
|
}
|
|
282
309
|
|
|
283
310
|
function truncate(str, len = 50) {
|
|
@@ -351,6 +378,7 @@ module.exports = {
|
|
|
351
378
|
json,
|
|
352
379
|
hr,
|
|
353
380
|
timeAgo,
|
|
381
|
+
parseTimestamp,
|
|
354
382
|
truncate,
|
|
355
383
|
statusBadge,
|
|
356
384
|
httpStatusColor,
|
package/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const ui = require('./cli/ui');
|
|
@@ -90,21 +90,11 @@ const COMMANDS = {
|
|
|
90
90
|
desc: 'View application logs',
|
|
91
91
|
usage: 'securenow logs [options]',
|
|
92
92
|
sub: {
|
|
93
|
-
list: { desc: 'List recent logs', flags: { app: 'App key', limit: 'Max results', minutes: 'Time window in minutes', level: 'Filter by level' }, run: (a, f) => require('./cli/monitor').logsList(a, f) },
|
|
93
|
+
list: { desc: 'List recent logs', flags: { app: 'App key', limit: 'Max results (default 200)', since: 'Time window (e.g. 30m, 6h, 2d)', minutes: 'Time window in minutes (alias for --since Nm)', level: 'Filter by level (error, warn, info, debug)', start: 'Start time (ISO 8601)', end: 'End time (ISO 8601)' }, run: (a, f) => require('./cli/monitor').logsList(a, f) },
|
|
94
94
|
trace: { desc: 'Show logs for a trace', usage: 'securenow logs trace <traceId>', run: (a, f) => require('./cli/monitor').logsTrace(a, f) },
|
|
95
95
|
},
|
|
96
96
|
defaultSub: 'list',
|
|
97
97
|
},
|
|
98
|
-
issues: {
|
|
99
|
-
desc: 'Manage security issues',
|
|
100
|
-
usage: 'securenow issues <subcommand> [options]',
|
|
101
|
-
sub: {
|
|
102
|
-
list: { desc: 'List issues', flags: { app: 'App key', status: 'Filter by status' }, run: (a, f) => require('./cli/monitor').issuesList(a, f) },
|
|
103
|
-
show: { desc: 'Show issue details', usage: 'securenow issues show <id>', run: (a, f) => require('./cli/monitor').issuesShow(a, f) },
|
|
104
|
-
resolve: { desc: 'Resolve an issue', usage: 'securenow issues resolve <id>', run: (a, f) => require('./cli/monitor').issuesResolve(a, f) },
|
|
105
|
-
},
|
|
106
|
-
defaultSub: 'list',
|
|
107
|
-
},
|
|
108
98
|
notifications: {
|
|
109
99
|
desc: 'Manage notifications',
|
|
110
100
|
usage: 'securenow notifications <subcommand> [options]',
|
|
@@ -362,7 +352,7 @@ function showHelp(commandName) {
|
|
|
362
352
|
'Authentication': ['login', 'logout', 'whoami'],
|
|
363
353
|
'Applications': ['apps', 'init', 'status'],
|
|
364
354
|
'Observe': ['traces', 'logs', 'analytics'],
|
|
365
|
-
'Detect & Respond': ['
|
|
355
|
+
'Detect & Respond': ['notifications', 'alerts', 'fp'],
|
|
366
356
|
'Investigate': ['ip', 'forensics', 'api-map'],
|
|
367
357
|
'Firewall': ['firewall'],
|
|
368
358
|
'Remediation': ['blocklist', 'allowlist', 'trusted'],
|
package/package.json
CHANGED