@zincapp/znvault-cli 3.3.0 → 4.0.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 (89) hide show
  1. package/README.md +32 -28
  2. package/dist/commands/advisor.d.ts +14 -1
  3. package/dist/commands/advisor.d.ts.map +1 -1
  4. package/dist/commands/advisor.js +352 -328
  5. package/dist/commands/advisor.js.map +1 -1
  6. package/dist/commands/agent/index.d.ts +2 -1
  7. package/dist/commands/agent/index.d.ts.map +1 -1
  8. package/dist/commands/agent/index.js +23 -19
  9. package/dist/commands/agent/index.js.map +1 -1
  10. package/dist/commands/apikey/index.d.ts +2 -1
  11. package/dist/commands/apikey/index.d.ts.map +1 -1
  12. package/dist/commands/apikey/index.js +23 -14
  13. package/dist/commands/apikey/index.js.map +1 -1
  14. package/dist/commands/audit.d.ts +2 -1
  15. package/dist/commands/audit.d.ts.map +1 -1
  16. package/dist/commands/audit.js +7 -2
  17. package/dist/commands/audit.js.map +1 -1
  18. package/dist/commands/group.d.ts +2 -1
  19. package/dist/commands/group.d.ts.map +1 -1
  20. package/dist/commands/group.js +47 -37
  21. package/dist/commands/group.js.map +1 -1
  22. package/dist/commands/host/index.d.ts +2 -1
  23. package/dist/commands/host/index.d.ts.map +1 -1
  24. package/dist/commands/host/index.js +22 -18
  25. package/dist/commands/host/index.js.map +1 -1
  26. package/dist/commands/kms/index.d.ts +2 -1
  27. package/dist/commands/kms/index.d.ts.map +1 -1
  28. package/dist/commands/kms/index.js +11 -6
  29. package/dist/commands/kms/index.js.map +1 -1
  30. package/dist/commands/policy/index.d.ts +2 -1
  31. package/dist/commands/policy/index.d.ts.map +1 -1
  32. package/dist/commands/policy/index.js +149 -101
  33. package/dist/commands/policy/index.js.map +1 -1
  34. package/dist/commands/quarantine.d.ts +2 -1
  35. package/dist/commands/quarantine.d.ts.map +1 -1
  36. package/dist/commands/quarantine.js +380 -337
  37. package/dist/commands/quarantine.js.map +1 -1
  38. package/dist/commands/role.d.ts +2 -1
  39. package/dist/commands/role.d.ts.map +1 -1
  40. package/dist/commands/role.js +62 -40
  41. package/dist/commands/role.js.map +1 -1
  42. package/dist/commands/secret/create.d.ts.map +1 -1
  43. package/dist/commands/secret/create.js +1 -5
  44. package/dist/commands/secret/create.js.map +1 -1
  45. package/dist/commands/ssh/index.d.ts +2 -1
  46. package/dist/commands/ssh/index.d.ts.map +1 -1
  47. package/dist/commands/ssh/index.js +18 -13
  48. package/dist/commands/ssh/index.js.map +1 -1
  49. package/dist/commands/sso/index.d.ts +2 -1
  50. package/dist/commands/sso/index.d.ts.map +1 -1
  51. package/dist/commands/sso/index.js +8 -5
  52. package/dist/commands/sso/index.js.map +1 -1
  53. package/dist/commands/superadmin/accounts.d.ts +10 -0
  54. package/dist/commands/superadmin/accounts.d.ts.map +1 -0
  55. package/dist/commands/{superadmin.js → superadmin/accounts.js} +16 -15
  56. package/dist/commands/superadmin/accounts.js.map +1 -0
  57. package/dist/commands/superadmin/index.d.ts +20 -0
  58. package/dist/commands/superadmin/index.d.ts.map +1 -0
  59. package/dist/commands/superadmin/index.js +60 -0
  60. package/dist/commands/superadmin/index.js.map +1 -0
  61. package/dist/commands/user.d.ts +2 -1
  62. package/dist/commands/user.d.ts.map +1 -1
  63. package/dist/commands/user.js +21 -10
  64. package/dist/commands/user.js.map +1 -1
  65. package/dist/index.js +18 -13
  66. package/dist/index.js.map +1 -1
  67. package/dist/lib/client/index.d.ts +5 -3
  68. package/dist/lib/client/index.d.ts.map +1 -1
  69. package/dist/lib/client/index.js +3 -3
  70. package/dist/lib/client/index.js.map +1 -1
  71. package/dist/lib/client/quarantine.d.ts +6 -26
  72. package/dist/lib/client/quarantine.d.ts.map +1 -1
  73. package/dist/lib/client/quarantine.js +24 -38
  74. package/dist/lib/client/quarantine.js.map +1 -1
  75. package/dist/lib/client/users.d.ts +10 -1
  76. package/dist/lib/client/users.d.ts.map +1 -1
  77. package/dist/lib/client/users.js +12 -2
  78. package/dist/lib/client/users.js.map +1 -1
  79. package/dist/lib/command-context.d.ts +49 -0
  80. package/dist/lib/command-context.d.ts.map +1 -0
  81. package/dist/lib/command-context.js +81 -0
  82. package/dist/lib/command-context.js.map +1 -0
  83. package/dist/lib/context-help.d.ts.map +1 -1
  84. package/dist/lib/context-help.js +5 -0
  85. package/dist/lib/context-help.js.map +1 -1
  86. package/package.json +1 -1
  87. package/dist/commands/superadmin.d.ts +0 -3
  88. package/dist/commands/superadmin.d.ts.map +0 -1
  89. package/dist/commands/superadmin.js.map +0 -1
@@ -3,389 +3,432 @@ import { client } from '../lib/client.js';
3
3
  import { promptConfirm } from '../lib/prompts.js';
4
4
  import * as output from '../lib/output.js';
5
5
  import { formatTtl as formatDuration } from '../lib/format-helpers.js';
6
+ import { resolveContext, withRegisterContext, } from '../lib/command-context.js';
6
7
  function formatBackoffLevel(level) {
7
8
  const labels = ['', '1m', '5m', '30m', '4h', '24h'];
8
9
  return labels[level] ?? `Level ${level}`;
9
10
  }
10
- export function registerQuarantineCommands(program) {
11
- const quarantine = program
11
+ export function registerQuarantineCommands(parent, opts) {
12
+ const ctx = resolveContext(opts);
13
+ withRegisterContext(ctx, () => { registerQuarantineCommandsInner(parent, ctx); });
14
+ }
15
+ function registerQuarantineCommandsInner(parent, ctx) {
16
+ // Helper: only register --tenant in superadmin context
17
+ const t = (cmd, desc, shortFlag = true) => ctx === 'superadmin'
18
+ ? cmd.option(shortFlag ? '-t, --tenant <tenant>' : '--tenant <tenant>', desc)
19
+ : cmd;
20
+ const quarantine = parent
12
21
  .command('quarantine')
13
- .description('IP quarantine management commands');
22
+ .description(ctx === 'superadmin'
23
+ ? 'IP quarantine management (cross-tenant)'
24
+ : 'IP quarantine management commands');
14
25
  // List quarantined IPs
15
- quarantine
16
- .command('list')
17
- .description('List quarantined IPs')
18
- .option('-s, --status <status>', 'Filter by status (active, released, expired)')
19
- .option('-t, --tenant <tenant>', 'Filter by tenant ID')
20
- .option('--include-expired', 'Include expired quarantines')
21
- .option('--limit <number>', 'Number of entries to show', '50')
22
- .option('--json', 'Output as JSON')
23
- .action(async (options) => {
24
- const spinner = output.spinner('Fetching quarantined IPs...').start();
25
- try {
26
- const result = await client.listQuarantines({
27
- status: options.status,
28
- tenantId: options.tenant,
29
- includeExpired: options.includeExpired,
30
- limit: parseInt(options.limit, 10),
31
- });
32
- spinner.stop();
33
- if (options.json) {
34
- output.json(result);
35
- return;
26
+ {
27
+ const listCmd = quarantine
28
+ .command('list')
29
+ .description('List quarantined IPs')
30
+ .option('-s, --status <status>', 'Filter by status (active, released, expired)');
31
+ t(listCmd, 'Filter by tenant ID');
32
+ listCmd
33
+ .option('--include-expired', 'Include expired quarantines')
34
+ .option('--limit <number>', 'Number of entries to show', '50')
35
+ .option('--json', 'Output as JSON')
36
+ .action(async (options) => {
37
+ const spinner = output.spinner('Fetching quarantined IPs...').start();
38
+ try {
39
+ const result = await client.listQuarantines({
40
+ status: options.status,
41
+ tenantId: options.tenant,
42
+ includeExpired: options.includeExpired,
43
+ limit: parseInt(options.limit, 10),
44
+ });
45
+ spinner.stop();
46
+ if (options.json) {
47
+ output.json(result);
48
+ return;
49
+ }
50
+ if (result.items.length === 0) {
51
+ output.info('No quarantined IPs found');
52
+ return;
53
+ }
54
+ output.table(['IP Address', 'Status', 'Failures', 'Level', 'Blocked Until', 'Tenant', 'Reason'], result.items.map(q => [
55
+ q.ipAddress,
56
+ output.formatStatus(q.status),
57
+ q.failureCount,
58
+ formatBackoffLevel(q.backoffLevel),
59
+ q.status === 'active' ? output.formatRelativeTime(q.blockedUntil) : '-',
60
+ q.tenantId ?? '-',
61
+ q.reason.substring(0, 30),
62
+ ]));
63
+ output.info(`Showing ${result.items.length} of ${result.pagination.total} quarantines`);
36
64
  }
37
- if (result.items.length === 0) {
38
- output.info('No quarantined IPs found');
39
- return;
65
+ catch (err) {
66
+ spinner.fail('Failed to list quarantines');
67
+ output.error(err instanceof Error ? err.message : String(err));
68
+ process.exit(1);
40
69
  }
41
- output.table(['IP Address', 'Status', 'Failures', 'Level', 'Blocked Until', 'Tenant', 'Reason'], result.items.map(q => [
42
- q.ipAddress,
43
- output.formatStatus(q.status),
44
- q.failureCount,
45
- formatBackoffLevel(q.backoffLevel),
46
- q.status === 'active' ? output.formatRelativeTime(q.blockedUntil) : '-',
47
- q.tenantId ?? '-',
48
- q.reason.substring(0, 30),
49
- ]));
50
- output.info(`Showing ${result.items.length} of ${result.pagination.total} quarantines`);
51
- }
52
- catch (err) {
53
- spinner.fail('Failed to list quarantines');
54
- output.error(err instanceof Error ? err.message : String(err));
55
- process.exit(1);
56
- }
57
- });
70
+ });
71
+ }
58
72
  // Get quarantine details
59
- quarantine
60
- .command('get <id>')
61
- .description('Get quarantine details by ID')
62
- .option('--json', 'Output as JSON')
63
- .action(async (id, options) => {
64
- const spinner = output.spinner('Fetching quarantine details...').start();
65
- try {
66
- const q = await client.getQuarantine(id);
67
- spinner.stop();
68
- if (options.json) {
69
- output.json(q);
70
- return;
71
- }
72
- output.section('Quarantine Details');
73
- output.keyValue({
74
- 'ID': q.id,
75
- 'IP Address': q.ipAddress,
76
- 'Status': output.formatStatus(q.status),
77
- 'Tenant': q.tenantId ?? 'N/A (System)',
78
- 'Failure Count': q.failureCount,
79
- 'Backoff Level': `${q.backoffLevel} (${formatBackoffLevel(q.backoffLevel)})`,
80
- 'Blocked Until': q.status === 'active' ? output.formatDate(q.blockedUntil) : '-',
81
- 'Reason': q.reason,
82
- 'First Failure': output.formatDate(q.firstFailureAt),
83
- 'Last Failure': output.formatDate(q.lastFailureAt),
84
- });
85
- if (q.lastFailurePath) {
86
- output.section('Last Request');
73
+ {
74
+ const getCmd = quarantine
75
+ .command('get <id>')
76
+ .description('Get quarantine details by ID');
77
+ t(getCmd, 'Tenant ID (superadmin only, cross-tenant)');
78
+ getCmd
79
+ .option('--json', 'Output as JSON')
80
+ .action(async (id, options) => {
81
+ const spinner = output.spinner('Fetching quarantine details...').start();
82
+ try {
83
+ const q = await client.getQuarantine(id, options.tenant);
84
+ spinner.stop();
85
+ if (options.json) {
86
+ output.json(q);
87
+ return;
88
+ }
89
+ output.section('Quarantine Details');
87
90
  output.keyValue({
88
- 'Path': q.lastFailurePath,
89
- 'Method': q.lastFailureMethod ?? '-',
90
- 'User Agent': q.userAgent?.substring(0, 50) ?? '-',
91
- 'API Key ID': q.apiKeyId ?? '-',
92
- 'Agent ID': q.agentId ?? '-',
91
+ 'ID': q.id,
92
+ 'IP Address': q.ipAddress,
93
+ 'Status': output.formatStatus(q.status),
94
+ 'Tenant': q.tenantId ?? 'N/A (System)',
95
+ 'Failure Count': q.failureCount,
96
+ 'Backoff Level': `${q.backoffLevel} (${formatBackoffLevel(q.backoffLevel)})`,
97
+ 'Blocked Until': q.status === 'active' ? output.formatDate(q.blockedUntil) : '-',
98
+ 'Reason': q.reason,
99
+ 'First Failure': output.formatDate(q.firstFailureAt),
100
+ 'Last Failure': output.formatDate(q.lastFailureAt),
93
101
  });
102
+ if (q.lastFailurePath) {
103
+ output.section('Last Request');
104
+ output.keyValue({
105
+ 'Path': q.lastFailurePath,
106
+ 'Method': q.lastFailureMethod ?? '-',
107
+ 'User Agent': q.userAgent?.substring(0, 50) ?? '-',
108
+ 'API Key ID': q.apiKeyId ?? '-',
109
+ 'Agent ID': q.agentId ?? '-',
110
+ });
111
+ }
112
+ if (q.releasedAt) {
113
+ output.section('Release Info');
114
+ output.keyValue({
115
+ 'Released At': output.formatDate(q.releasedAt),
116
+ 'Released By': q.releasedBy ?? '-',
117
+ 'Release Reason': q.releaseReason ?? '-',
118
+ });
119
+ }
120
+ console.log();
94
121
  }
95
- if (q.releasedAt) {
96
- output.section('Release Info');
97
- output.keyValue({
98
- 'Released At': output.formatDate(q.releasedAt),
99
- 'Released By': q.releasedBy ?? '-',
100
- 'Release Reason': q.releaseReason ?? '-',
101
- });
122
+ catch (err) {
123
+ spinner.fail('Failed to get quarantine');
124
+ output.error(err instanceof Error ? err.message : String(err));
125
+ process.exit(1);
102
126
  }
103
- console.log();
104
- }
105
- catch (err) {
106
- spinner.fail('Failed to get quarantine');
107
- output.error(err instanceof Error ? err.message : String(err));
108
- process.exit(1);
109
- }
110
- });
127
+ });
128
+ }
111
129
  // Release a quarantine by ID
112
- quarantine
113
- .command('release <id> <reason>')
114
- .description('Release a quarantine by ID')
115
- .option('-y, --yes', 'Skip confirmation')
116
- .option('--json', 'Output as JSON')
117
- .action(async (id, reason, options) => {
118
- try {
119
- // Get quarantine info first
120
- const q = await client.getQuarantine(id);
121
- if (!options.yes) {
122
- const confirmed = await promptConfirm(`Are you sure you want to release quarantine for IP ${q.ipAddress}?`);
123
- if (!confirmed) {
124
- output.info('Cancelled');
125
- return;
130
+ {
131
+ const releaseCmd = quarantine
132
+ .command('release <id> <reason>')
133
+ .description('Release a quarantine by ID');
134
+ t(releaseCmd, 'Tenant ID (superadmin only, cross-tenant)');
135
+ releaseCmd
136
+ .option('-y, --yes', 'Skip confirmation')
137
+ .option('--json', 'Output as JSON')
138
+ .action(async (id, reason, options) => {
139
+ try {
140
+ // Get quarantine info first
141
+ const q = await client.getQuarantine(id, options.tenant);
142
+ if (!options.yes) {
143
+ const confirmed = await promptConfirm(`Are you sure you want to release quarantine for IP ${q.ipAddress}?`);
144
+ if (!confirmed) {
145
+ output.info('Cancelled');
146
+ return;
147
+ }
148
+ }
149
+ const spinner = output.spinner('Releasing quarantine...').start();
150
+ try {
151
+ const result = await client.releaseQuarantine(id, reason, options.tenant);
152
+ spinner.succeed('Quarantine released');
153
+ if (options.json) {
154
+ output.json(result);
155
+ return;
156
+ }
157
+ output.success(result.message);
126
158
  }
159
+ catch (err) {
160
+ spinner.fail('Failed to release quarantine');
161
+ throw err;
162
+ }
163
+ }
164
+ catch (err) {
165
+ output.error(err instanceof Error ? err.message : String(err));
166
+ process.exit(1);
127
167
  }
128
- const spinner = output.spinner('Releasing quarantine...').start();
168
+ });
169
+ }
170
+ // Release all quarantines for an IP
171
+ {
172
+ const releaseIpCmd = quarantine
173
+ .command('release-ip <ip> <reason>')
174
+ .description('Release all quarantines for an IP address');
175
+ t(releaseIpCmd, 'Filter by tenant ID');
176
+ releaseIpCmd
177
+ .option('-y, --yes', 'Skip confirmation')
178
+ .option('--json', 'Output as JSON')
179
+ .action(async (ip, reason, options) => {
180
+ try {
181
+ if (!options.yes) {
182
+ const confirmed = await promptConfirm(`Are you sure you want to release all quarantines for IP ${ip}?`);
183
+ if (!confirmed) {
184
+ output.info('Cancelled');
185
+ return;
186
+ }
187
+ }
188
+ const spinner = output.spinner('Releasing IP quarantines...').start();
189
+ try {
190
+ const result = await client.releaseQuarantineIp(ip, reason, options.tenant);
191
+ spinner.succeed('Quarantine(s) released');
192
+ if (options.json) {
193
+ output.json(result);
194
+ return;
195
+ }
196
+ output.success(result.message);
197
+ if (result.releasedCount !== undefined) {
198
+ output.info(`Released ${result.releasedCount} quarantine(s)`);
199
+ }
200
+ }
201
+ catch (err) {
202
+ spinner.fail('Failed to release IP quarantines');
203
+ throw err;
204
+ }
205
+ }
206
+ catch (err) {
207
+ output.error(err instanceof Error ? err.message : String(err));
208
+ process.exit(1);
209
+ }
210
+ });
211
+ }
212
+ // Get failure history for an IP
213
+ {
214
+ const historyCmd = quarantine
215
+ .command('history <ip>')
216
+ .description('Get failure history for an IP address');
217
+ t(historyCmd, 'Filter by tenant ID');
218
+ historyCmd
219
+ .option('--limit <number>', 'Number of entries to show', '100')
220
+ .option('--json', 'Output as JSON')
221
+ .action(async (ip, options) => {
222
+ const spinner = output.spinner('Fetching failure history...').start();
129
223
  try {
130
- const result = await client.releaseQuarantine(id, reason);
131
- spinner.succeed('Quarantine released');
224
+ const result = await client.getQuarantineHistory(ip, {
225
+ limit: parseInt(options.limit, 10),
226
+ tenantId: options.tenant,
227
+ });
228
+ spinner.stop();
132
229
  if (options.json) {
133
230
  output.json(result);
134
231
  return;
135
232
  }
136
- output.success(result.message);
233
+ if (result.failures.length === 0) {
234
+ output.info(`No failure history found for IP ${ip}`);
235
+ return;
236
+ }
237
+ output.section(`Failure History for ${ip}`);
238
+ output.table(['Time', 'Type', 'Path', 'Method', 'Username', 'API Key', 'Quarantine'], result.failures.map(f => [
239
+ output.formatRelativeTime(f.ts),
240
+ f.failureType,
241
+ f.path?.substring(0, 25) ?? '-',
242
+ f.method ?? '-',
243
+ f.username ?? '-',
244
+ f.apiKeyPrefix ?? '-',
245
+ f.quarantineId ? 'Yes' : '-',
246
+ ]));
247
+ output.info(`Showing ${result.failures.length} entries`);
137
248
  }
138
249
  catch (err) {
139
- spinner.fail('Failed to release quarantine');
140
- throw err;
250
+ spinner.fail('Failed to get failure history');
251
+ output.error(err instanceof Error ? err.message : String(err));
252
+ process.exit(1);
141
253
  }
142
- }
143
- catch (err) {
144
- output.error(err instanceof Error ? err.message : String(err));
145
- process.exit(1);
146
- }
147
- });
148
- // Release all quarantines for an IP
149
- quarantine
150
- .command('release-ip <ip> <reason>')
151
- .description('Release all quarantines for an IP address')
152
- .option('-t, --tenant <tenant>', 'Filter by tenant ID')
153
- .option('-y, --yes', 'Skip confirmation')
154
- .option('--json', 'Output as JSON')
155
- .action(async (ip, reason, options) => {
156
- try {
157
- if (!options.yes) {
158
- const confirmed = await promptConfirm(`Are you sure you want to release all quarantines for IP ${ip}?`);
159
- if (!confirmed) {
160
- output.info('Cancelled');
254
+ });
255
+ }
256
+ // Get quarantine statistics
257
+ {
258
+ const statsCmd = quarantine
259
+ .command('stats')
260
+ .description('Get quarantine statistics');
261
+ t(statsCmd, 'Filter by tenant ID');
262
+ statsCmd
263
+ .option('--json', 'Output as JSON')
264
+ .action(async (options) => {
265
+ const spinner = output.spinner('Fetching statistics...').start();
266
+ try {
267
+ const stats = await client.getQuarantineStats(options.tenant);
268
+ spinner.stop();
269
+ if (options.json) {
270
+ output.json(stats);
161
271
  return;
162
272
  }
273
+ output.section('Quarantine Statistics');
274
+ output.keyValue({
275
+ 'Active Quarantines': stats.activeQuarantines,
276
+ 'Quarantines (24h)': stats.totalQuarantines24h,
277
+ 'Unique IPs (24h)': stats.uniqueIpsQuarantined24h,
278
+ 'Failures (1h)': stats.failuresLast1h,
279
+ 'Failures (24h)': stats.failuresLast24h,
280
+ });
281
+ if (stats.topFailingIps.length > 0) {
282
+ output.section('Top Failing IPs (24h)');
283
+ output.table(['IP Address', 'Failures'], stats.topFailingIps.map(ip => [ip.ip, ip.count]));
284
+ }
285
+ console.log();
286
+ }
287
+ catch (err) {
288
+ spinner.fail('Failed to get statistics');
289
+ output.error(err instanceof Error ? err.message : String(err));
290
+ process.exit(1);
163
291
  }
164
- const spinner = output.spinner('Releasing IP quarantines...').start();
292
+ });
293
+ }
294
+ // Get quarantine configuration
295
+ //
296
+ // Semantics:
297
+ // - Tenant context (top-level `quarantine config`): operates on caller's
298
+ // own tenant (via JWT).
299
+ // - Superadmin context without --tenant: operates on system (null-tenant) config.
300
+ // - Superadmin context with --tenant X: operates on tenant X's config.
301
+ {
302
+ const configCmd = quarantine
303
+ .command('config')
304
+ .description('Get quarantine configuration');
305
+ t(configCmd, 'Tenant ID (omit for system config)');
306
+ configCmd
307
+ .option('--json', 'Output as JSON')
308
+ .action(async (options) => {
309
+ const spinner = output.spinner('Fetching configuration...').start();
165
310
  try {
166
- const result = await client.releaseQuarantineIp(ip, reason, options.tenant);
167
- spinner.succeed('Quarantine(s) released');
311
+ const config = await client.getQuarantineConfig(options.tenant);
312
+ spinner.stop();
168
313
  if (options.json) {
169
- output.json(result);
314
+ output.json(config);
170
315
  return;
171
316
  }
172
- output.success(result.message);
173
- if (result.releasedCount !== undefined) {
174
- output.info(`Released ${result.releasedCount} quarantine(s)`);
317
+ output.section('Quarantine Configuration');
318
+ output.keyValue({
319
+ 'Enabled': config.enabled ? 'Yes' : 'No',
320
+ 'Failures Before Quarantine': config.failuresBeforeQuarantine,
321
+ 'Lockdown Threshold (IPs)': config.lockdownThresholdIps,
322
+ 'Lockdown Window': formatDuration(config.lockdownThresholdWindowSeconds),
323
+ 'Auto-Expire After': formatDuration(config.autoExpireAfterSeconds),
324
+ });
325
+ output.section('Backoff Durations');
326
+ output.keyValue({
327
+ 'Level 1': formatDuration(config.backoffLevel1Seconds),
328
+ 'Level 2': formatDuration(config.backoffLevel2Seconds),
329
+ 'Level 3': formatDuration(config.backoffLevel3Seconds),
330
+ 'Level 4': formatDuration(config.backoffLevel4Seconds),
331
+ 'Level 5': formatDuration(config.backoffLevel5Seconds),
332
+ });
333
+ if (config.ipAllowlist.length > 0) {
334
+ output.section('IP Allowlist');
335
+ config.ipAllowlist.forEach(ip => output.info(` ${ip}`));
175
336
  }
337
+ console.log();
176
338
  }
177
339
  catch (err) {
178
- spinner.fail('Failed to release IP quarantines');
179
- throw err;
340
+ spinner.fail('Failed to get configuration');
341
+ output.error(err instanceof Error ? err.message : String(err));
342
+ process.exit(1);
180
343
  }
181
- }
182
- catch (err) {
183
- output.error(err instanceof Error ? err.message : String(err));
184
- process.exit(1);
185
- }
186
- });
187
- // Get failure history for an IP
188
- quarantine
189
- .command('history <ip>')
190
- .description('Get failure history for an IP address')
191
- .option('-t, --tenant <tenant>', 'Filter by tenant ID')
192
- .option('--limit <number>', 'Number of entries to show', '100')
193
- .option('--json', 'Output as JSON')
194
- .action(async (ip, options) => {
195
- const spinner = output.spinner('Fetching failure history...').start();
196
- try {
197
- const result = await client.getQuarantineHistory(ip, {
198
- limit: parseInt(options.limit, 10),
199
- tenantId: options.tenant,
200
- });
201
- spinner.stop();
202
- if (options.json) {
203
- output.json(result);
204
- return;
344
+ });
345
+ }
346
+ // Set quarantine configuration
347
+ {
348
+ const setConfigCmd = quarantine
349
+ .command('set-config')
350
+ .description('Update quarantine configuration');
351
+ t(setConfigCmd, 'Tenant ID (omit for system config)');
352
+ setConfigCmd
353
+ .option('--enabled <bool>', 'Enable/disable quarantine')
354
+ .option('--failures <num>', 'Failures before quarantine')
355
+ .option('--lockdown-ips <num>', 'Number of IPs to trigger lockdown')
356
+ .option('--lockdown-window <sec>', 'Lockdown threshold window (seconds)')
357
+ .option('--level1 <sec>', 'Level 1 backoff (seconds)')
358
+ .option('--level2 <sec>', 'Level 2 backoff (seconds)')
359
+ .option('--level3 <sec>', 'Level 3 backoff (seconds)')
360
+ .option('--level4 <sec>', 'Level 4 backoff (seconds)')
361
+ .option('--level5 <sec>', 'Level 5 backoff (seconds)')
362
+ .option('--auto-expire <sec>', 'Auto-expire after (seconds)')
363
+ .option('-y, --yes', 'Skip confirmation')
364
+ .option('--json', 'Output as JSON')
365
+ .action(async (options) => {
366
+ const updates = {};
367
+ if (options.enabled !== undefined) {
368
+ updates.enabled = options.enabled === 'true';
205
369
  }
206
- if (result.failures.length === 0) {
207
- output.info(`No failure history found for IP ${ip}`);
208
- return;
370
+ if (options.failures !== undefined) {
371
+ updates.failuresBeforeQuarantine = parseInt(options.failures, 10);
209
372
  }
210
- output.section(`Failure History for ${ip}`);
211
- output.table(['Time', 'Type', 'Path', 'Method', 'Username', 'API Key', 'Quarantine'], result.failures.map(f => [
212
- output.formatRelativeTime(f.ts),
213
- f.failureType,
214
- f.path?.substring(0, 25) ?? '-',
215
- f.method ?? '-',
216
- f.username ?? '-',
217
- f.apiKeyPrefix ?? '-',
218
- f.quarantineId ? 'Yes' : '-',
219
- ]));
220
- output.info(`Showing ${result.failures.length} entries`);
221
- }
222
- catch (err) {
223
- spinner.fail('Failed to get failure history');
224
- output.error(err instanceof Error ? err.message : String(err));
225
- process.exit(1);
226
- }
227
- });
228
- // Get quarantine statistics
229
- quarantine
230
- .command('stats')
231
- .description('Get quarantine statistics')
232
- .option('-t, --tenant <tenant>', 'Filter by tenant ID')
233
- .option('--json', 'Output as JSON')
234
- .action(async (options) => {
235
- const spinner = output.spinner('Fetching statistics...').start();
236
- try {
237
- const stats = await client.getQuarantineStats(options.tenant);
238
- spinner.stop();
239
- if (options.json) {
240
- output.json(stats);
241
- return;
373
+ if (options.lockdownIps !== undefined) {
374
+ updates.lockdownThresholdIps = parseInt(options.lockdownIps, 10);
242
375
  }
243
- output.section('Quarantine Statistics');
244
- output.keyValue({
245
- 'Active Quarantines': stats.activeQuarantines,
246
- 'Quarantines (24h)': stats.totalQuarantines24h,
247
- 'Unique IPs (24h)': stats.uniqueIpsQuarantined24h,
248
- 'Failures (1h)': stats.failuresLast1h,
249
- 'Failures (24h)': stats.failuresLast24h,
250
- });
251
- if (stats.topFailingIps.length > 0) {
252
- output.section('Top Failing IPs (24h)');
253
- output.table(['IP Address', 'Failures'], stats.topFailingIps.map(ip => [ip.ip, ip.count]));
376
+ if (options.lockdownWindow !== undefined) {
377
+ updates.lockdownThresholdWindowSeconds = parseInt(options.lockdownWindow, 10);
254
378
  }
255
- console.log();
256
- }
257
- catch (err) {
258
- spinner.fail('Failed to get statistics');
259
- output.error(err instanceof Error ? err.message : String(err));
260
- process.exit(1);
261
- }
262
- });
263
- // Get quarantine configuration
264
- quarantine
265
- .command('config')
266
- .description('Get quarantine configuration')
267
- .option('-t, --tenant <tenant>', 'Tenant ID (superadmin only)')
268
- .option('--json', 'Output as JSON')
269
- .action(async (options) => {
270
- const spinner = output.spinner('Fetching configuration...').start();
271
- try {
272
- const config = await client.getQuarantineConfig(options.tenant);
273
- spinner.stop();
274
- if (options.json) {
275
- output.json(config);
276
- return;
379
+ if (options.level1 !== undefined) {
380
+ updates.backoffLevel1Seconds = parseInt(options.level1, 10);
277
381
  }
278
- output.section('Quarantine Configuration');
279
- output.keyValue({
280
- 'Enabled': config.enabled ? 'Yes' : 'No',
281
- 'Failures Before Quarantine': config.failuresBeforeQuarantine,
282
- 'Lockdown Threshold (IPs)': config.lockdownThresholdIps,
283
- 'Lockdown Window': formatDuration(config.lockdownThresholdWindowSeconds),
284
- 'Auto-Expire After': formatDuration(config.autoExpireAfterSeconds),
285
- });
286
- output.section('Backoff Durations');
287
- output.keyValue({
288
- 'Level 1': formatDuration(config.backoffLevel1Seconds),
289
- 'Level 2': formatDuration(config.backoffLevel2Seconds),
290
- 'Level 3': formatDuration(config.backoffLevel3Seconds),
291
- 'Level 4': formatDuration(config.backoffLevel4Seconds),
292
- 'Level 5': formatDuration(config.backoffLevel5Seconds),
293
- });
294
- if (config.ipAllowlist.length > 0) {
295
- output.section('IP Allowlist');
296
- config.ipAllowlist.forEach(ip => output.info(` ${ip}`));
382
+ if (options.level2 !== undefined) {
383
+ updates.backoffLevel2Seconds = parseInt(options.level2, 10);
297
384
  }
298
- console.log();
299
- }
300
- catch (err) {
301
- spinner.fail('Failed to get configuration');
302
- output.error(err instanceof Error ? err.message : String(err));
303
- process.exit(1);
304
- }
305
- });
306
- // Set quarantine configuration
307
- quarantine
308
- .command('set-config')
309
- .description('Update quarantine configuration')
310
- .option('-t, --tenant <tenant>', 'Tenant ID (superadmin only)')
311
- .option('--enabled <bool>', 'Enable/disable quarantine')
312
- .option('--failures <num>', 'Failures before quarantine')
313
- .option('--lockdown-ips <num>', 'Number of IPs to trigger lockdown')
314
- .option('--lockdown-window <sec>', 'Lockdown threshold window (seconds)')
315
- .option('--level1 <sec>', 'Level 1 backoff (seconds)')
316
- .option('--level2 <sec>', 'Level 2 backoff (seconds)')
317
- .option('--level3 <sec>', 'Level 3 backoff (seconds)')
318
- .option('--level4 <sec>', 'Level 4 backoff (seconds)')
319
- .option('--level5 <sec>', 'Level 5 backoff (seconds)')
320
- .option('--auto-expire <sec>', 'Auto-expire after (seconds)')
321
- .option('-y, --yes', 'Skip confirmation')
322
- .option('--json', 'Output as JSON')
323
- .action(async (options) => {
324
- const updates = {};
325
- if (options.enabled !== undefined) {
326
- updates.enabled = options.enabled === 'true';
327
- }
328
- if (options.failures !== undefined) {
329
- updates.failuresBeforeQuarantine = parseInt(options.failures, 10);
330
- }
331
- if (options.lockdownIps !== undefined) {
332
- updates.lockdownThresholdIps = parseInt(options.lockdownIps, 10);
333
- }
334
- if (options.lockdownWindow !== undefined) {
335
- updates.lockdownThresholdWindowSeconds = parseInt(options.lockdownWindow, 10);
336
- }
337
- if (options.level1 !== undefined) {
338
- updates.backoffLevel1Seconds = parseInt(options.level1, 10);
339
- }
340
- if (options.level2 !== undefined) {
341
- updates.backoffLevel2Seconds = parseInt(options.level2, 10);
342
- }
343
- if (options.level3 !== undefined) {
344
- updates.backoffLevel3Seconds = parseInt(options.level3, 10);
345
- }
346
- if (options.level4 !== undefined) {
347
- updates.backoffLevel4Seconds = parseInt(options.level4, 10);
348
- }
349
- if (options.level5 !== undefined) {
350
- updates.backoffLevel5Seconds = parseInt(options.level5, 10);
351
- }
352
- if (options.autoExpire !== undefined) {
353
- updates.autoExpireAfterSeconds = parseInt(options.autoExpire, 10);
354
- }
355
- if (Object.keys(updates).length === 0) {
356
- output.error('No configuration options provided');
357
- output.info('Use --help to see available options');
358
- process.exit(1);
359
- }
360
- try {
361
- if (!options.yes) {
362
- output.section('Configuration Changes');
363
- output.keyValue(updates);
364
- const confirmed = await promptConfirm('Apply these changes?');
365
- if (!confirmed) {
366
- output.info('Cancelled');
367
- return;
368
- }
385
+ if (options.level3 !== undefined) {
386
+ updates.backoffLevel3Seconds = parseInt(options.level3, 10);
387
+ }
388
+ if (options.level4 !== undefined) {
389
+ updates.backoffLevel4Seconds = parseInt(options.level4, 10);
390
+ }
391
+ if (options.level5 !== undefined) {
392
+ updates.backoffLevel5Seconds = parseInt(options.level5, 10);
393
+ }
394
+ if (options.autoExpire !== undefined) {
395
+ updates.autoExpireAfterSeconds = parseInt(options.autoExpire, 10);
396
+ }
397
+ if (Object.keys(updates).length === 0) {
398
+ output.error('No configuration options provided');
399
+ output.info('Use --help to see available options');
400
+ process.exit(1);
369
401
  }
370
- const spinner = output.spinner('Updating configuration...').start();
371
402
  try {
372
- const result = await client.updateQuarantineConfig(updates, options.tenant);
373
- spinner.succeed('Configuration updated');
374
- if (options.json) {
375
- output.json(result);
376
- return;
403
+ if (!options.yes) {
404
+ output.section('Configuration Changes');
405
+ output.keyValue(updates);
406
+ const confirmed = await promptConfirm('Apply these changes?');
407
+ if (!confirmed) {
408
+ output.info('Cancelled');
409
+ return;
410
+ }
411
+ }
412
+ const spinner = output.spinner('Updating configuration...').start();
413
+ try {
414
+ const result = await client.updateQuarantineConfig(updates, options.tenant);
415
+ spinner.succeed('Configuration updated');
416
+ if (options.json) {
417
+ output.json(result);
418
+ return;
419
+ }
420
+ output.success('Quarantine configuration updated successfully');
421
+ }
422
+ catch (err) {
423
+ spinner.fail('Failed to update configuration');
424
+ throw err;
377
425
  }
378
- output.success('Quarantine configuration updated successfully');
379
426
  }
380
427
  catch (err) {
381
- spinner.fail('Failed to update configuration');
382
- throw err;
428
+ output.error(err instanceof Error ? err.message : String(err));
429
+ process.exit(1);
383
430
  }
384
- }
385
- catch (err) {
386
- output.error(err instanceof Error ? err.message : String(err));
387
- process.exit(1);
388
- }
389
- });
431
+ });
432
+ }
390
433
  }
391
434
  //# sourceMappingURL=quarantine.js.map