securenow 5.2.1 → 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/apps.js +167 -0
- package/cli/auth.js +208 -0
- package/cli/client.js +113 -0
- package/cli/config.js +111 -0
- package/cli/init.js +100 -0
- package/cli/monitor.js +458 -0
- package/cli/security.js +630 -0
- package/cli/ui.js +312 -0
- package/cli.js +357 -235
- package/nextjs.js +48 -6
- package/package.json +3 -1
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
|
-
|
|
11
|
-
const path = require('path');
|
|
6
|
+
// ── Argument Parser ──
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
registerSecureNow();
|
|
36
|
+
return { positional, flags };
|
|
43
37
|
}
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
# OTEL_EXPORTER_OTLP_HEADERS="x-api-key=your-api-key-here"
|
|
249
|
+
// ── Help System ──
|
|
67
250
|
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
});
|