securenow 5.18.0 → 6.0.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 (85) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +40 -239
  3. package/cli.js +455 -415
  4. package/console-instrumentation.js +136 -147
  5. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
  6. package/docs/ARCHITECTURE.md +3 -3
  7. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  8. package/docs/AUTO-SETUP.md +4 -4
  9. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  10. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  11. package/docs/CHANGELOG-NEXTJS.md +35 -1
  12. package/docs/CUSTOMER-GUIDE.md +16 -16
  13. package/docs/EASIEST-SETUP.md +5 -5
  14. package/docs/ENVIRONMENT-VARIABLES.md +652 -880
  15. package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
  16. package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
  17. package/docs/INDEX.md +4 -22
  18. package/docs/LOGGING-GUIDE.md +708 -701
  19. package/docs/LOGGING-QUICKSTART.md +255 -234
  20. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  21. package/docs/NEXTJS-GUIDE.md +14 -14
  22. package/docs/NEXTJS-QUICKSTART.md +1 -1
  23. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  24. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  25. package/docs/REDACTION-EXAMPLES.md +1 -1
  26. package/docs/REQUEST-BODY-CAPTURE.md +10 -19
  27. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  28. package/examples/README.md +6 -6
  29. package/examples/instrumentation-with-auto-capture.ts +1 -1
  30. package/examples/nextjs-env-example.txt +2 -2
  31. package/examples/nextjs-instrumentation.js +1 -1
  32. package/examples/nextjs-instrumentation.ts +1 -1
  33. package/examples/nextjs-with-logging-example.md +6 -6
  34. package/examples/nextjs-with-options.ts +1 -1
  35. package/examples/test-nextjs-setup.js +1 -1
  36. package/nextjs-auto-capture.js +207 -199
  37. package/nextjs-middleware.js +181 -186
  38. package/nextjs-webpack-config.js +53 -88
  39. package/nextjs-wrapper.js +158 -158
  40. package/nextjs.d.ts +1 -1
  41. package/nextjs.js +198 -186
  42. package/package.json +45 -67
  43. package/postinstall.js +6 -6
  44. package/register.d.ts +1 -1
  45. package/register.js +4 -39
  46. package/tracing.d.ts +1 -2
  47. package/tracing.js +26 -286
  48. package/web-vite.mjs +156 -239
  49. package/CONSUMING-APPS-GUIDE.md +0 -455
  50. package/NPM_README.md +0 -1933
  51. package/SKILL-API.md +0 -600
  52. package/SKILL-CLI.md +0 -409
  53. package/cidr.js +0 -83
  54. package/cli/apps.js +0 -585
  55. package/cli/auth.js +0 -280
  56. package/cli/client.js +0 -115
  57. package/cli/config.js +0 -173
  58. package/cli/firewall.js +0 -100
  59. package/cli/fp.js +0 -638
  60. package/cli/init.js +0 -201
  61. package/cli/monitor.js +0 -440
  62. package/cli/run.js +0 -133
  63. package/cli/security.js +0 -1064
  64. package/cli/ui.js +0 -386
  65. package/docs/API-KEYS-GUIDE.md +0 -233
  66. package/docs/AUTO-SETUP-SUMMARY.md +0 -331
  67. package/docs/BODY-CAPTURE-FIX.md +0 -261
  68. package/docs/COMPLETION-REPORT.md +0 -408
  69. package/docs/FINAL-SOLUTION.md +0 -335
  70. package/docs/FIREWALL-GUIDE.md +0 -426
  71. package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
  72. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
  73. package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
  74. package/docs/NUXT-GUIDE.md +0 -166
  75. package/docs/SOLUTION-SUMMARY.md +0 -312
  76. package/firewall-cloud.js +0 -212
  77. package/firewall-iptables.js +0 -139
  78. package/firewall-only.js +0 -38
  79. package/firewall-tcp.js +0 -74
  80. package/firewall.js +0 -720
  81. package/free-trial-banner.js +0 -174
  82. package/nuxt-server-plugin.mjs +0 -423
  83. package/nuxt.d.ts +0 -60
  84. package/nuxt.mjs +0 -75
  85. package/resolve-ip.js +0 -77
package/cli.js CHANGED
@@ -1,459 +1,499 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const ui = require('./cli/ui');
4
+ /**
5
+ * SecureNow CLI
6
+ *
7
+ * Usage: npx securenow init [options]
8
+ */
5
9
 
6
- // ── Argument Parser ──
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const os = require('os');
13
+ const http = require('http');
14
+ const https = require('https');
15
+ const crypto = require('crypto');
16
+ const readline = require('readline');
17
+ const { exec } = require('child_process');
7
18
 
8
- function parseArgs(argv) {
9
- const positional = [];
10
- const flags = {};
19
+ const APP_URL = (process.env.SECURENOW_APP_URL || 'https://app.securenow.ai').replace(/\/$/, '');
20
+ const API_URL = (process.env.SECURENOW_API_URL || 'https://api.securenow.ai').replace(/\/$/, '');
21
+ const CONFIG_DIR = path.join(os.homedir(), '.securenow');
22
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
11
23
 
12
- for (let i = 0; i < argv.length; i++) {
13
- const arg = argv[i];
14
- if (arg.startsWith('--')) {
15
- const eqIdx = arg.indexOf('=');
16
- if (eqIdx !== -1) {
17
- flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
18
- } else {
19
- const next = argv[i + 1];
20
- if (next && !next.startsWith('-')) {
21
- flags[arg.slice(2)] = next;
22
- i++;
23
- } else {
24
- flags[arg.slice(2)] = true;
25
- }
26
- }
27
- } else if (arg.startsWith('-') && arg.length === 2) {
28
- const shortMap = { f: 'force', y: 'yes', v: 'verbose', j: 'json' };
29
- const long = shortMap[arg[1]] || arg[1];
30
- flags[long] = true;
31
- } else {
32
- positional.push(arg);
33
- }
34
- }
24
+ const commands = {
25
+ init: initCommand,
26
+ login: loginCommand,
27
+ logout: logoutCommand,
28
+ status: statusCommand,
29
+ apps: appsCommand,
30
+ help: helpCommand,
31
+ version: versionCommand,
32
+ };
33
+
34
+ // Templates
35
+ const templates = {
36
+ typescript: `import { registerSecureNow } from 'securenow/nextjs';
35
37
 
36
- return { positional, flags };
38
+ export function register() {
39
+ registerSecureNow();
37
40
  }
38
41
 
39
- // ── Command Registry ──
40
-
41
- const COMMANDS = {
42
- init: {
43
- desc: 'Set up SecureNow in the current project (instrumentation, config, env)',
44
- usage: 'securenow init [--key <API_KEY>]',
45
- flags: { key: 'API key to write to .env', 'api-key': 'Alias for --key' },
46
- run: (a, f) => require('./cli/init').init(a, f),
47
- },
48
- login: {
49
- desc: 'Authenticate with SecureNow',
50
- usage: 'securenow login [--token <TOKEN>] [--local]',
51
- flags: { token: 'Authenticate with a token directly', local: 'Save credentials to this project only (.securenow/)' },
52
- run: (a, f) => require('./cli/auth').login(a, f),
53
- },
54
- logout: {
55
- desc: 'Clear stored credentials',
56
- usage: 'securenow logout [--local]',
57
- flags: { local: 'Clear project-local credentials only' },
58
- run: (a, f) => require('./cli/auth').logout(a, f),
59
- },
60
- whoami: {
61
- desc: 'Show current session info',
62
- usage: 'securenow whoami',
63
- run: () => require('./cli/auth').whoami(),
64
- },
65
- apps: {
66
- desc: 'Manage applications',
67
- usage: 'securenow apps <subcommand> [options]',
68
- sub: {
69
- list: { desc: 'List all applications', run: (a, f) => require('./cli/apps').list(a, f) },
70
- create: { desc: 'Create a new application (interactive instance picker)', usage: 'securenow apps create <name> [--hosts host1,host2] [--instance <id>]', run: (a, f) => require('./cli/apps').create(a, f) },
71
- info: { desc: 'Show application details', usage: 'securenow apps info <id>', run: (a, f) => require('./cli/apps').info(a, f) },
72
- delete: { desc: 'Delete an application', usage: 'securenow apps delete <id> [--force]', run: (a, f) => require('./cli/apps').remove(a, f) },
73
- default: { desc: 'Set default application', usage: 'securenow apps default <key>', run: (a, f) => require('./cli/apps').setDefault(a, f) },
74
- discover: { desc: 'Discover subdomains and add as apps', usage: 'securenow apps discover [appId] [--domain example.com]', run: (a, f) => require('./cli/apps').discover(a, f) },
75
- scan: { desc: 'Scan all app domains for new subdomains', usage: 'securenow apps scan [--yes]', run: (a, f) => require('./cli/apps').scan(a, f) },
76
- },
77
- defaultSub: 'list',
78
- },
79
- traces: {
80
- desc: 'View and analyze traces',
81
- usage: 'securenow traces <subcommand> [options]',
82
- sub: {
83
- list: { desc: 'List recent traces', flags: { app: 'App key', limit: 'Max results', start: 'Start time', end: 'End time' }, run: (a, f) => require('./cli/monitor').tracesList(a, f) },
84
- show: { desc: 'Show trace details', usage: 'securenow traces show <traceId>', run: (a, f) => require('./cli/monitor').tracesShow(a, f) },
85
- analyze: { desc: 'AI-analyze a trace', usage: 'securenow traces analyze <traceId>', run: (a, f) => require('./cli/monitor').tracesAnalyze(a, f) },
86
- },
87
- defaultSub: 'list',
88
- },
89
- logs: {
90
- desc: 'View application logs',
91
- usage: 'securenow logs [options]',
92
- sub: {
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
- trace: { desc: 'Show logs for a trace', usage: 'securenow logs trace <traceId>', run: (a, f) => require('./cli/monitor').logsTrace(a, f) },
95
- },
96
- defaultSub: 'list',
97
- },
98
- notifications: {
99
- desc: 'Manage notifications',
100
- usage: 'securenow notifications <subcommand> [options]',
101
- sub: {
102
- list: { desc: 'List notifications', flags: { limit: 'Max results', page: 'Page number' }, run: (a, f) => require('./cli/monitor').notificationsList(a, f) },
103
- read: { desc: 'Mark notification as read', usage: 'securenow notifications read <id>', run: (a, f) => require('./cli/monitor').notificationsRead(a, f) },
104
- 'read-all': { desc: 'Mark all as read', run: () => require('./cli/monitor').notificationsReadAll() },
105
- unread: { desc: 'Show unread count', run: () => require('./cli/monitor').notificationsUnread() },
106
- },
107
- defaultSub: 'list',
108
- },
109
- alerts: {
110
- desc: 'Manage alerting',
111
- usage: 'securenow alerts <subcommand> [options]',
112
- sub: {
113
- rules: {
114
- desc: 'List, show, or update alert rules',
115
- flags: {
116
- json: 'Output as JSON',
117
- 'applications-all': 'With update: scope rule to all apps',
118
- 'no-applications-all': 'With update: scope to explicit --apps list',
119
- apps: 'Comma-separated app keys (with update)',
120
- },
121
- run: (a, f) => require('./cli/security').alertRulesRoute(a, f),
122
- },
123
- channels: { desc: 'List alert channels', run: (a, f) => require('./cli/security').alertChannelsList(a, f) },
124
- history: { desc: 'View alert history', flags: { limit: 'Max results' }, run: (a, f) => require('./cli/security').alertHistoryList(a, f) },
125
- },
126
- defaultSub: 'rules',
127
- },
128
- fp: {
129
- desc: 'Manage false-positive exclusion rules',
130
- usage: 'securenow fp <subcommand> [options]',
131
- flags: { json: 'Output as JSON', conditions: 'JSON array of conditions', 'match-mode': 'Match mode: all (AND) or any (OR)', 'rule-scope': 'Scope: this_rule | specific_rules | all_existing | any_rule', 'target-rules': 'Comma-separated rule IDs (when --rule-scope specific_rules)', 'path-safe': 'Add path_safe_values condition (standard|strict)', 'query-safe': 'Add query_safe_values condition (standard|strict)', 'query-keys': 'Allowed query param names (comma-separated)', 'ua-safe': 'Add ua_safe_values condition (standard|strict)', 'headers-safe': 'Add headers_safe_values condition (standard|strict)', 'headers-keys': 'Allowed header names (comma-separated)' },
132
- sub: {
133
- list: { desc: 'List all exclusion rules', run: (a, f) => require('./cli/fp').list(a, f) },
134
- show: { desc: 'Show exclusion rule details', usage: 'securenow fp show <id>', run: (a, f) => require('./cli/fp').show(a, f) },
135
- create: { desc: 'Create an exclusion rule', usage: 'securenow fp create [--conditions \'[...]\'] [--path /api/event] [--method POST] [--path-safe standard] [--ua-safe standard] [--headers-safe standard] [--query-keys page,limit] [--headers-keys host,content-type] [--reason "..."] [--rule-scope any_rule|specific_rules|all_existing] [--target-rules id1,id2]', run: (a, f) => require('./cli/fp').create(a, f) },
136
- edit: { desc: 'Edit an exclusion rule', usage: 'securenow fp edit <id> [--active true/false] [--conditions \'[...]\']', run: (a, f) => require('./cli/fp').edit(a, f) },
137
- delete: { desc: 'Delete an exclusion rule', usage: 'securenow fp delete <id> [--yes]', run: (a, f) => require('./cli/fp').remove(a, f) },
138
- 'test-body': { desc: 'Test a request body against conditions', usage: 'securenow fp test-body <body|@file> --conditions \'[...]\'', run: (a, f) => require('./cli/fp').testBody(a, f) },
139
- 'dry-run': { desc: 'Dry-run conditions against live traces (last 3 days)', usage: 'securenow fp dry-run --conditions \'[...]\'', run: (a, f) => require('./cli/fp').dryRun(a, f) },
140
- 'ai-fill': { desc: 'AI-generate exclusion conditions', usage: 'securenow fp ai-fill [--description "..."] [--context \'{"method":"POST",...}\']', run: (a, f) => require('./cli/fp').aiFill(a, f) },
141
- mark: { desc: 'Mark an IP as false positive on a notification', usage: 'securenow fp mark <notification-id> <ip> [--conditions \'[...]\'] [--reason "..."] [--rule-scope this_rule|specific_rules|all_existing|any_rule] [--target-rules id1,id2]', run: (a, f) => require('./cli/fp').mark(a, f) },
142
- },
143
- defaultSub: 'list',
144
- },
145
- firewall: {
146
- desc: 'Firewall status and IP testing',
147
- usage: 'securenow firewall <subcommand> [options]',
148
- sub: {
149
- status: { desc: 'Show firewall status, layers, and blocklist info', run: (a, f) => require('./cli/firewall').status(a, f) },
150
- 'test-ip': { desc: 'Check if an IP would be blocked', usage: 'securenow firewall test-ip <ip>', run: (a, f) => require('./cli/firewall').testIp(a, f) },
151
- },
152
- defaultSub: 'status',
153
- },
154
- blocklist: {
155
- desc: 'Manage IP blocklist',
156
- usage: 'securenow blocklist <subcommand> [options]',
157
- sub: {
158
- list: { desc: 'List blocked IPs', run: (a, f) => require('./cli/security').blocklistList(a, f) },
159
- add: { desc: 'Block an IP', usage: 'securenow blocklist add <ip> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistAdd(a, f) },
160
- remove: { desc: 'Unblock an IP', usage: 'securenow blocklist remove <id>', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
161
- stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
162
- },
163
- defaultSub: 'list',
164
- },
165
- allowlist: {
166
- desc: 'Manage IP allowlist (only allow listed IPs)',
167
- usage: 'securenow allowlist <subcommand> [options]',
168
- sub: {
169
- list: { desc: 'List allowed IPs', run: (a, f) => require('./cli/security').allowlistList(a, f) },
170
- add: { desc: 'Allow an IP', usage: 'securenow allowlist add <ip> [--label <label>] [--reason <reason>]', run: (a, f) => require('./cli/security').allowlistAdd(a, f) },
171
- remove: { desc: 'Remove an allowed IP', usage: 'securenow allowlist remove <id>', run: (a, f) => require('./cli/security').allowlistRemove(a, f) },
172
- stats: { desc: 'Allowlist statistics', run: (a, f) => require('./cli/security').allowlistStats(a, f) },
173
- },
174
- defaultSub: 'list',
175
- },
176
- trusted: {
177
- desc: 'Manage trusted IPs',
178
- usage: 'securenow trusted <subcommand> [options]',
179
- sub: {
180
- list: { desc: 'List trusted IPs', run: (a, f) => require('./cli/security').trustedList(a, f) },
181
- add: { desc: 'Add trusted IP', usage: 'securenow trusted add <ip> [--label <label>]', run: (a, f) => require('./cli/security').trustedAdd(a, f) },
182
- remove: { desc: 'Remove trusted IP', usage: 'securenow trusted remove <id>', run: (a, f) => require('./cli/security').trustedRemove(a, f) },
183
- },
184
- defaultSub: 'list',
185
- },
186
- ip: {
187
- desc: 'IP intelligence lookup',
188
- usage: 'securenow ip <ip-address>',
189
- sub: {
190
- lookup: { desc: 'Look up IP intelligence', run: (a, f) => require('./cli/security').ipLookup(a, f) },
191
- traces: { desc: 'Show traces for an IP', usage: 'securenow ip traces <ip>', run: (a, f) => require('./cli/security').ipTraces(a, f) },
192
- },
193
- defaultAction: (a, f) => require('./cli/security').ipLookup(a, f),
194
- },
195
- forensics: {
196
- desc: 'Run forensic queries (natural language → SQL)',
197
- usage: 'securenow forensics <query> [--app <key>]',
198
- sub: {
199
- query: { desc: 'Run a forensic query', flags: { app: 'App key to scope query' }, run: (a, f) => require('./cli/security').forensicsQuery(a, f) },
200
- chat: { desc: 'Interactive forensics chat (scoped to an app)', usage: 'securenow forensics chat --app <key>', flags: { app: 'App key to chat with' }, run: (a, f) => require('./cli/security').forensicsChat(a, f) },
201
- library: { desc: 'View saved queries', run: (a, f) => require('./cli/security').forensicsLibrary(a, f) },
202
- },
203
- defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
204
- },
205
- 'api-map': {
206
- desc: 'View API map',
207
- usage: 'securenow api-map [stats]',
208
- sub: {
209
- list: { desc: 'List discovered API endpoints', run: (a, f) => require('./cli/security').apiMapList(a, f) },
210
- stats: { desc: 'API map statistics', run: (a, f) => require('./cli/security').apiMapStats(a, f) },
211
- },
212
- defaultSub: 'list',
213
- },
214
- instances: {
215
- desc: 'Manage ClickHouse instances',
216
- usage: 'securenow instances <subcommand> [options]',
217
- sub: {
218
- list: { desc: 'List instances', run: (a, f) => require('./cli/security').instancesList(a, f) },
219
- test: { desc: 'Test instance connection', usage: 'securenow instances test <id>', run: (a, f) => require('./cli/security').instancesTest(a, f) },
220
- },
221
- defaultSub: 'list',
222
- },
223
- analytics: {
224
- desc: 'View response analytics',
225
- usage: 'securenow analytics [--app <key>]',
226
- run: (a, f) => require('./cli/security').analytics(a, f),
227
- },
228
- status: {
229
- desc: 'Dashboard overview',
230
- usage: 'securenow status [--app <key>]',
231
- run: (a, f) => require('./cli/monitor').status(a, f),
232
- },
233
- config: {
234
- desc: 'Manage CLI configuration',
235
- usage: 'securenow config <set|get> [key] [value]',
236
- sub: {
237
- set: {
238
- desc: 'Set a config value',
239
- usage: 'securenow config set <key> <value>',
240
- run: (a) => {
241
- const conf = require('./cli/config');
242
- const [key, ...rest] = a;
243
- const value = rest.join(' ');
244
- if (!key || !value) { ui.error('Usage: securenow config set <key> <value>'); process.exit(1); }
245
- conf.setConfigValue(key, value);
246
- ui.success(`${key} = ${value}`);
247
- },
248
- },
249
- get: {
250
- desc: 'Get a config value',
251
- usage: 'securenow config get [key]',
252
- run: (a) => {
253
- const conf = require('./cli/config');
254
- const key = a[0];
255
- if (key) {
256
- const val = conf.getConfigValue(key);
257
- console.log(val != null ? val : ui.c.dim('(not set)'));
258
- } else {
259
- const all = conf.loadConfig();
260
- console.log('');
261
- ui.keyValue(Object.entries(all).map(([k, v]) => [k, v != null ? String(v) : ui.c.dim('(not set)')]));
262
- console.log('');
263
- }
264
- },
265
- },
266
- path: {
267
- desc: 'Show config file path',
268
- run: () => {
269
- const conf = require('./cli/config');
270
- console.log(`Config: ${conf.CONFIG_FILE}`);
271
- console.log(`Credentials: ${conf.CREDENTIALS_FILE}`);
272
- if (conf.hasLocalCredentials()) {
273
- console.log(`Local creds: ${conf.LOCAL_CREDENTIALS_FILE} ${require('./cli/ui').c.green('(active)')}`);
274
- }
275
- console.log(`Auth source: ${conf.getAuthSource()}`);
276
- },
277
- },
278
- },
279
- },
280
- init: {
281
- desc: 'Initialize SecureNow instrumentation',
282
- usage: 'securenow init [--ts|--js] [--src|--root] [--force]',
283
- flags: { typescript: 'Force TypeScript', javascript: 'Force JavaScript', src: 'Create in src/', root: 'Create in root', force: 'Overwrite existing' },
284
- run: (a, f) => require('./cli/init').init(a, f),
285
- },
286
- run: {
287
- desc: 'Run a Node.js app with automatic OTel instrumentation',
288
- usage: 'securenow run [node-flags] <script> [app-args]',
289
- flags: { watch: 'Enable Node.js watch mode', inspect: 'Enable Node.js inspector' },
290
- rawArgv: true,
291
- run: (rawArgs) => require('./cli/run').run(rawArgs),
292
- },
293
- version: {
294
- desc: 'Show CLI version',
295
- run: () => {
296
- try {
297
- const pkg = require('./package.json');
298
- console.log(`securenow v${pkg.version}`);
299
- } catch {
300
- console.log('securenow (version unknown)');
301
- }
302
- },
303
- },
42
+ /**
43
+ * Configuration via .env.local:
44
+ *
45
+ * Required:
46
+ * SECURENOW_APPID=my-nextjs-app
47
+ *
48
+ * Optional:
49
+ * SECURENOW_INSTANCE=http://your-signoz-server:4318
50
+ * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
51
+ * OTEL_LOG_LEVEL=info
52
+ */
53
+ `,
54
+ javascript: `const { registerSecureNow } = require('securenow/nextjs');
55
+
56
+ export function register() {
57
+ registerSecureNow();
58
+ }
59
+
60
+ /**
61
+ * Configuration via .env.local:
62
+ *
63
+ * Required:
64
+ * SECURENOW_APPID=my-nextjs-app
65
+ *
66
+ * Optional:
67
+ * SECURENOW_INSTANCE=http://your-signoz-server:4318
68
+ * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
69
+ * OTEL_LOG_LEVEL=info
70
+ */
71
+ `,
72
+ env: `# SecureNow Configuration
73
+ # Required: Your application identifier
74
+ SECURENOW_APPID=my-nextjs-app
75
+
76
+ # Optional: Your SigNoz/OpenTelemetry collector endpoint
77
+ # Default: https://freetrial.securenow.ai:4318
78
+ SECURENOW_INSTANCE=http://your-signoz-server:4318
79
+
80
+ # Optional: API key or authentication headers
81
+ # OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-api-key-here"
82
+
83
+ # Optional: Log level (debug|info|warn|error)
84
+ # OTEL_LOG_LEVEL=info
85
+ `,
304
86
  };
305
87
 
306
- // ── Help System ──
88
+ function initCommand(args) {
89
+ const flags = parseFlags(args);
90
+ const cwd = process.cwd();
91
+
92
+ console.log('\n🚀 SecureNow Setup\n');
93
+
94
+ // Check if Next.js project
95
+ const isNextJs = isNextJsProject(cwd);
96
+ if (!isNextJs && !flags.force) {
97
+ console.log('⚠️ This doesn\'t appear to be a Next.js project.');
98
+ console.log(' If you want to proceed anyway, use: npx securenow init --force\n');
99
+ process.exit(1);
100
+ }
101
+
102
+ // Determine TypeScript or JavaScript
103
+ const useTypeScript = flags.typescript ||
104
+ (!flags.javascript && fs.existsSync(path.join(cwd, 'tsconfig.json')));
105
+
106
+ // Determine if using src folder
107
+ const useSrc = flags.src ||
108
+ (!flags.root && fs.existsSync(path.join(cwd, 'src')));
109
+
110
+ // Construct file paths
111
+ const fileName = useTypeScript ? 'instrumentation.ts' : 'instrumentation.js';
112
+ const filePath = useSrc
113
+ ? path.join(cwd, 'src', fileName)
114
+ : path.join(cwd, fileName);
115
+
116
+ // Check if file already exists
117
+ if (fs.existsSync(filePath) && !flags.force) {
118
+ console.log(`❌ ${useSrc ? 'src/' : ''}${fileName} already exists`);
119
+ console.log(' Use --force to overwrite\n');
120
+ process.exit(1);
121
+ }
122
+
123
+ // Create instrumentation file
124
+ try {
125
+ const template = useTypeScript ? templates.typescript : templates.javascript;
126
+
127
+ // Ensure src directory exists if needed
128
+ if (useSrc) {
129
+ fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
130
+ }
131
+
132
+ fs.writeFileSync(filePath, template, 'utf8');
133
+ console.log(`✅ Created ${useSrc ? 'src/' : ''}${fileName}`);
134
+ } catch (error) {
135
+ console.error(`❌ Failed to create instrumentation file: ${error.message}\n`);
136
+ process.exit(1);
137
+ }
138
+
139
+ // Create .env.local if it doesn't exist
140
+ const envPath = path.join(cwd, '.env.local');
141
+ if (!fs.existsSync(envPath) || flags.force) {
142
+ try {
143
+ fs.writeFileSync(envPath, templates.env, 'utf8');
144
+ console.log('✅ Created .env.local template');
145
+ } catch (error) {
146
+ console.warn(`⚠️ Could not create .env.local: ${error.message}`);
147
+ }
148
+ } else {
149
+ console.log('ℹ️ .env.local already exists (skipped)');
150
+ }
151
+
152
+ // Success message
153
+ console.log('\n┌─────────────────────────────────────────────────┐');
154
+ console.log('│ 🎉 Setup Complete! │');
155
+ console.log('│ │');
156
+ console.log('│ Next steps: │');
157
+ console.log('│ │');
158
+ console.log('│ 1. Edit .env.local and configure: │');
159
+ console.log('│ SECURENOW_APPID=your-app-name │');
160
+ console.log('│ SECURENOW_INSTANCE=http://signoz:4318 │');
161
+ console.log('│ │');
162
+ console.log('│ 2. Start your Next.js app: npm run dev │');
163
+ console.log('│ │');
164
+ console.log('│ 3. Check SigNoz dashboard for traces! │');
165
+ console.log('│ │');
166
+ console.log('│ 📚 Documentation: │');
167
+ console.log('│ node_modules/securenow/NEXTJS-GUIDE.md │');
168
+ console.log('└─────────────────────────────────────────────────┘\n');
169
+ }
170
+
171
+ // ─── Auth / config ──────────────────────────────────────────────
307
172
 
308
- function showBanner() {
309
- console.log('');
310
- console.log(` ${ui.c.bold(ui.c.cyan('SecureNow CLI'))} ${ui.c.dim('— Security observability from the terminal')}`);
311
- console.log('');
173
+ function loadConfig() {
174
+ try {
175
+ if (!fs.existsSync(CONFIG_FILE)) return {};
176
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
177
+ } catch {
178
+ return {};
179
+ }
312
180
  }
313
181
 
314
- function showHelp(commandName) {
315
- if (commandName && COMMANDS[commandName]) {
316
- const cmd = COMMANDS[commandName];
317
- showBanner();
318
- console.log(` ${ui.c.bold(commandName)} ${cmd.desc}`);
319
- console.log('');
320
- if (cmd.usage) {
321
- console.log(` ${ui.c.bold('USAGE')}`);
322
- console.log(` ${cmd.usage}`);
323
- console.log('');
324
- }
325
- if (cmd.sub) {
326
- console.log(` ${ui.c.bold('SUBCOMMANDS')}`);
327
- const entries = Object.entries(cmd.sub);
328
- const maxLen = entries.reduce((m, [k]) => Math.max(m, k.length), 0);
329
- for (const [name, sub] of entries) {
330
- console.log(` ${ui.c.cyan(name.padEnd(maxLen + 2))} ${sub.desc}`);
331
- }
332
- console.log('');
182
+ function saveConfig(cfg) {
183
+ try {
184
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
185
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), { mode: 0o600 });
186
+ try { fs.chmodSync(CONFIG_FILE, 0o600); } catch {}
187
+ } catch (err) {
188
+ console.error(`❌ Could not write ${CONFIG_FILE}: ${err.message}`);
189
+ process.exit(1);
190
+ }
191
+ }
192
+
193
+ function openBrowser(url) {
194
+ const cmd =
195
+ process.platform === 'darwin' ? `open "${url}"` :
196
+ process.platform === 'win32' ? `start "" "${url}"` :
197
+ `xdg-open "${url}"`;
198
+ exec(cmd, () => {});
199
+ }
200
+
201
+ function loginCommand(args) {
202
+ const flags = parseFlags(args);
203
+ const state = crypto.randomBytes(16).toString('hex');
204
+
205
+ const server = http.createServer((req, res) => {
206
+ const url = new URL(req.url, 'http://127.0.0.1');
207
+ if (url.pathname !== '/callback') {
208
+ res.writeHead(404); res.end();
209
+ return;
333
210
  }
334
- if (cmd.flags) {
335
- console.log(` ${ui.c.bold('FLAGS')}`);
336
- for (const [flag, desc] of Object.entries(cmd.flags)) {
337
- console.log(` --${ui.c.cyan(flag.padEnd(16))} ${desc}`);
338
- }
339
- console.log('');
211
+ const token = url.searchParams.get('token');
212
+ const gotState = url.searchParams.get('state');
213
+ if (!token || gotState !== state) {
214
+ res.writeHead(400, { 'Content-Type': 'text/html' });
215
+ res.end('<h1>Auth failed</h1><p>You can close this tab and try again.</p>');
216
+ return;
340
217
  }
341
- console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
342
- console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
343
- console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help`);
344
- console.log('');
345
- return;
218
+ saveConfig({ token, savedAt: new Date().toISOString() });
219
+ res.writeHead(200, { 'Content-Type': 'text/html' });
220
+ res.end('<h1>✅ Logged in to SecureNow</h1><p>You can close this tab and return to your terminal.</p>');
221
+ console.log('\n✅ Logged in. Token saved to', CONFIG_FILE, '\n');
222
+ setTimeout(() => { server.close(); process.exit(0); }, 200);
223
+ });
224
+
225
+ server.listen(0, '127.0.0.1', () => {
226
+ const port = server.address().port;
227
+ const callback = `http://127.0.0.1:${port}/callback`;
228
+ const authUrl = `${APP_URL}/cli/auth?callback=${encodeURIComponent(callback)}&state=${state}${flags.force ? '&force_login=1' : ''}`;
229
+ console.log('\n🔐 SecureNow Login\n');
230
+ console.log('Opening browser to authenticate...');
231
+ console.log('If it doesn\'t open, paste this URL:\n ' + authUrl + '\n');
232
+ openBrowser(authUrl);
233
+ });
234
+
235
+ setTimeout(() => {
236
+ console.error('\n⏱ Login timed out after 5 minutes.');
237
+ try { server.close(); } catch {}
238
+ process.exit(1);
239
+ }, 5 * 60 * 1000).unref();
240
+ }
241
+
242
+ function logoutCommand() {
243
+ if (fs.existsSync(CONFIG_FILE)) {
244
+ try { fs.unlinkSync(CONFIG_FILE); } catch {}
346
245
  }
246
+ console.log('✅ Logged out');
247
+ }
347
248
 
348
- showBanner();
349
-
350
- const groups = {
351
- 'Run': ['run'],
352
- 'Authentication': ['login', 'logout', 'whoami'],
353
- 'Applications': ['apps', 'init', 'status'],
354
- 'Observe': ['traces', 'logs', 'analytics'],
355
- 'Detect & Respond': ['notifications', 'alerts', 'fp'],
356
- 'Investigate': ['ip', 'forensics', 'api-map'],
357
- 'Firewall': ['firewall'],
358
- 'Remediation': ['blocklist', 'allowlist', 'trusted'],
359
- 'Settings': ['instances', 'config', 'version'],
360
- };
361
-
362
- for (const [group, cmds] of Object.entries(groups)) {
363
- console.log(` ${ui.c.bold(group)}`);
364
- for (const name of cmds) {
365
- const cmd = COMMANDS[name];
366
- if (cmd) {
367
- console.log(` ${ui.c.cyan(name.padEnd(18))} ${cmd.desc}`);
368
- }
369
- }
370
- console.log('');
249
+ function statusCommand() {
250
+ const cfg = loadConfig();
251
+ if (!cfg.token) {
252
+ console.log('❌ Not logged in. Run: npx securenow login');
253
+ process.exit(1);
371
254
  }
255
+ console.log('✅ Logged in');
256
+ console.log(' Config: ' + CONFIG_FILE);
257
+ console.log(' API: ' + API_URL);
258
+ apiRequest('GET', '/api/applications', null, cfg.token)
259
+ .then((data) => {
260
+ const count = (data && (data.applications || data)) ? (data.applications || data).length : 0;
261
+ console.log(' Apps: ' + count);
262
+ })
263
+ .catch((err) => {
264
+ console.log(' Apps: (could not reach API: ' + err.message + ')');
265
+ process.exit(1);
266
+ });
267
+ }
268
+
269
+ // ─── HTTP helper ────────────────────────────────────────────────
372
270
 
373
- console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
374
- console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
375
- console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help for a command`);
376
- console.log('');
377
- console.log(` ${ui.c.dim('Run')} securenow help <command> ${ui.c.dim('for detailed usage')}`);
378
- console.log('');
271
+ function apiRequest(method, pathname, body, token) {
272
+ return new Promise((resolve, reject) => {
273
+ const u = new URL(API_URL + pathname);
274
+ const lib = u.protocol === 'https:' ? https : http;
275
+ const data = body ? JSON.stringify(body) : null;
276
+ const req = lib.request({
277
+ method,
278
+ hostname: u.hostname,
279
+ port: u.port || (u.protocol === 'https:' ? 443 : 80),
280
+ path: u.pathname + u.search,
281
+ headers: {
282
+ 'Content-Type': 'application/json',
283
+ 'Accept': 'application/json',
284
+ 'User-Agent': 'securenow-cli',
285
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
286
+ ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
287
+ },
288
+ }, (res) => {
289
+ let chunks = '';
290
+ res.on('data', (c) => { chunks += c; });
291
+ res.on('end', () => {
292
+ if (res.statusCode === 401) return reject(new Error('Unauthorized — run: npx securenow login'));
293
+ if (res.statusCode >= 400) {
294
+ let msg = `HTTP ${res.statusCode}`;
295
+ try { const j = JSON.parse(chunks); if (j.error) msg = j.error; } catch {}
296
+ return reject(new Error(msg));
297
+ }
298
+ try { resolve(chunks ? JSON.parse(chunks) : {}); }
299
+ catch { resolve({}); }
300
+ });
301
+ });
302
+ req.on('error', reject);
303
+ if (data) req.write(data);
304
+ req.end();
305
+ });
379
306
  }
380
307
 
381
- // ── Main Router ──
308
+ // ─── apps ───────────────────────────────────────────────────────
382
309
 
383
- async function main() {
384
- const { positional, flags } = parseArgs(process.argv.slice(2));
385
- const cmdName = positional[0] || 'help';
310
+ function appsCommand(args) {
311
+ const sub = args[0];
312
+ const rest = args.slice(1);
313
+ if (sub === 'list' || !sub) return appsList(rest);
314
+ if (sub === 'create' || sub === 'add') return appsCreate(rest);
315
+ console.error('Unknown apps subcommand. Try: list, create');
316
+ process.exit(1);
317
+ }
386
318
 
387
- if (cmdName === 'help' || flags.help) {
388
- showHelp(flags.help === true ? positional[0] : flags.help || positional[1] || (cmdName !== 'help' ? cmdName : null));
389
- return;
319
+ function requireAuth() {
320
+ const cfg = loadConfig();
321
+ if (!cfg.token) {
322
+ console.error('❌ Not logged in. Run: npx securenow login');
323
+ process.exit(1);
390
324
  }
325
+ return cfg.token;
326
+ }
391
327
 
392
- const cmd = COMMANDS[cmdName];
393
- if (!cmd) {
394
- // Auto-detect: if the first arg looks like a file path, treat it as `securenow run <file>`
395
- if (/\.(m?[jt]sx?|cjs)$/.test(cmdName) || cmdName.includes('/') || cmdName.includes('\\')) {
396
- return COMMANDS.run.run(process.argv.slice(2));
328
+ async function appsList(args) {
329
+ const json = args.includes('--json');
330
+ const token = requireAuth();
331
+ try {
332
+ const data = await apiRequest('GET', '/api/applications', null, token);
333
+ const apps = data.applications || data || [];
334
+ if (json) { console.log(JSON.stringify(apps, null, 2)); return; }
335
+ if (!apps.length) { console.log('(no applications yet — npx securenow apps create)'); return; }
336
+ console.log('\nApplications:\n');
337
+ for (const a of apps) {
338
+ console.log(` • ${a.name}${a.key ? ' ' + a.key : ''}`);
339
+ if (a.hosts && a.hosts.length) console.log(` hosts: ${a.hosts.join(', ')}`);
397
340
  }
398
- ui.error(`Unknown command: ${cmdName}`);
399
- ui.info('Run `securenow help` for a list of commands.');
341
+ console.log('');
342
+ } catch (err) {
343
+ console.error('❌ ' + err.message);
400
344
  process.exit(1);
401
345
  }
346
+ }
402
347
 
403
- if (cmd.rawArgv) {
404
- // Pass raw argv (everything after the command name) so the command can
405
- // forward flags like --watch, --inspect verbatim to child processes.
406
- const cmdIdx = process.argv.indexOf(cmdName);
407
- const rawArgs = cmdIdx !== -1 ? process.argv.slice(cmdIdx + 1) : positional.slice(1);
408
- await cmd.run(rawArgs);
409
- return;
410
- }
348
+ function prompt(question) {
349
+ return new Promise((resolve) => {
350
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
351
+ rl.question(question, (a) => { rl.close(); resolve(a.trim()); });
352
+ });
353
+ }
411
354
 
412
- if (cmd.run && !cmd.sub) {
413
- await cmd.run(positional.slice(1), flags);
414
- return;
355
+ async function appsCreate(args) {
356
+ const token = requireAuth();
357
+ const flags = {};
358
+ for (let i = 0; i < args.length; i++) {
359
+ if (args[i] === '--name') flags.name = args[++i];
360
+ else if (args[i] === '--hosts') flags.hosts = args[++i];
361
+ }
362
+ const name = flags.name || await prompt('Application name: ');
363
+ if (!name) { console.error('❌ Name is required'); process.exit(1); }
364
+ const hostsStr = flags.hosts != null ? flags.hosts : await prompt('Hosts (comma-separated, optional): ');
365
+ const hosts = hostsStr ? hostsStr.split(',').map(s => s.trim()).filter(Boolean) : [];
366
+ try {
367
+ const data = await apiRequest('POST', '/api/applications', { name, hosts }, token);
368
+ const app = data.application || data;
369
+ console.log('\n✅ Created application: ' + (app.name || name));
370
+ if (app.key) console.log(' Key: ' + app.key);
371
+ console.log('\nNext: add SECURENOW_APPID=' + (app.name || name) + ' to your .env.local\n');
372
+ } catch (err) {
373
+ console.error('❌ ' + err.message);
374
+ process.exit(1);
415
375
  }
376
+ }
416
377
 
417
- if (cmd.sub) {
418
- let subName = positional[1];
419
- let subArgs = positional.slice(2);
378
+ function helpCommand() {
379
+ console.log(`
380
+ SecureNow CLI - OpenTelemetry instrumentation for Next.js
420
381
 
421
- if (subName && cmd.sub[subName]) {
422
- if (flags.help) {
423
- showHelp(cmdName);
424
- return;
425
- }
426
- await cmd.sub[subName].run(subArgs, flags);
427
- return;
428
- }
382
+ USAGE:
383
+ npx securenow <command> [options]
429
384
 
430
- if (cmd.defaultAction) {
431
- const allArgs = subName ? [subName, ...subArgs] : [];
432
- await cmd.defaultAction(allArgs, flags);
433
- return;
434
- }
385
+ COMMANDS:
386
+ login Authenticate with SecureNow (opens browser)
387
+ logout Remove saved credentials
388
+ status Show current auth + API connectivity
389
+ init Scaffold instrumentation in your Next.js project
390
+ apps list List your applications
391
+ apps create Create a new application (interactive)
392
+ help Show this help message
393
+ version Show package version
435
394
 
436
- if (cmd.defaultSub) {
437
- const allArgs = subName ? [subName, ...subArgs] : [];
438
- await cmd.sub[cmd.defaultSub].run(allArgs, flags);
439
- return;
440
- }
395
+ OPTIONS:
396
+ --typescript, --ts Force TypeScript (creates instrumentation.ts)
397
+ --javascript, --js Force JavaScript (creates instrumentation.js)
398
+ --src Create file in src/ folder
399
+ --root Create file in project root
400
+ --force, -f Overwrite existing files
401
+
402
+ EXAMPLES:
403
+ # First-time setup
404
+ npx securenow login
405
+ npx securenow apps create --name my-app
406
+ npx securenow init
441
407
 
442
- showHelp(cmdName);
443
- return;
408
+ # Auto-detect and setup
409
+ npx securenow init
410
+
411
+ # Force TypeScript in src folder
412
+ npx securenow init --typescript --src
413
+
414
+ # Force JavaScript in root
415
+ npx securenow init --javascript --root
416
+
417
+ # Overwrite existing files
418
+ npx securenow init --force
419
+
420
+ DOCUMENTATION:
421
+ Quick Start: node_modules/securenow/NEXTJS-QUICKSTART.md
422
+ Full Guide: node_modules/securenow/NEXTJS-GUIDE.md
423
+ Examples: node_modules/securenow/examples/
424
+ `);
425
+ }
426
+
427
+ function versionCommand() {
428
+ try {
429
+ const packageJson = require('./package.json');
430
+ console.log(`securenow v${packageJson.version}`);
431
+ } catch (error) {
432
+ console.log('securenow (version unknown)');
444
433
  }
434
+ }
445
435
 
446
- if (cmd.run) {
447
- await cmd.run(positional.slice(1), flags);
436
+ // Utility functions
437
+ function parseFlags(args) {
438
+ const flags = {};
439
+
440
+ for (let i = 0; i < args.length; i++) {
441
+ const arg = args[i];
442
+
443
+ if (arg === '--typescript' || arg === '--ts') {
444
+ flags.typescript = true;
445
+ } else if (arg === '--javascript' || arg === '--js') {
446
+ flags.javascript = true;
447
+ } else if (arg === '--src') {
448
+ flags.src = true;
449
+ } else if (arg === '--root') {
450
+ flags.root = true;
451
+ } else if (arg === '--force' || arg === '-f') {
452
+ flags.force = true;
453
+ }
448
454
  }
455
+
456
+ return flags;
449
457
  }
450
458
 
451
- main().catch((err) => {
452
- if (err.name !== 'CLIError') {
453
- ui.error(err.message || 'An unexpected error occurred');
459
+ function isNextJsProject(dir) {
460
+ try {
461
+ const packageJsonPath = path.join(dir, 'package.json');
462
+ if (!fs.existsSync(packageJsonPath)) return false;
463
+
464
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
465
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
466
+
467
+ return !!deps.next;
468
+ } catch (error) {
469
+ return false;
454
470
  }
455
- if (process.env.SECURENOW_DEBUG) {
456
- console.error(err.stack || err);
471
+ }
472
+
473
+ // Main
474
+ function main() {
475
+ const args = process.argv.slice(2);
476
+ const command = args[0] || 'help';
477
+ const commandFn = commands[command];
478
+
479
+ if (!commandFn) {
480
+ console.error(`Unknown command: ${command}`);
481
+ console.log('Run "npx securenow help" for usage\n');
482
+ process.exit(1);
457
483
  }
458
- process.exit(1);
459
- });
484
+
485
+ commandFn(args.slice(1));
486
+ }
487
+
488
+ if (require.main === module) {
489
+ main();
490
+ }
491
+
492
+ module.exports = { main };
493
+
494
+
495
+
496
+
497
+
498
+
499
+