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.
- package/LICENSE +15 -0
- package/README.md +40 -239
- package/cli.js +455 -415
- package/console-instrumentation.js +136 -147
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +455 -1339
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/AUTO-BODY-CAPTURE.md +1 -1
- package/docs/AUTO-SETUP.md +4 -4
- package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
- package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
- package/docs/CHANGELOG-NEXTJS.md +35 -1
- package/docs/CUSTOMER-GUIDE.md +16 -16
- package/docs/EASIEST-SETUP.md +5 -5
- package/docs/ENVIRONMENT-VARIABLES.md +652 -880
- package/docs/EXPRESS-BODY-CAPTURE.md +12 -13
- package/docs/EXPRESS-SETUP-GUIDE.md +720 -719
- package/docs/INDEX.md +4 -22
- package/docs/LOGGING-GUIDE.md +708 -701
- package/docs/LOGGING-QUICKSTART.md +255 -234
- package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
- package/docs/NEXTJS-GUIDE.md +14 -14
- package/docs/NEXTJS-QUICKSTART.md +1 -1
- package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
- package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
- package/docs/REDACTION-EXAMPLES.md +1 -1
- package/docs/REQUEST-BODY-CAPTURE.md +10 -19
- package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
- package/examples/README.md +6 -6
- package/examples/instrumentation-with-auto-capture.ts +1 -1
- package/examples/nextjs-env-example.txt +2 -2
- package/examples/nextjs-instrumentation.js +1 -1
- package/examples/nextjs-instrumentation.ts +1 -1
- package/examples/nextjs-with-logging-example.md +6 -6
- package/examples/nextjs-with-options.ts +1 -1
- package/examples/test-nextjs-setup.js +1 -1
- package/nextjs-auto-capture.js +207 -199
- package/nextjs-middleware.js +181 -186
- package/nextjs-webpack-config.js +53 -88
- package/nextjs-wrapper.js +158 -158
- package/nextjs.d.ts +1 -1
- package/nextjs.js +198 -186
- package/package.json +45 -67
- package/postinstall.js +6 -6
- package/register.d.ts +1 -1
- package/register.js +4 -39
- package/tracing.d.ts +1 -2
- package/tracing.js +26 -286
- package/web-vite.mjs +156 -239
- package/CONSUMING-APPS-GUIDE.md +0 -455
- package/NPM_README.md +0 -1933
- package/SKILL-API.md +0 -600
- package/SKILL-CLI.md +0 -409
- package/cidr.js +0 -83
- package/cli/apps.js +0 -585
- package/cli/auth.js +0 -280
- package/cli/client.js +0 -115
- package/cli/config.js +0 -173
- package/cli/firewall.js +0 -100
- package/cli/fp.js +0 -638
- package/cli/init.js +0 -201
- package/cli/monitor.js +0 -440
- package/cli/run.js +0 -133
- package/cli/security.js +0 -1064
- package/cli/ui.js +0 -386
- package/docs/API-KEYS-GUIDE.md +0 -233
- package/docs/AUTO-SETUP-SUMMARY.md +0 -331
- package/docs/BODY-CAPTURE-FIX.md +0 -261
- package/docs/COMPLETION-REPORT.md +0 -408
- package/docs/FINAL-SOLUTION.md +0 -335
- package/docs/FIREWALL-GUIDE.md +0 -426
- package/docs/IMPLEMENTATION-SUMMARY.md +0 -410
- package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +0 -323
- package/docs/NEXTJS-SETUP-COMPLETE.md +0 -795
- package/docs/NUXT-GUIDE.md +0 -166
- package/docs/SOLUTION-SUMMARY.md +0 -312
- package/firewall-cloud.js +0 -212
- package/firewall-iptables.js +0 -139
- package/firewall-only.js +0 -38
- package/firewall-tcp.js +0 -74
- package/firewall.js +0 -720
- package/free-trial-banner.js +0 -174
- package/nuxt-server-plugin.mjs +0 -423
- package/nuxt.d.ts +0 -60
- package/nuxt.mjs +0 -75
- 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
|
-
|
|
4
|
+
/**
|
|
5
|
+
* SecureNow CLI
|
|
6
|
+
*
|
|
7
|
+
* Usage: npx securenow init [options]
|
|
8
|
+
*/
|
|
5
9
|
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
38
|
+
export function register() {
|
|
39
|
+
registerSecureNow();
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
console.log('');
|
|
345
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
'Run
|
|
352
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
//
|
|
308
|
+
// ─── apps ───────────────────────────────────────────────────────
|
|
382
309
|
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
const
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
399
|
-
|
|
341
|
+
console.log('');
|
|
342
|
+
} catch (err) {
|
|
343
|
+
console.error('❌ ' + err.message);
|
|
400
344
|
process.exit(1);
|
|
401
345
|
}
|
|
346
|
+
}
|
|
402
347
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
378
|
+
function helpCommand() {
|
|
379
|
+
console.log(`
|
|
380
|
+
SecureNow CLI - OpenTelemetry instrumentation for Next.js
|
|
420
381
|
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
443
|
-
|
|
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
|
-
|
|
447
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
456
|
-
|
|
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
|
-
|
|
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
|
+
|