securenow 6.0.2 → 6.1.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.
Files changed (87) hide show
  1. package/CONSUMING-APPS-GUIDE.md +455 -0
  2. package/NPM_README.md +2029 -0
  3. package/README.md +297 -40
  4. package/SKILL-API.md +634 -0
  5. package/SKILL-CLI.md +454 -0
  6. package/cidr.js +83 -0
  7. package/cli/apps.js +585 -0
  8. package/cli/auth.js +280 -0
  9. package/cli/client.js +115 -0
  10. package/cli/config.js +173 -0
  11. package/cli/diagnostics.js +387 -0
  12. package/cli/firewall.js +100 -0
  13. package/cli/fp.js +638 -0
  14. package/cli/init.js +201 -0
  15. package/cli/monitor.js +440 -0
  16. package/cli/run.js +148 -0
  17. package/cli/security.js +980 -0
  18. package/cli/ui.js +386 -0
  19. package/cli/utils.js +127 -0
  20. package/cli.js +466 -455
  21. package/console-instrumentation.js +147 -136
  22. package/docs/ALL-FRAMEWORKS-QUICKSTART.md +1377 -455
  23. package/docs/API-KEYS-GUIDE.md +233 -0
  24. package/docs/ARCHITECTURE.md +3 -3
  25. package/docs/AUTO-BODY-CAPTURE.md +1 -1
  26. package/docs/AUTO-SETUP-SUMMARY.md +331 -0
  27. package/docs/AUTO-SETUP.md +4 -4
  28. package/docs/AUTOMATIC-IP-CAPTURE.md +5 -5
  29. package/docs/BODY-CAPTURE-FIX.md +261 -0
  30. package/docs/BODY-CAPTURE-QUICKSTART.md +2 -2
  31. package/docs/CHANGELOG-NEXTJS.md +1 -35
  32. package/docs/COMPLETION-REPORT.md +408 -0
  33. package/docs/CUSTOMER-GUIDE.md +16 -16
  34. package/docs/EASIEST-SETUP.md +5 -5
  35. package/docs/ENVIRONMENT-VARIABLES.md +880 -652
  36. package/docs/EXPRESS-BODY-CAPTURE.md +13 -12
  37. package/docs/EXPRESS-SETUP-GUIDE.md +719 -720
  38. package/docs/FINAL-SOLUTION.md +335 -0
  39. package/docs/FIREWALL-GUIDE.md +426 -0
  40. package/docs/IMPLEMENTATION-SUMMARY.md +410 -0
  41. package/docs/INDEX.md +22 -4
  42. package/docs/LOGGING-GUIDE.md +701 -708
  43. package/docs/LOGGING-QUICKSTART.md +234 -255
  44. package/docs/NEXTJS-BODY-CAPTURE-COMPARISON.md +323 -0
  45. package/docs/NEXTJS-BODY-CAPTURE.md +2 -2
  46. package/docs/NEXTJS-GUIDE.md +14 -14
  47. package/docs/NEXTJS-QUICKSTART.md +1 -1
  48. package/docs/NEXTJS-SETUP-COMPLETE.md +795 -0
  49. package/docs/NEXTJS-WRAPPER-APPROACH.md +1 -1
  50. package/docs/NUXT-GUIDE.md +166 -0
  51. package/docs/QUICKSTART-BODY-CAPTURE.md +2 -2
  52. package/docs/REDACTION-EXAMPLES.md +1 -1
  53. package/docs/REQUEST-BODY-CAPTURE.md +19 -10
  54. package/docs/SOLUTION-SUMMARY.md +312 -0
  55. package/docs/VERCEL-OTEL-MIGRATION.md +3 -3
  56. package/examples/README.md +6 -6
  57. package/examples/instrumentation-with-auto-capture.ts +1 -1
  58. package/examples/nextjs-env-example.txt +2 -2
  59. package/examples/nextjs-instrumentation.js +1 -1
  60. package/examples/nextjs-instrumentation.ts +1 -1
  61. package/examples/nextjs-with-logging-example.md +6 -6
  62. package/examples/nextjs-with-options.ts +1 -1
  63. package/examples/test-nextjs-setup.js +1 -1
  64. package/firewall-cloud.js +212 -0
  65. package/firewall-iptables.js +139 -0
  66. package/firewall-only.js +38 -0
  67. package/firewall-tcp.js +74 -0
  68. package/firewall.js +720 -0
  69. package/free-trial-banner.js +174 -0
  70. package/nextjs-auto-capture.js +199 -207
  71. package/nextjs-middleware.js +186 -181
  72. package/nextjs-webpack-config.js +88 -53
  73. package/nextjs-wrapper.js +158 -158
  74. package/nextjs.d.ts +1 -1
  75. package/nextjs.js +639 -647
  76. package/nuxt-server-plugin.mjs +423 -0
  77. package/nuxt.d.ts +60 -0
  78. package/nuxt.mjs +75 -0
  79. package/package.json +186 -164
  80. package/postinstall.js +6 -6
  81. package/register.d.ts +1 -1
  82. package/register.js +39 -4
  83. package/resolve-ip.js +77 -0
  84. package/tracing.d.ts +2 -1
  85. package/tracing.js +295 -34
  86. package/web-vite.mjs +239 -156
  87. package/LICENSE +0 -15
package/cli/ui.js ADDED
@@ -0,0 +1,386 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+
5
+ const NO_COLOR = !!process.env.NO_COLOR || !process.stdout.isTTY;
6
+
7
+ const codes = {
8
+ reset: '\x1b[0m',
9
+ bold: '\x1b[1m',
10
+ dim: '\x1b[2m',
11
+ italic: '\x1b[3m',
12
+ underline: '\x1b[4m',
13
+ red: '\x1b[31m',
14
+ green: '\x1b[32m',
15
+ yellow: '\x1b[33m',
16
+ blue: '\x1b[34m',
17
+ magenta: '\x1b[35m',
18
+ cyan: '\x1b[36m',
19
+ white: '\x1b[37m',
20
+ gray: '\x1b[90m',
21
+ bgRed: '\x1b[41m',
22
+ bgGreen: '\x1b[42m',
23
+ bgYellow: '\x1b[43m',
24
+ bgBlue: '\x1b[44m',
25
+ bgCyan: '\x1b[46m',
26
+ };
27
+
28
+ function style(code, text) {
29
+ if (NO_COLOR) return text;
30
+ return `${code}${text}${codes.reset}`;
31
+ }
32
+
33
+ const c = {
34
+ bold: (s) => style(codes.bold, s),
35
+ dim: (s) => style(codes.dim, s),
36
+ italic: (s) => style(codes.italic, s),
37
+ underline: (s) => style(codes.underline, s),
38
+ red: (s) => style(codes.red, s),
39
+ green: (s) => style(codes.green, s),
40
+ yellow: (s) => style(codes.yellow, s),
41
+ blue: (s) => style(codes.blue, s),
42
+ magenta: (s) => style(codes.magenta, s),
43
+ cyan: (s) => style(codes.cyan, s),
44
+ white: (s) => style(codes.white, s),
45
+ gray: (s) => style(codes.gray, s),
46
+ success: (s) => style(codes.green, s),
47
+ error: (s) => style(codes.red, s),
48
+ warn: (s) => style(codes.yellow, s),
49
+ info: (s) => style(codes.cyan, s),
50
+ };
51
+
52
+ function stripAnsi(str) {
53
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
54
+ }
55
+
56
+ function visibleLength(str) {
57
+ return stripAnsi(str).length;
58
+ }
59
+
60
+ function padEnd(str, len) {
61
+ const visible = visibleLength(str);
62
+ if (visible >= len) return str;
63
+ return str + ' '.repeat(len - visible);
64
+ }
65
+
66
+ function table(headers, rows, opts = {}) {
67
+ if (!rows.length) {
68
+ console.log(c.dim(' No results found.'));
69
+ return;
70
+ }
71
+
72
+ const maxWidth = process.stdout.columns || 120;
73
+ const colWidths = headers.map((h, i) => {
74
+ const headerLen = visibleLength(String(h));
75
+ const maxCell = rows.reduce((max, row) => {
76
+ const cell = String(row[i] ?? '');
77
+ return Math.max(max, visibleLength(cell));
78
+ }, 0);
79
+ return Math.min(Math.max(headerLen, maxCell), opts.maxColWidth || 60);
80
+ });
81
+
82
+ const totalWidth = colWidths.reduce((a, b) => a + b, 0) + (colWidths.length - 1) * 3;
83
+ if (totalWidth > maxWidth && colWidths.length > 1) {
84
+ const lastIdx = colWidths.length - 1;
85
+ const excess = totalWidth - maxWidth;
86
+ colWidths[lastIdx] = Math.max(10, colWidths[lastIdx] - excess);
87
+ }
88
+
89
+ const headerLine = headers.map((h, i) => padEnd(c.bold(String(h)), colWidths[i])).join(' ');
90
+ const separator = colWidths.map(w => c.dim('─'.repeat(w))).join('───');
91
+
92
+ console.log(` ${headerLine}`);
93
+ console.log(` ${separator}`);
94
+
95
+ for (const row of rows) {
96
+ const line = row.map((cell, i) => {
97
+ let s = String(cell ?? '');
98
+ if (visibleLength(s) > colWidths[i]) {
99
+ s = stripAnsi(s).slice(0, colWidths[i] - 1) + '…';
100
+ }
101
+ return padEnd(s, colWidths[i]);
102
+ }).join(' ');
103
+ console.log(` ${line}`);
104
+ }
105
+ }
106
+
107
+ function keyValue(pairs) {
108
+ const maxKey = pairs.reduce((max, [k]) => Math.max(max, k.length), 0);
109
+ for (const [key, value] of pairs) {
110
+ console.log(` ${c.bold(key.padEnd(maxKey))} ${value ?? c.dim('—')}`);
111
+ }
112
+ }
113
+
114
+ function heading(text) {
115
+ console.log(`\n${c.bold(c.cyan(text))}`);
116
+ }
117
+
118
+ function subheading(text) {
119
+ console.log(`\n ${c.bold(text)}`);
120
+ }
121
+
122
+ function success(msg) {
123
+ console.log(`${c.green('✓')} ${msg}`);
124
+ }
125
+
126
+ function error(msg) {
127
+ console.error(`${c.red('✗')} ${msg}`);
128
+ }
129
+
130
+ function warn(msg) {
131
+ console.log(`${c.yellow('!')} ${msg}`);
132
+ }
133
+
134
+ function info(msg) {
135
+ console.log(`${c.cyan('ℹ')} ${msg}`);
136
+ }
137
+
138
+ function spinner(message) {
139
+ if (!process.stderr.isTTY) {
140
+ process.stderr.write(` ${message}...\n`);
141
+ return {
142
+ update() {},
143
+ stop(msg) { if (msg) process.stderr.write(` ${msg}\n`); },
144
+ fail(msg) { if (msg) process.stderr.write(` ${msg}\n`); },
145
+ };
146
+ }
147
+
148
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
149
+ let i = 0;
150
+ let currentMsg = message;
151
+ const interval = setInterval(() => {
152
+ const frame = c.cyan(frames[i++ % frames.length]);
153
+ process.stderr.write(`\r ${frame} ${currentMsg}`);
154
+ }, 80);
155
+
156
+ return {
157
+ update(msg) { currentMsg = msg; },
158
+ stop(finalMsg) {
159
+ clearInterval(interval);
160
+ process.stderr.write(`\r ${c.green('✓')} ${finalMsg || currentMsg}\n`);
161
+ },
162
+ fail(finalMsg) {
163
+ clearInterval(interval);
164
+ process.stderr.write(`\r ${c.red('✗')} ${finalMsg || currentMsg}\n`);
165
+ },
166
+ };
167
+ }
168
+
169
+ function prompt(question, opts = {}) {
170
+ const rl = readline.createInterface({
171
+ input: process.stdin,
172
+ output: process.stderr,
173
+ });
174
+
175
+ return new Promise((resolve) => {
176
+ const q = opts.secret ? question : ` ${c.cyan('?')} ${question} `;
177
+ rl.question(q, (answer) => {
178
+ rl.close();
179
+ resolve(answer.trim());
180
+ });
181
+
182
+ if (opts.secret) {
183
+ const onData = (char) => {
184
+ const code = char.toString();
185
+ if (code === '\n' || code === '\r' || code === '\u0004') return;
186
+ process.stderr.write('*');
187
+ };
188
+ process.stdin.on('data', onData);
189
+ rl.once('close', () => process.stdin.removeListener('data', onData));
190
+ }
191
+ });
192
+ }
193
+
194
+ async function confirm(question, defaultYes = false) {
195
+ const hint = defaultYes ? 'Y/n' : 'y/N';
196
+ const answer = await prompt(`${question} ${c.dim(`(${hint})`)}`);
197
+ if (!answer) return defaultYes;
198
+ return answer.toLowerCase().startsWith('y');
199
+ }
200
+
201
+ async function select(question, choices) {
202
+ console.log(`\n ${c.cyan('?')} ${question}\n`);
203
+ choices.forEach((choice, i) => {
204
+ console.log(` ${c.bold(String(i + 1))} ${choice.label || choice}`);
205
+ });
206
+ console.log('');
207
+ const answer = await prompt('Enter number');
208
+ const idx = parseInt(answer, 10) - 1;
209
+ if (idx < 0 || idx >= choices.length) {
210
+ error('Invalid selection');
211
+ process.exit(1);
212
+ }
213
+ return choices[idx].value ?? choices[idx];
214
+ }
215
+
216
+ async function multiSelect(question, choices) {
217
+ console.log(`\n ${c.cyan('?')} ${question}\n`);
218
+ choices.forEach((choice, i) => {
219
+ const label = choice.label || choice;
220
+ const detail = choice.detail ? c.dim(` — ${choice.detail}`) : '';
221
+ console.log(` ${c.bold(String(i + 1).padStart(3))} ${label}${detail}`);
222
+ });
223
+ console.log('');
224
+ console.log(c.dim(` Enter numbers separated by commas, a range (e.g. 1-5), or "all"`));
225
+ const answer = await prompt('Selection');
226
+
227
+ if (!answer) return [];
228
+
229
+ if (answer.toLowerCase() === 'all') {
230
+ return choices.map((ch) => ch.value ?? ch);
231
+ }
232
+
233
+ const indices = new Set();
234
+ for (const part of answer.split(',')) {
235
+ const trimmed = part.trim();
236
+ if (trimmed.includes('-')) {
237
+ const [startStr, endStr] = trimmed.split('-');
238
+ const start = parseInt(startStr, 10);
239
+ const end = parseInt(endStr, 10);
240
+ if (!isNaN(start) && !isNaN(end)) {
241
+ for (let i = Math.min(start, end); i <= Math.max(start, end); i++) {
242
+ indices.add(i);
243
+ }
244
+ }
245
+ } else {
246
+ const num = parseInt(trimmed, 10);
247
+ if (!isNaN(num)) indices.add(num);
248
+ }
249
+ }
250
+
251
+ const selected = [];
252
+ for (const idx of indices) {
253
+ if (idx >= 1 && idx <= choices.length) {
254
+ selected.push(choices[idx - 1].value ?? choices[idx - 1]);
255
+ }
256
+ }
257
+ return selected;
258
+ }
259
+
260
+ function json(data) {
261
+ console.log(JSON.stringify(data, null, 2));
262
+ }
263
+
264
+ function hr() {
265
+ const width = Math.min(process.stdout.columns || 80, 80);
266
+ console.log(c.dim('─'.repeat(width)));
267
+ }
268
+
269
+ // Parses timestamps the backend returns in multiple formats:
270
+ // - UInt64 nanoseconds as a string (signoz logs) → "1775856777891000000"
271
+ // - ClickHouse DateTime64 string (signoz traces) → "2026-04-10 21:34:51.133000000" (UTC, no Z)
272
+ // - ISO 8601 → "2026-04-10T21:34:51.133Z"
273
+ // - number (ms) / Date → passthrough
274
+ function parseTimestamp(ts) {
275
+ if (ts == null || ts === '') return null;
276
+ if (ts instanceof Date) return isNaN(ts.getTime()) ? null : ts;
277
+ if (typeof ts === 'number') {
278
+ const d = new Date(ts);
279
+ return isNaN(d.getTime()) ? null : d;
280
+ }
281
+ const s = String(ts).trim();
282
+ if (/^\d{16,}$/.test(s)) {
283
+ const d = new Date(Number(s) / 1e6);
284
+ return isNaN(d.getTime()) ? null : d;
285
+ }
286
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(s) && !/[zZ]|[+-]\d{2}:?\d{2}$/.test(s)) {
287
+ const isoish = s.replace(' ', 'T').replace(/\.(\d{3})\d*$/, '.$1') + 'Z';
288
+ const d = new Date(isoish);
289
+ if (!isNaN(d.getTime())) return d;
290
+ }
291
+ const d = new Date(s);
292
+ return isNaN(d.getTime()) ? null : d;
293
+ }
294
+
295
+ function timeAgo(dateStr) {
296
+ const date = parseTimestamp(dateStr);
297
+ if (!date) return '—';
298
+ const diff = Date.now() - date.getTime();
299
+ const seconds = Math.floor(diff / 1000);
300
+ if (seconds < 60) return 'just now';
301
+ const minutes = Math.floor(seconds / 60);
302
+ if (minutes < 60) return `${minutes}m ago`;
303
+ const hours = Math.floor(minutes / 60);
304
+ if (hours < 24) return `${hours}h ago`;
305
+ const days = Math.floor(hours / 24);
306
+ if (days < 30) return `${days}d ago`;
307
+ return date.toLocaleDateString();
308
+ }
309
+
310
+ function truncate(str, len = 50) {
311
+ if (!str) return '';
312
+ str = String(str);
313
+ if (str.length <= len) return str;
314
+ return str.slice(0, len - 1) + '…';
315
+ }
316
+
317
+ function statusBadge(status) {
318
+ const map = {
319
+ active: c.green('● active'),
320
+ inactive: c.dim('○ inactive'),
321
+ healthy: c.green('● healthy'),
322
+ unhealthy: c.red('● unhealthy'),
323
+ warning: c.yellow('● warning'),
324
+ open: c.red('● open'),
325
+ resolved: c.green('● resolved'),
326
+ closed: c.dim('● closed'),
327
+ acknowledged: c.yellow('● ack'),
328
+ blocked: c.red('■ blocked'),
329
+ trusted: c.green('■ trusted'),
330
+ enabled: c.green('● enabled'),
331
+ disabled: c.dim('○ disabled'),
332
+ paused: c.yellow('◆ paused'),
333
+ critical: c.red('▲ critical'),
334
+ high: c.red('▲ high'),
335
+ medium: c.yellow('▲ medium'),
336
+ low: c.blue('▲ low'),
337
+ info: c.cyan('▲ info'),
338
+ read: c.dim('○ read'),
339
+ unread: c.cyan('● unread'),
340
+ };
341
+ return map[(status || '').toLowerCase()] || status;
342
+ }
343
+
344
+ function httpStatusColor(code) {
345
+ code = parseInt(code, 10);
346
+ if (code < 300) return c.green(code);
347
+ if (code < 400) return c.cyan(code);
348
+ if (code < 500) return c.yellow(code);
349
+ return c.red(code);
350
+ }
351
+
352
+ function durationColor(ms) {
353
+ ms = parseFloat(ms);
354
+ if (isNaN(ms)) return '—';
355
+ if (ms < 100) return c.green(`${ms.toFixed(0)}ms`);
356
+ if (ms < 500) return c.yellow(`${ms.toFixed(0)}ms`);
357
+ if (ms < 1000) return c.red(`${ms.toFixed(0)}ms`);
358
+ return c.red(`${(ms / 1000).toFixed(2)}s`);
359
+ }
360
+
361
+ module.exports = {
362
+ c,
363
+ NO_COLOR,
364
+ stripAnsi,
365
+ table,
366
+ keyValue,
367
+ heading,
368
+ subheading,
369
+ success,
370
+ error,
371
+ warn,
372
+ info,
373
+ spinner,
374
+ prompt,
375
+ confirm,
376
+ select,
377
+ multiSelect,
378
+ json,
379
+ hr,
380
+ timeAgo,
381
+ parseTimestamp,
382
+ truncate,
383
+ statusBadge,
384
+ httpStatusColor,
385
+ durationColor,
386
+ };
package/cli/utils.js ADDED
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const ui = require('./ui');
5
+ const cidrLib = require('../cidr');
6
+ const { redactSensitiveData, DEFAULT_SENSITIVE_FIELDS } = require('../nextjs-middleware');
7
+
8
+ // ── redact ──
9
+
10
+ function readInput(arg) {
11
+ if (!arg) return null;
12
+ if (arg.startsWith('@')) {
13
+ const file = arg.slice(1);
14
+ return fs.readFileSync(file, 'utf8');
15
+ }
16
+ return arg;
17
+ }
18
+
19
+ function redact(args, flags) {
20
+ const input = readInput(args[0]);
21
+ if (!input) {
22
+ ui.error('Missing input. Provide a JSON string or @path/to/file.json');
23
+ console.log('');
24
+ console.log(` ${ui.c.bold('Usage:')} securenow redact '<json>' [--fields f1,f2]`);
25
+ console.log(` securenow redact @request.json`);
26
+ console.log('');
27
+ process.exit(1);
28
+ }
29
+
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(input);
33
+ } catch (err) {
34
+ ui.error(`Invalid JSON: ${err.message}`);
35
+ process.exit(1);
36
+ }
37
+
38
+ const extra = flags.fields
39
+ ? String(flags.fields).split(',').map((s) => s.trim()).filter(Boolean)
40
+ : [];
41
+ const envExtra = (process.env.SECURENOW_SENSITIVE_FIELDS || '')
42
+ .split(',').map((s) => s.trim()).filter(Boolean);
43
+ const fields = [...DEFAULT_SENSITIVE_FIELDS, ...envExtra, ...extra];
44
+
45
+ const result = redactSensitiveData(parsed, fields);
46
+
47
+ if (flags.json) {
48
+ ui.json(result);
49
+ return;
50
+ }
51
+ console.log(JSON.stringify(result, null, 2));
52
+ }
53
+
54
+ // ── cidr ──
55
+
56
+ function cidrMatch(args, flags) {
57
+ const ip = args[0];
58
+ const cidrs = args[1];
59
+ if (!ip || !cidrs) {
60
+ ui.error('Usage: securenow cidr match <ip> <cidr1[,cidr2,...]>');
61
+ process.exit(1);
62
+ }
63
+
64
+ const list = cidrs.split(',').map((s) => s.trim()).filter(Boolean);
65
+ const matcher = cidrLib.createMatcher(list);
66
+ const hit = matcher.isBlocked(ip);
67
+ const stats = matcher.stats();
68
+
69
+ if (flags.json) {
70
+ ui.json({ ip, cidrs: list, match: hit, stats });
71
+ return;
72
+ }
73
+
74
+ console.log('');
75
+ ui.keyValue([
76
+ ['IP', ip],
77
+ ['Entries', `${stats.exact} exact, ${stats.cidr} CIDR`],
78
+ ['Match', hit ? ui.c.red('BLOCKED') : ui.c.green('not in list')],
79
+ ]);
80
+ console.log('');
81
+ process.exit(hit ? 0 : 2);
82
+ }
83
+
84
+ function cidrParse(args, flags) {
85
+ const cidr = args[0];
86
+ if (!cidr) {
87
+ ui.error('Usage: securenow cidr parse <cidr>');
88
+ process.exit(1);
89
+ }
90
+
91
+ const parsed = cidrLib.parseCidr(cidr);
92
+ if (!parsed) {
93
+ ui.error(`Invalid CIDR: ${cidr}`);
94
+ process.exit(1);
95
+ }
96
+
97
+ const intToIp = (n) => [
98
+ (n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff,
99
+ ].join('.');
100
+ const broadcast = (parsed.network | (~parsed.mask >>> 0)) >>> 0;
101
+ const size = (~parsed.mask >>> 0) + 1;
102
+
103
+ const out = {
104
+ cidr,
105
+ network: intToIp(parsed.network),
106
+ broadcast: intToIp(broadcast),
107
+ mask: intToIp(parsed.mask),
108
+ size,
109
+ };
110
+
111
+ if (flags.json) {
112
+ ui.json(out);
113
+ return;
114
+ }
115
+
116
+ console.log('');
117
+ ui.keyValue([
118
+ ['CIDR', out.cidr],
119
+ ['Network', out.network],
120
+ ['Broadcast', out.broadcast],
121
+ ['Mask', out.mask],
122
+ ['Size', String(out.size)],
123
+ ]);
124
+ console.log('');
125
+ }
126
+
127
+ module.exports = { redact, cidrMatch, cidrParse };