securenow 5.2.2 → 5.3.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/cli.js CHANGED
@@ -1,267 +1,389 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- /**
5
- * SecureNow CLI
6
- *
7
- * Usage: npx securenow init [options]
8
- */
4
+ const ui = require('./cli/ui');
9
5
 
10
- const fs = require('fs');
11
- const path = require('path');
6
+ // ── Argument Parser ──
12
7
 
13
- const commands = {
14
- init: initCommand,
15
- help: helpCommand,
16
- version: versionCommand,
17
- };
18
-
19
- // Templates
20
- const templates = {
21
- typescript: `import { registerSecureNow } from 'securenow/nextjs';
22
-
23
- export function register() {
24
- registerSecureNow();
25
- }
8
+ function parseArgs(argv) {
9
+ const positional = [];
10
+ const flags = {};
26
11
 
27
- /**
28
- * Configuration via .env.local:
29
- *
30
- * Required:
31
- * SECURENOW_APPID=my-nextjs-app
32
- *
33
- * Optional:
34
- * SECURENOW_INSTANCE=http://your-otlp-backend:4318
35
- * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
36
- * OTEL_LOG_LEVEL=info
37
- */
38
- `,
39
- javascript: `const { registerSecureNow } = require('securenow/nextjs');
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
+ }
40
35
 
41
- export function register() {
42
- registerSecureNow();
36
+ return { positional, flags };
43
37
  }
44
38
 
45
- /**
46
- * Configuration via .env.local:
47
- *
48
- * Required:
49
- * SECURENOW_APPID=my-nextjs-app
50
- *
51
- * Optional:
52
- * SECURENOW_INSTANCE=http://your-otlp-backend:4318
53
- * OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-key"
54
- * OTEL_LOG_LEVEL=info
55
- */
56
- `,
57
- env: `# SecureNow Configuration
58
- # Required: Your application identifier
59
- SECURENOW_APPID=my-nextjs-app
60
-
61
- # Optional: Your OTLP-compatible backend / collector endpoint
62
- # Default: https://freetrial.securenow.ai:4318
63
- SECURENOW_INSTANCE=http://your-otlp-backend:4318
39
+ // ── Command Registry ──
40
+
41
+ const COMMANDS = {
42
+ login: {
43
+ desc: 'Authenticate with SecureNow',
44
+ usage: 'securenow login [--token <TOKEN>]',
45
+ flags: { token: 'Authenticate with a token directly' },
46
+ run: (a, f) => require('./cli/auth').login(a, f),
47
+ },
48
+ logout: {
49
+ desc: 'Clear stored credentials',
50
+ usage: 'securenow logout',
51
+ run: () => require('./cli/auth').logout(),
52
+ },
53
+ whoami: {
54
+ desc: 'Show current session info',
55
+ usage: 'securenow whoami',
56
+ run: () => require('./cli/auth').whoami(),
57
+ },
58
+ apps: {
59
+ desc: 'Manage applications',
60
+ usage: 'securenow apps <subcommand> [options]',
61
+ sub: {
62
+ list: { desc: 'List all applications', run: (a, f) => require('./cli/apps').list(a, f) },
63
+ create: { desc: 'Create a new application', usage: 'securenow apps create <name> [--hosts host1,host2] [--instance <id>]', run: (a, f) => require('./cli/apps').create(a, f) },
64
+ info: { desc: 'Show application details', usage: 'securenow apps info <id>', run: (a, f) => require('./cli/apps').info(a, f) },
65
+ delete: { desc: 'Delete an application', usage: 'securenow apps delete <id> [--force]', run: (a, f) => require('./cli/apps').remove(a, f) },
66
+ default: { desc: 'Set default application', usage: 'securenow apps default <key>', run: (a, f) => require('./cli/apps').setDefault(a, f) },
67
+ },
68
+ defaultSub: 'list',
69
+ },
70
+ traces: {
71
+ desc: 'View and analyze traces',
72
+ usage: 'securenow traces <subcommand> [options]',
73
+ sub: {
74
+ 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) },
75
+ show: { desc: 'Show trace details', usage: 'securenow traces show <traceId>', run: (a, f) => require('./cli/monitor').tracesShow(a, f) },
76
+ analyze: { desc: 'AI-analyze a trace', usage: 'securenow traces analyze <traceId>', run: (a, f) => require('./cli/monitor').tracesAnalyze(a, f) },
77
+ },
78
+ defaultSub: 'list',
79
+ },
80
+ logs: {
81
+ desc: 'View application logs',
82
+ usage: 'securenow logs [options]',
83
+ sub: {
84
+ 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) },
85
+ trace: { desc: 'Show logs for a trace', usage: 'securenow logs trace <traceId>', run: (a, f) => require('./cli/monitor').logsTrace(a, f) },
86
+ },
87
+ defaultSub: 'list',
88
+ },
89
+ issues: {
90
+ desc: 'Manage security issues',
91
+ usage: 'securenow issues <subcommand> [options]',
92
+ sub: {
93
+ list: { desc: 'List issues', flags: { app: 'App key', status: 'Filter by status' }, run: (a, f) => require('./cli/monitor').issuesList(a, f) },
94
+ show: { desc: 'Show issue details', usage: 'securenow issues show <id>', run: (a, f) => require('./cli/monitor').issuesShow(a, f) },
95
+ resolve: { desc: 'Resolve an issue', usage: 'securenow issues resolve <id>', run: (a, f) => require('./cli/monitor').issuesResolve(a, f) },
96
+ },
97
+ defaultSub: 'list',
98
+ },
99
+ notifications: {
100
+ desc: 'Manage notifications',
101
+ usage: 'securenow notifications <subcommand> [options]',
102
+ sub: {
103
+ list: { desc: 'List notifications', flags: { limit: 'Max results', page: 'Page number' }, run: (a, f) => require('./cli/monitor').notificationsList(a, f) },
104
+ read: { desc: 'Mark notification as read', usage: 'securenow notifications read <id>', run: (a, f) => require('./cli/monitor').notificationsRead(a, f) },
105
+ 'read-all': { desc: 'Mark all as read', run: () => require('./cli/monitor').notificationsReadAll() },
106
+ unread: { desc: 'Show unread count', run: () => require('./cli/monitor').notificationsUnread() },
107
+ },
108
+ defaultSub: 'list',
109
+ },
110
+ alerts: {
111
+ desc: 'Manage alerting',
112
+ usage: 'securenow alerts <subcommand> [options]',
113
+ sub: {
114
+ rules: { desc: 'List alert rules', run: (a, f) => require('./cli/security').alertRulesList(a, f) },
115
+ channels: { desc: 'List alert channels', run: (a, f) => require('./cli/security').alertChannelsList(a, f) },
116
+ history: { desc: 'View alert history', flags: { limit: 'Max results' }, run: (a, f) => require('./cli/security').alertHistoryList(a, f) },
117
+ },
118
+ defaultSub: 'rules',
119
+ },
120
+ blocklist: {
121
+ desc: 'Manage IP blocklist',
122
+ usage: 'securenow blocklist <subcommand> [options]',
123
+ sub: {
124
+ list: { desc: 'List blocked IPs', run: (a, f) => require('./cli/security').blocklistList(a, f) },
125
+ add: { desc: 'Block an IP', usage: 'securenow blocklist add <ip> [--reason <reason>]', run: (a, f) => require('./cli/security').blocklistAdd(a, f) },
126
+ remove: { desc: 'Unblock an IP', usage: 'securenow blocklist remove <id>', run: (a, f) => require('./cli/security').blocklistRemove(a, f) },
127
+ stats: { desc: 'Blocklist statistics', run: (a, f) => require('./cli/security').blocklistStats(a, f) },
128
+ },
129
+ defaultSub: 'list',
130
+ },
131
+ trusted: {
132
+ desc: 'Manage trusted IPs',
133
+ usage: 'securenow trusted <subcommand> [options]',
134
+ sub: {
135
+ list: { desc: 'List trusted IPs', run: (a, f) => require('./cli/security').trustedList(a, f) },
136
+ add: { desc: 'Add trusted IP', usage: 'securenow trusted add <ip> [--label <label>]', run: (a, f) => require('./cli/security').trustedAdd(a, f) },
137
+ remove: { desc: 'Remove trusted IP', usage: 'securenow trusted remove <id>', run: (a, f) => require('./cli/security').trustedRemove(a, f) },
138
+ },
139
+ defaultSub: 'list',
140
+ },
141
+ ip: {
142
+ desc: 'IP intelligence lookup',
143
+ usage: 'securenow ip <ip-address>',
144
+ sub: {
145
+ lookup: { desc: 'Look up IP intelligence', run: (a, f) => require('./cli/security').ipLookup(a, f) },
146
+ traces: { desc: 'Show traces for an IP', usage: 'securenow ip traces <ip>', run: (a, f) => require('./cli/security').ipTraces(a, f) },
147
+ },
148
+ defaultAction: (a, f) => require('./cli/security').ipLookup(a, f),
149
+ },
150
+ forensics: {
151
+ desc: 'Run forensic queries (natural language → SQL)',
152
+ usage: 'securenow forensics <query>',
153
+ sub: {
154
+ query: { desc: 'Run a forensic query', run: (a, f) => require('./cli/security').forensicsQuery(a, f) },
155
+ library: { desc: 'View saved queries', run: (a, f) => require('./cli/security').forensicsLibrary(a, f) },
156
+ },
157
+ defaultAction: (a, f) => require('./cli/security').forensicsQuery(a, f),
158
+ },
159
+ 'api-map': {
160
+ desc: 'View API map',
161
+ usage: 'securenow api-map [stats]',
162
+ sub: {
163
+ list: { desc: 'List discovered API endpoints', run: (a, f) => require('./cli/security').apiMapList(a, f) },
164
+ stats: { desc: 'API map statistics', run: (a, f) => require('./cli/security').apiMapStats(a, f) },
165
+ },
166
+ defaultSub: 'list',
167
+ },
168
+ instances: {
169
+ desc: 'Manage ClickHouse instances',
170
+ usage: 'securenow instances <subcommand> [options]',
171
+ sub: {
172
+ list: { desc: 'List instances', run: (a, f) => require('./cli/security').instancesList(a, f) },
173
+ test: { desc: 'Test instance connection', usage: 'securenow instances test <id>', run: (a, f) => require('./cli/security').instancesTest(a, f) },
174
+ },
175
+ defaultSub: 'list',
176
+ },
177
+ analytics: {
178
+ desc: 'View response analytics',
179
+ usage: 'securenow analytics [--app <key>]',
180
+ run: (a, f) => require('./cli/security').analytics(a, f),
181
+ },
182
+ status: {
183
+ desc: 'Dashboard overview',
184
+ usage: 'securenow status [--app <key>]',
185
+ run: (a, f) => require('./cli/monitor').status(a, f),
186
+ },
187
+ config: {
188
+ desc: 'Manage CLI configuration',
189
+ usage: 'securenow config <set|get> [key] [value]',
190
+ sub: {
191
+ set: {
192
+ desc: 'Set a config value',
193
+ usage: 'securenow config set <key> <value>',
194
+ run: (a) => {
195
+ const conf = require('./cli/config');
196
+ const [key, ...rest] = a;
197
+ const value = rest.join(' ');
198
+ if (!key || !value) { ui.error('Usage: securenow config set <key> <value>'); process.exit(1); }
199
+ conf.setConfigValue(key, value);
200
+ ui.success(`${key} = ${value}`);
201
+ },
202
+ },
203
+ get: {
204
+ desc: 'Get a config value',
205
+ usage: 'securenow config get [key]',
206
+ run: (a) => {
207
+ const conf = require('./cli/config');
208
+ const key = a[0];
209
+ if (key) {
210
+ const val = conf.getConfigValue(key);
211
+ console.log(val != null ? val : ui.c.dim('(not set)'));
212
+ } else {
213
+ const all = conf.loadConfig();
214
+ console.log('');
215
+ ui.keyValue(Object.entries(all).map(([k, v]) => [k, v != null ? String(v) : ui.c.dim('(not set)')]));
216
+ console.log('');
217
+ }
218
+ },
219
+ },
220
+ path: {
221
+ desc: 'Show config file path',
222
+ run: () => {
223
+ const conf = require('./cli/config');
224
+ console.log(`Config: ${conf.CONFIG_FILE}`);
225
+ console.log(`Credentials: ${conf.CREDENTIALS_FILE}`);
226
+ },
227
+ },
228
+ },
229
+ },
230
+ init: {
231
+ desc: 'Initialize SecureNow instrumentation',
232
+ usage: 'securenow init [--ts|--js] [--src|--root] [--force]',
233
+ flags: { typescript: 'Force TypeScript', javascript: 'Force JavaScript', src: 'Create in src/', root: 'Create in root', force: 'Overwrite existing' },
234
+ run: (a, f) => require('./cli/init').init(a, f),
235
+ },
236
+ version: {
237
+ desc: 'Show CLI version',
238
+ run: () => {
239
+ try {
240
+ const pkg = require('./package.json');
241
+ console.log(`securenow v${pkg.version}`);
242
+ } catch {
243
+ console.log('securenow (version unknown)');
244
+ }
245
+ },
246
+ },
247
+ };
64
248
 
65
- # Optional: API key or authentication headers
66
- # OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-api-key-here"
249
+ // ── Help System ──
67
250
 
68
- # Optional: Log level (debug|info|warn|error)
69
- # OTEL_LOG_LEVEL=info
70
- `,
71
- };
251
+ function showBanner() {
252
+ console.log('');
253
+ console.log(` ${ui.c.bold(ui.c.cyan('SecureNow CLI'))} ${ui.c.dim('— Security observability from the terminal')}`);
254
+ console.log('');
255
+ }
72
256
 
73
- function initCommand(args) {
74
- const flags = parseFlags(args);
75
- const cwd = process.cwd();
76
-
77
- console.log('\n🚀 SecureNow Setup\n');
78
-
79
- // Check if Next.js project
80
- const isNextJs = isNextJsProject(cwd);
81
- if (!isNextJs && !flags.force) {
82
- console.log('⚠️ This doesn\'t appear to be a Next.js project.');
83
- console.log(' If you want to proceed anyway, use: npx securenow init --force\n');
84
- process.exit(1);
85
- }
86
-
87
- // Determine TypeScript or JavaScript
88
- const useTypeScript = flags.typescript ||
89
- (!flags.javascript && fs.existsSync(path.join(cwd, 'tsconfig.json')));
90
-
91
- // Determine if using src folder
92
- const useSrc = flags.src ||
93
- (!flags.root && fs.existsSync(path.join(cwd, 'src')));
94
-
95
- // Construct file paths
96
- const fileName = useTypeScript ? 'instrumentation.ts' : 'instrumentation.js';
97
- const filePath = useSrc
98
- ? path.join(cwd, 'src', fileName)
99
- : path.join(cwd, fileName);
100
-
101
- // Check if file already exists
102
- if (fs.existsSync(filePath) && !flags.force) {
103
- console.log(`❌ ${useSrc ? 'src/' : ''}${fileName} already exists`);
104
- console.log(' Use --force to overwrite\n');
105
- process.exit(1);
106
- }
107
-
108
- // Create instrumentation file
109
- try {
110
- const template = useTypeScript ? templates.typescript : templates.javascript;
111
-
112
- // Ensure src directory exists if needed
113
- if (useSrc) {
114
- fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
257
+ function showHelp(commandName) {
258
+ if (commandName && COMMANDS[commandName]) {
259
+ const cmd = COMMANDS[commandName];
260
+ showBanner();
261
+ console.log(` ${ui.c.bold(commandName)} ${cmd.desc}`);
262
+ console.log('');
263
+ if (cmd.usage) {
264
+ console.log(` ${ui.c.bold('USAGE')}`);
265
+ console.log(` ${cmd.usage}`);
266
+ console.log('');
115
267
  }
116
-
117
- fs.writeFileSync(filePath, template, 'utf8');
118
- console.log(`✅ Created ${useSrc ? 'src/' : ''}${fileName}`);
119
- } catch (error) {
120
- console.error(`❌ Failed to create instrumentation file: ${error.message}\n`);
121
- process.exit(1);
122
- }
123
-
124
- // Create .env.local if it doesn't exist
125
- const envPath = path.join(cwd, '.env.local');
126
- if (!fs.existsSync(envPath) || flags.force) {
127
- try {
128
- fs.writeFileSync(envPath, templates.env, 'utf8');
129
- console.log('✅ Created .env.local template');
130
- } catch (error) {
131
- console.warn(`⚠️ Could not create .env.local: ${error.message}`);
268
+ if (cmd.sub) {
269
+ console.log(` ${ui.c.bold('SUBCOMMANDS')}`);
270
+ const entries = Object.entries(cmd.sub);
271
+ const maxLen = entries.reduce((m, [k]) => Math.max(m, k.length), 0);
272
+ for (const [name, sub] of entries) {
273
+ console.log(` ${ui.c.cyan(name.padEnd(maxLen + 2))} ${sub.desc}`);
274
+ }
275
+ console.log('');
132
276
  }
133
- } else {
134
- console.log('ℹ️ .env.local already exists (skipped)');
277
+ if (cmd.flags) {
278
+ console.log(` ${ui.c.bold('FLAGS')}`);
279
+ for (const [flag, desc] of Object.entries(cmd.flags)) {
280
+ console.log(` --${ui.c.cyan(flag.padEnd(16))} ${desc}`);
281
+ }
282
+ console.log('');
283
+ }
284
+ console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
285
+ console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
286
+ console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help`);
287
+ console.log('');
288
+ return;
135
289
  }
136
-
137
- // Success message
138
- console.log('\n┌─────────────────────────────────────────────────┐');
139
- console.log('│ 🎉 Setup Complete! │');
140
- console.log('│ │');
141
- console.log('│ Next steps: │');
142
- console.log('│ │');
143
- console.log('│ 1. Edit .env.local and configure: │');
144
- console.log('│ SECURENOW_APPID=your-app-name │');
145
- console.log('│ SECURENOW_INSTANCE=http://your-otlp-backend:4318 │');
146
- console.log('│ │');
147
- console.log('│ 2. Start your Next.js app: npm run dev │');
148
- console.log('│ │');
149
- console.log('│ 3. Check SecureNow dashboard for traces! │');
150
- console.log('│ │');
151
- console.log('│ 📚 Documentation: │');
152
- console.log('│ node_modules/securenow/NEXTJS-GUIDE.md │');
153
- console.log('└─────────────────────────────────────────────────┘\n');
154
- }
155
-
156
- function helpCommand() {
157
- console.log(`
158
- SecureNow CLI - OpenTelemetry instrumentation for Next.js
159
-
160
- USAGE:
161
- npx securenow <command> [options]
162
-
163
- COMMANDS:
164
- init Initialize SecureNow in your Next.js project
165
- help Show this help message
166
- version Show package version
167
-
168
- OPTIONS:
169
- --typescript, --ts Force TypeScript (creates instrumentation.ts)
170
- --javascript, --js Force JavaScript (creates instrumentation.js)
171
- --src Create file in src/ folder
172
- --root Create file in project root
173
- --force, -f Overwrite existing files
174
-
175
- EXAMPLES:
176
- # Auto-detect and setup
177
- npx securenow init
178
290
 
179
- # Force TypeScript in src folder
180
- npx securenow init --typescript --src
181
-
182
- # Force JavaScript in root
183
- npx securenow init --javascript --root
184
-
185
- # Overwrite existing files
186
- npx securenow init --force
291
+ showBanner();
292
+
293
+ const groups = {
294
+ 'Authentication': ['login', 'logout', 'whoami'],
295
+ 'Applications': ['apps', 'init', 'status'],
296
+ 'Observe': ['traces', 'logs', 'analytics'],
297
+ 'Detect & Respond': ['issues', 'notifications', 'alerts'],
298
+ 'Investigate': ['ip', 'forensics', 'api-map'],
299
+ 'Remediation': ['blocklist', 'trusted'],
300
+ 'Settings': ['instances', 'config', 'version'],
301
+ };
302
+
303
+ for (const [group, cmds] of Object.entries(groups)) {
304
+ console.log(` ${ui.c.bold(group)}`);
305
+ for (const name of cmds) {
306
+ const cmd = COMMANDS[name];
307
+ if (cmd) {
308
+ console.log(` ${ui.c.cyan(name.padEnd(18))} ${cmd.desc}`);
309
+ }
310
+ }
311
+ console.log('');
312
+ }
187
313
 
188
- DOCUMENTATION:
189
- Quick Start: node_modules/securenow/NEXTJS-QUICKSTART.md
190
- Full Guide: node_modules/securenow/NEXTJS-GUIDE.md
191
- Examples: node_modules/securenow/examples/
192
- `);
314
+ console.log(` ${ui.c.bold('GLOBAL FLAGS')}`);
315
+ console.log(` --${ui.c.cyan('json'.padEnd(16))} Output as JSON`);
316
+ console.log(` --${ui.c.cyan('help'.padEnd(16))} Show help for a command`);
317
+ console.log('');
318
+ console.log(` ${ui.c.dim('Run')} securenow help <command> ${ui.c.dim('for detailed usage')}`);
319
+ console.log('');
193
320
  }
194
321
 
195
- function versionCommand() {
196
- try {
197
- const packageJson = require('./package.json');
198
- console.log(`securenow v${packageJson.version}`);
199
- } catch (error) {
200
- console.log('securenow (version unknown)');
201
- }
202
- }
322
+ // ── Main Router ──
203
323
 
204
- // Utility functions
205
- function parseFlags(args) {
206
- const flags = {};
207
-
208
- for (let i = 0; i < args.length; i++) {
209
- const arg = args[i];
210
-
211
- if (arg === '--typescript' || arg === '--ts') {
212
- flags.typescript = true;
213
- } else if (arg === '--javascript' || arg === '--js') {
214
- flags.javascript = true;
215
- } else if (arg === '--src') {
216
- flags.src = true;
217
- } else if (arg === '--root') {
218
- flags.root = true;
219
- } else if (arg === '--force' || arg === '-f') {
220
- flags.force = true;
221
- }
222
- }
223
-
224
- return flags;
225
- }
324
+ async function main() {
325
+ const { positional, flags } = parseArgs(process.argv.slice(2));
326
+ const cmdName = positional[0] || 'help';
226
327
 
227
- function isNextJsProject(dir) {
228
- try {
229
- const packageJsonPath = path.join(dir, 'package.json');
230
- if (!fs.existsSync(packageJsonPath)) return false;
231
-
232
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
233
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
234
-
235
- return !!deps.next;
236
- } catch (error) {
237
- return false;
328
+ if (cmdName === 'help' || flags.help) {
329
+ showHelp(flags.help === true ? positional[0] : flags.help || positional[1] || (cmdName !== 'help' ? cmdName : null));
330
+ return;
238
331
  }
239
- }
240
332
 
241
- // Main
242
- function main() {
243
- const args = process.argv.slice(2);
244
- const command = args[0] || 'help';
245
- const commandFn = commands[command];
246
-
247
- if (!commandFn) {
248
- console.error(`Unknown command: ${command}`);
249
- console.log('Run "npx securenow help" for usage\n');
333
+ const cmd = COMMANDS[cmdName];
334
+ if (!cmd) {
335
+ ui.error(`Unknown command: ${cmdName}`);
336
+ ui.info('Run `securenow help` for a list of commands.');
250
337
  process.exit(1);
251
338
  }
252
-
253
- commandFn(args.slice(1));
254
- }
255
-
256
- if (require.main === module) {
257
- main();
258
- }
259
-
260
- module.exports = { main };
261
339
 
340
+ if (cmd.run && !cmd.sub) {
341
+ await cmd.run(positional.slice(1), flags);
342
+ return;
343
+ }
262
344
 
345
+ if (cmd.sub) {
346
+ let subName = positional[1];
347
+ let subArgs = positional.slice(2);
348
+
349
+ if (subName && cmd.sub[subName]) {
350
+ if (flags.help) {
351
+ showHelp(cmdName);
352
+ return;
353
+ }
354
+ await cmd.sub[subName].run(subArgs, flags);
355
+ return;
356
+ }
263
357
 
358
+ if (cmd.defaultAction) {
359
+ const allArgs = subName ? [subName, ...subArgs] : [];
360
+ await cmd.defaultAction(allArgs, flags);
361
+ return;
362
+ }
264
363
 
364
+ if (cmd.defaultSub) {
365
+ const allArgs = subName ? [subName, ...subArgs] : [];
366
+ await cmd.sub[cmd.defaultSub].run(allArgs, flags);
367
+ return;
368
+ }
265
369
 
370
+ showHelp(cmdName);
371
+ return;
372
+ }
266
373
 
374
+ if (cmd.run) {
375
+ await cmd.run(positional.slice(1), flags);
376
+ }
377
+ }
267
378
 
379
+ main().catch((err) => {
380
+ if (err.name === 'CLIError') {
381
+ ui.error(err.message);
382
+ } else {
383
+ ui.error(err.message || 'An unexpected error occurred');
384
+ if (process.env.SECURENOW_DEBUG) {
385
+ console.error(err.stack);
386
+ }
387
+ }
388
+ process.exit(1);
389
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "5.2.2",
3
+ "version": "5.3.0",
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",
@@ -20,6 +20,7 @@
20
20
  "observability",
21
21
  "apm",
22
22
  "monitoring",
23
+ "cli",
23
24
  "nextjs",
24
25
  "next.js",
25
26
  "instrumentation",
@@ -84,6 +85,7 @@
84
85
  "nextjs-wrapper.js",
85
86
  "nextjs-wrapper.d.ts",
86
87
  "cli.js",
88
+ "cli/",
87
89
  "free-trial-banner.js",
88
90
  "postinstall.js",
89
91
  "register-vite.js",