@zincapp/znvault-cli 3.3.1 → 4.0.2
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/README.md +32 -28
- package/dist/commands/advisor.d.ts +14 -1
- package/dist/commands/advisor.d.ts.map +1 -1
- package/dist/commands/advisor.js +355 -331
- package/dist/commands/advisor.js.map +1 -1
- package/dist/commands/agent/index.d.ts +2 -1
- package/dist/commands/agent/index.d.ts.map +1 -1
- package/dist/commands/agent/index.js +23 -19
- package/dist/commands/agent/index.js.map +1 -1
- package/dist/commands/apikey/conditions.d.ts.map +1 -1
- package/dist/commands/apikey/conditions.js +4 -3
- package/dist/commands/apikey/conditions.js.map +1 -1
- package/dist/commands/apikey/create.d.ts.map +1 -1
- package/dist/commands/apikey/create.js +4 -2
- package/dist/commands/apikey/create.js.map +1 -1
- package/dist/commands/apikey/delete.d.ts.map +1 -1
- package/dist/commands/apikey/delete.js +4 -2
- package/dist/commands/apikey/delete.js.map +1 -1
- package/dist/commands/apikey/enable-disable.d.ts.map +1 -1
- package/dist/commands/apikey/enable-disable.js +7 -4
- package/dist/commands/apikey/enable-disable.js.map +1 -1
- package/dist/commands/apikey/helpers.d.ts +16 -0
- package/dist/commands/apikey/helpers.d.ts.map +1 -1
- package/dist/commands/apikey/helpers.js +9 -0
- package/dist/commands/apikey/helpers.js.map +1 -1
- package/dist/commands/apikey/index.d.ts +2 -1
- package/dist/commands/apikey/index.d.ts.map +1 -1
- package/dist/commands/apikey/index.js +23 -14
- package/dist/commands/apikey/index.js.map +1 -1
- package/dist/commands/apikey/list.d.ts.map +1 -1
- package/dist/commands/apikey/list.js +4 -3
- package/dist/commands/apikey/list.js.map +1 -1
- package/dist/commands/apikey/managed/bind.d.ts.map +1 -1
- package/dist/commands/apikey/managed/bind.js +4 -3
- package/dist/commands/apikey/managed/bind.js.map +1 -1
- package/dist/commands/apikey/managed/conditions.d.ts.map +1 -1
- package/dist/commands/apikey/managed/conditions.js +5 -4
- package/dist/commands/apikey/managed/conditions.js.map +1 -1
- package/dist/commands/apikey/managed/config.d.ts.map +1 -1
- package/dist/commands/apikey/managed/config.js +4 -3
- package/dist/commands/apikey/managed/config.js.map +1 -1
- package/dist/commands/apikey/managed/create.d.ts.map +1 -1
- package/dist/commands/apikey/managed/create.js +4 -1
- package/dist/commands/apikey/managed/create.js.map +1 -1
- package/dist/commands/apikey/managed/delete.d.ts.map +1 -1
- package/dist/commands/apikey/managed/delete.js +4 -2
- package/dist/commands/apikey/managed/delete.js.map +1 -1
- package/dist/commands/apikey/managed/get.d.ts.map +1 -1
- package/dist/commands/apikey/managed/get.js +4 -2
- package/dist/commands/apikey/managed/get.js.map +1 -1
- package/dist/commands/apikey/managed/list.d.ts.map +1 -1
- package/dist/commands/apikey/managed/list.js +4 -2
- package/dist/commands/apikey/managed/list.js.map +1 -1
- package/dist/commands/apikey/managed/permissions.d.ts.map +1 -1
- package/dist/commands/apikey/managed/permissions.js +5 -3
- package/dist/commands/apikey/managed/permissions.js.map +1 -1
- package/dist/commands/apikey/managed/rotate.d.ts.map +1 -1
- package/dist/commands/apikey/managed/rotate.js +4 -3
- package/dist/commands/apikey/managed/rotate.js.map +1 -1
- package/dist/commands/apikey/permissions.d.ts.map +1 -1
- package/dist/commands/apikey/permissions.js +5 -3
- package/dist/commands/apikey/permissions.js.map +1 -1
- package/dist/commands/apikey/policies.d.ts.map +1 -1
- package/dist/commands/apikey/policies.js +10 -7
- package/dist/commands/apikey/policies.js.map +1 -1
- package/dist/commands/apikey/rotate.d.ts.map +1 -1
- package/dist/commands/apikey/rotate.js +4 -3
- package/dist/commands/apikey/rotate.js.map +1 -1
- package/dist/commands/apikey/show.d.ts.map +1 -1
- package/dist/commands/apikey/show.js +5 -4
- package/dist/commands/apikey/show.js.map +1 -1
- package/dist/commands/audit.d.ts +2 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +15 -3
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/cluster.d.ts.map +1 -1
- package/dist/commands/cluster.js +31 -14
- package/dist/commands/cluster.js.map +1 -1
- package/dist/commands/group.d.ts +2 -1
- package/dist/commands/group.d.ts.map +1 -1
- package/dist/commands/group.js +61 -39
- package/dist/commands/group.js.map +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +6 -5
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/host/index.d.ts +2 -1
- package/dist/commands/host/index.d.ts.map +1 -1
- package/dist/commands/host/index.js +22 -18
- package/dist/commands/host/index.js.map +1 -1
- package/dist/commands/kms/crud.d.ts +1 -1
- package/dist/commands/kms/crud.d.ts.map +1 -1
- package/dist/commands/kms/crud.js +6 -6
- package/dist/commands/kms/crud.js.map +1 -1
- package/dist/commands/kms/index.d.ts +2 -1
- package/dist/commands/kms/index.d.ts.map +1 -1
- package/dist/commands/kms/index.js +14 -6
- package/dist/commands/kms/index.js.map +1 -1
- package/dist/commands/kms/lifecycle.d.ts +1 -1
- package/dist/commands/kms/lifecycle.d.ts.map +1 -1
- package/dist/commands/kms/lifecycle.js +7 -7
- package/dist/commands/kms/lifecycle.js.map +1 -1
- package/dist/commands/kms/routing.d.ts +5 -0
- package/dist/commands/kms/routing.d.ts.map +1 -1
- package/dist/commands/kms/routing.js +41 -5
- package/dist/commands/kms/routing.js.map +1 -1
- package/dist/commands/policy/attachments.d.ts +9 -5
- package/dist/commands/policy/attachments.d.ts.map +1 -1
- package/dist/commands/policy/attachments.js +16 -13
- package/dist/commands/policy/attachments.js.map +1 -1
- package/dist/commands/policy/crud.d.ts +6 -5
- package/dist/commands/policy/crud.d.ts.map +1 -1
- package/dist/commands/policy/crud.js +16 -11
- package/dist/commands/policy/crud.js.map +1 -1
- package/dist/commands/policy/helpers.d.ts +16 -0
- package/dist/commands/policy/helpers.d.ts.map +1 -1
- package/dist/commands/policy/helpers.js +9 -0
- package/dist/commands/policy/helpers.js.map +1 -1
- package/dist/commands/policy/index.d.ts +2 -1
- package/dist/commands/policy/index.d.ts.map +1 -1
- package/dist/commands/policy/index.js +149 -101
- package/dist/commands/policy/index.js.map +1 -1
- package/dist/commands/policy/io.d.ts +3 -2
- package/dist/commands/policy/io.d.ts.map +1 -1
- package/dist/commands/policy/io.js +7 -5
- package/dist/commands/policy/io.js.map +1 -1
- package/dist/commands/policy/list.d.ts +6 -2
- package/dist/commands/policy/list.d.ts.map +1 -1
- package/dist/commands/policy/list.js +7 -6
- package/dist/commands/policy/list.js.map +1 -1
- package/dist/commands/quarantine.d.ts +2 -1
- package/dist/commands/quarantine.d.ts.map +1 -1
- package/dist/commands/quarantine.js +394 -339
- package/dist/commands/quarantine.js.map +1 -1
- package/dist/commands/role.d.ts +2 -1
- package/dist/commands/role.d.ts.map +1 -1
- package/dist/commands/role.js +92 -49
- package/dist/commands/role.js.map +1 -1
- package/dist/commands/ssh/index.d.ts +2 -1
- package/dist/commands/ssh/index.d.ts.map +1 -1
- package/dist/commands/ssh/index.js +18 -13
- package/dist/commands/ssh/index.js.map +1 -1
- package/dist/commands/sso/crud.d.ts +1 -1
- package/dist/commands/sso/crud.d.ts.map +1 -1
- package/dist/commands/sso/crud.js +16 -16
- package/dist/commands/sso/crud.js.map +1 -1
- package/dist/commands/sso/helpers.d.ts +7 -1
- package/dist/commands/sso/helpers.d.ts.map +1 -1
- package/dist/commands/sso/helpers.js +32 -1
- package/dist/commands/sso/helpers.js.map +1 -1
- package/dist/commands/sso/index.d.ts +2 -1
- package/dist/commands/sso/index.d.ts.map +1 -1
- package/dist/commands/sso/index.js +13 -5
- package/dist/commands/sso/index.js.map +1 -1
- package/dist/commands/superadmin/accounts.d.ts +10 -0
- package/dist/commands/superadmin/accounts.d.ts.map +1 -0
- package/dist/commands/{superadmin.js → superadmin/accounts.js} +16 -15
- package/dist/commands/superadmin/accounts.js.map +1 -0
- package/dist/commands/superadmin/index.d.ts +20 -0
- package/dist/commands/superadmin/index.d.ts.map +1 -0
- package/dist/commands/superadmin/index.js +60 -0
- package/dist/commands/superadmin/index.js.map +1 -0
- package/dist/commands/user.d.ts +2 -1
- package/dist/commands/user.d.ts.map +1 -1
- package/dist/commands/user.js +26 -13
- package/dist/commands/user.js.map +1 -1
- package/dist/index.js +18 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/client/apikeys.d.ts +21 -13
- package/dist/lib/client/apikeys.d.ts.map +1 -1
- package/dist/lib/client/apikeys.js +30 -26
- package/dist/lib/client/apikeys.js.map +1 -1
- package/dist/lib/client/audit.d.ts +54 -1
- package/dist/lib/client/audit.d.ts.map +1 -1
- package/dist/lib/client/audit.js +83 -9
- package/dist/lib/client/audit.js.map +1 -1
- package/dist/lib/client/index.d.ts +37 -36
- package/dist/lib/client/index.d.ts.map +1 -1
- package/dist/lib/client/index.js +37 -36
- package/dist/lib/client/index.js.map +1 -1
- package/dist/lib/client/managed-keys.d.ts +10 -7
- package/dist/lib/client/managed-keys.d.ts.map +1 -1
- package/dist/lib/client/managed-keys.js +16 -16
- package/dist/lib/client/managed-keys.js.map +1 -1
- package/dist/lib/client/policies.d.ts +14 -10
- package/dist/lib/client/policies.d.ts.map +1 -1
- package/dist/lib/client/policies.js +22 -22
- package/dist/lib/client/policies.js.map +1 -1
- package/dist/lib/client/quarantine.d.ts +25 -9
- package/dist/lib/client/quarantine.d.ts.map +1 -1
- package/dist/lib/client/quarantine.js +44 -27
- package/dist/lib/client/quarantine.js.map +1 -1
- package/dist/lib/client/users.d.ts +30 -3
- package/dist/lib/client/users.d.ts.map +1 -1
- package/dist/lib/client/users.js +39 -15
- package/dist/lib/client/users.js.map +1 -1
- package/dist/lib/command-context.d.ts +64 -0
- package/dist/lib/command-context.d.ts.map +1 -0
- package/dist/lib/command-context.js +90 -0
- package/dist/lib/command-context.js.map +1 -0
- package/dist/lib/context-help.d.ts.map +1 -1
- package/dist/lib/context-help.js +5 -0
- package/dist/lib/context-help.js.map +1 -1
- package/dist/lib/db/health.d.ts.map +1 -1
- package/dist/lib/db/health.js +13 -4
- package/dist/lib/db/health.js.map +1 -1
- package/dist/lib/mode.d.ts +18 -4
- package/dist/lib/mode.d.ts.map +1 -1
- package/dist/lib/mode.js +13 -6
- package/dist/lib/mode.js.map +1 -1
- package/dist/tui/hooks/useApi.d.ts +14 -7
- package/dist/tui/hooks/useApi.d.ts.map +1 -1
- package/dist/tui/hooks/useApi.js.map +1 -1
- package/dist/tui/screens/Dashboard.d.ts.map +1 -1
- package/dist/tui/screens/Dashboard.js +2 -3
- package/dist/tui/screens/Dashboard.js.map +1 -1
- package/dist/types/index.d.ts +59 -12
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/commands/superadmin.d.ts +0 -3
- package/dist/commands/superadmin.d.ts.map +0 -1
- package/dist/commands/superadmin.js.map +0 -1
package/dist/commands/advisor.js
CHANGED
|
@@ -1,357 +1,381 @@
|
|
|
1
1
|
// Path: znvault-cli/src/commands/advisor.ts
|
|
2
2
|
import { client } from '../lib/client.js';
|
|
3
3
|
import * as output from '../lib/output.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { resolveContext, withRegisterContext, } from '../lib/command-context.js';
|
|
5
|
+
/**
|
|
6
|
+
* Register the `advisor` command group.
|
|
7
|
+
*
|
|
8
|
+
* Context semantics:
|
|
9
|
+
* - `tenant` (default, top-level): exposes `audit`, `suggest`, and `rules`
|
|
10
|
+
* (read-only catalog). No `--tenant` option. No `llm` subgroup.
|
|
11
|
+
* - `superadmin` (under `znvault superadmin advisor`): exposes `audit` and
|
|
12
|
+
* `suggest` with `--tenant`, plus the system-wide `llm` subgroup.
|
|
13
|
+
*
|
|
14
|
+
* Note: `rules` is the read-only rule catalog and is intentionally NOT
|
|
15
|
+
* mirrored to superadmin — it's tenant-context only.
|
|
16
|
+
*/
|
|
17
|
+
export function registerAdvisorCommands(parent, opts) {
|
|
18
|
+
const ctx = resolveContext(opts);
|
|
19
|
+
withRegisterContext(ctx, () => { registerAdvisorCommandsInner(parent, ctx); });
|
|
20
|
+
}
|
|
21
|
+
function registerAdvisorCommandsInner(parent, ctx) {
|
|
22
|
+
const asSuperadmin = ctx === 'superadmin';
|
|
23
|
+
const advisor = parent
|
|
7
24
|
.command('advisor')
|
|
8
|
-
.description(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
25
|
+
.description(ctx === 'superadmin'
|
|
26
|
+
? 'AI-powered security advisor (cross-tenant)'
|
|
27
|
+
: 'AI-powered security advisor commands');
|
|
28
|
+
// Helper: only register --tenant in superadmin context
|
|
29
|
+
const t = (cmd, desc) => ctx === 'superadmin' ? cmd.option('--tenant <id>', desc) : cmd;
|
|
30
|
+
// ========== audit (dual-purpose) ==========
|
|
31
|
+
{
|
|
32
|
+
const auditCmd = advisor
|
|
33
|
+
.command('audit')
|
|
34
|
+
.description('Run a security audit')
|
|
35
|
+
.option('--category <category>', 'Filter by category (security, organization, compliance)')
|
|
36
|
+
.option('--severity <severity>', 'Filter by severity (critical, high, medium, low)')
|
|
37
|
+
.option('--ai-summary', 'Include AI-generated summary')
|
|
38
|
+
.option('--json', 'Output as JSON');
|
|
39
|
+
t(auditCmd, 'Target tenant ID (superadmin cross-tenant)');
|
|
40
|
+
auditCmd.action(async (options) => {
|
|
41
|
+
const spinner = output.spinner('Running security audit...').start();
|
|
42
|
+
try {
|
|
43
|
+
const body = {};
|
|
44
|
+
if (options.category) {
|
|
45
|
+
body.categories = [options.category];
|
|
46
|
+
}
|
|
47
|
+
if (options.severity) {
|
|
48
|
+
body.severity = [options.severity];
|
|
49
|
+
}
|
|
50
|
+
if (options.aiSummary) {
|
|
51
|
+
body.includeAiSummary = true;
|
|
52
|
+
}
|
|
53
|
+
// Superadmin context (with or without --tenant) must use the
|
|
54
|
+
// admin surface; the tenant route rejects pure superadmin principals.
|
|
55
|
+
const path = asSuperadmin
|
|
56
|
+
? `/v1/superadmin/advisor/audit${options.tenant ? `?tenantId=${encodeURIComponent(options.tenant)}` : ''}`
|
|
57
|
+
: '/v1/advisor/audit';
|
|
58
|
+
const response = await client.post(path, body);
|
|
59
|
+
spinner.stop();
|
|
60
|
+
if (options.json) {
|
|
61
|
+
output.json(response.data);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const result = response.data;
|
|
65
|
+
// Summary header
|
|
66
|
+
output.section('Audit Summary');
|
|
67
|
+
output.keyValue({
|
|
68
|
+
'Audit ID': result.auditId,
|
|
69
|
+
'Tenant': result.tenantId,
|
|
70
|
+
'Status': result.status,
|
|
71
|
+
'Started': output.formatRelativeTime(result.startedAt),
|
|
72
|
+
'Total Findings': result.summary.total,
|
|
73
|
+
});
|
|
74
|
+
// Severity breakdown
|
|
75
|
+
if (result.summary.total > 0) {
|
|
76
|
+
output.section('By Severity');
|
|
77
|
+
const severityColors = {
|
|
78
|
+
critical: '\x1b[31m', // red
|
|
79
|
+
high: '\x1b[33m', // yellow
|
|
80
|
+
medium: '\x1b[36m', // cyan
|
|
81
|
+
low: '\x1b[37m', // white
|
|
82
|
+
};
|
|
83
|
+
const reset = '\x1b[0m';
|
|
84
|
+
for (const [sev, count] of Object.entries(result.summary.bySeverity)) {
|
|
85
|
+
if (count > 0) {
|
|
86
|
+
const color = severityColors[sev] || '';
|
|
87
|
+
console.log(` ${color}${sev.toUpperCase()}: ${count}${reset}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Findings table
|
|
91
|
+
output.section('Findings');
|
|
92
|
+
output.table(['Severity', 'Rule', 'Description', 'Resource'], result.findings.slice(0, 20).map(f => [
|
|
93
|
+
f.severity.toUpperCase(),
|
|
94
|
+
f.ruleId,
|
|
95
|
+
f.description.substring(0, 40) + (f.description.length > 40 ? '...' : ''),
|
|
96
|
+
`${f.resourceType}:${f.resourceId.substring(0, 8)}`,
|
|
97
|
+
]));
|
|
98
|
+
if (result.findings.length > 20) {
|
|
99
|
+
output.info(`... and ${result.findings.length - 20} more findings`);
|
|
68
100
|
}
|
|
69
101
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
]));
|
|
78
|
-
if (result.findings.length > 20) {
|
|
79
|
-
output.info(`... and ${result.findings.length - 20} more findings`);
|
|
102
|
+
else {
|
|
103
|
+
output.success('No security findings detected!');
|
|
104
|
+
}
|
|
105
|
+
// AI Summary
|
|
106
|
+
if (result.aiSummary) {
|
|
107
|
+
output.section('AI Summary');
|
|
108
|
+
console.log(result.aiSummary);
|
|
80
109
|
}
|
|
81
110
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (result.aiSummary) {
|
|
87
|
-
output.section('AI Summary');
|
|
88
|
-
console.log(result.aiSummary);
|
|
111
|
+
catch (err) {
|
|
112
|
+
spinner.fail('Failed to run audit');
|
|
113
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
114
|
+
process.exit(1);
|
|
89
115
|
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
output.
|
|
117
|
-
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// ========== rules (tenant-only catalog) ==========
|
|
119
|
+
if (ctx === 'tenant') {
|
|
120
|
+
advisor
|
|
121
|
+
.command('rules')
|
|
122
|
+
.description('List available security rules')
|
|
123
|
+
.option('--category <category>', 'Filter by category')
|
|
124
|
+
.option('--severity <severity>', 'Filter by severity')
|
|
125
|
+
.option('--json', 'Output as JSON')
|
|
126
|
+
.action(async (options) => {
|
|
127
|
+
const spinner = output.spinner('Fetching rules...').start();
|
|
128
|
+
try {
|
|
129
|
+
const query = [];
|
|
130
|
+
if (options.category)
|
|
131
|
+
query.push(`category=${options.category}`);
|
|
132
|
+
if (options.severity)
|
|
133
|
+
query.push(`severity=${options.severity}`);
|
|
134
|
+
const path = query.length > 0 ? `/v1/advisor/rules?${query.join('&')}` : '/v1/advisor/rules';
|
|
135
|
+
const response = await client.get(path);
|
|
136
|
+
spinner.stop();
|
|
137
|
+
if (options.json) {
|
|
138
|
+
output.json(response.data);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const rules = response.data;
|
|
142
|
+
output.table(['ID', 'Name', 'Category', 'Severity'], rules.map(r => [
|
|
143
|
+
r.id,
|
|
144
|
+
r.name.substring(0, 30),
|
|
145
|
+
r.category,
|
|
146
|
+
r.severity.toUpperCase(),
|
|
147
|
+
]));
|
|
148
|
+
output.info(`Total: ${rules.length} rules`);
|
|
118
149
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
r.category,
|
|
124
|
-
r.severity.toUpperCase(),
|
|
125
|
-
]));
|
|
126
|
-
output.info(`Total: ${rules.length} rules`);
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
spinner.fail('Failed to list rules');
|
|
130
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
// Suggest secret configuration
|
|
135
|
-
const suggestCmd = advisor
|
|
136
|
-
.command('suggest')
|
|
137
|
-
.description('Get AI suggestions for naming and configuring a new secret')
|
|
138
|
-
.argument('<description>', 'Description of the secret (e.g., "stripe api key for payments")')
|
|
139
|
-
.option('--environment <env>', 'Environment hint (prod, staging, dev)')
|
|
140
|
-
.option('--service <name>', 'Service name hint')
|
|
141
|
-
.option('--team <name>', 'Team name hint')
|
|
142
|
-
.option('--json', 'Output as JSON');
|
|
143
|
-
// Add tenant option (hidden for tenant users)
|
|
144
|
-
addTenantOption(suggestCmd);
|
|
145
|
-
suggestCmd.action(async (description, options) => {
|
|
146
|
-
const spinner = output.spinner('Getting AI suggestions...').start();
|
|
147
|
-
try {
|
|
148
|
-
const body = { description };
|
|
149
|
-
const hints = {};
|
|
150
|
-
if (options.environment)
|
|
151
|
-
hints.environment = options.environment;
|
|
152
|
-
if (options.service)
|
|
153
|
-
hints.service = options.service;
|
|
154
|
-
if (options.team)
|
|
155
|
-
hints.team = options.team;
|
|
156
|
-
if (Object.keys(hints).length > 0)
|
|
157
|
-
body.hints = hints;
|
|
158
|
-
const path = options.tenant
|
|
159
|
-
? `/v1/superadmin/advisor/suggest?tenantId=${encodeURIComponent(options.tenant)}`
|
|
160
|
-
: '/v1/advisor/suggest';
|
|
161
|
-
const response = await client.post(path, body);
|
|
162
|
-
spinner.stop();
|
|
163
|
-
if (options.json) {
|
|
164
|
-
output.json(response.data);
|
|
165
|
-
return;
|
|
150
|
+
catch (err) {
|
|
151
|
+
spinner.fail('Failed to list rules');
|
|
152
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
153
|
+
process.exit(1);
|
|
166
154
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// ========== suggest (dual-purpose) ==========
|
|
158
|
+
{
|
|
159
|
+
const suggestCmd = advisor
|
|
160
|
+
.command('suggest')
|
|
161
|
+
.description('Get AI suggestions for naming and configuring a new secret')
|
|
162
|
+
.argument('<description>', 'Description of the secret (e.g., "stripe api key for payments")')
|
|
163
|
+
.option('--environment <env>', 'Environment hint (prod, staging, dev)')
|
|
164
|
+
.option('--service <name>', 'Service name hint')
|
|
165
|
+
.option('--team <name>', 'Team name hint')
|
|
166
|
+
.option('--json', 'Output as JSON');
|
|
167
|
+
t(suggestCmd, 'Target tenant ID (superadmin cross-tenant)');
|
|
168
|
+
suggestCmd.action(async (description, options) => {
|
|
169
|
+
const spinner = output.spinner('Getting AI suggestions...').start();
|
|
170
|
+
try {
|
|
171
|
+
const body = { description };
|
|
172
|
+
const hints = {};
|
|
173
|
+
if (options.environment)
|
|
174
|
+
hints.environment = options.environment;
|
|
175
|
+
if (options.service)
|
|
176
|
+
hints.service = options.service;
|
|
177
|
+
if (options.team)
|
|
178
|
+
hints.team = options.team;
|
|
179
|
+
if (Object.keys(hints).length > 0)
|
|
180
|
+
body.hints = hints;
|
|
181
|
+
const path = asSuperadmin
|
|
182
|
+
? `/v1/superadmin/advisor/suggest${options.tenant ? `?tenantId=${encodeURIComponent(options.tenant)}` : ''}`
|
|
183
|
+
: '/v1/advisor/suggest';
|
|
184
|
+
const response = await client.post(path, body);
|
|
185
|
+
spinner.stop();
|
|
186
|
+
if (options.json) {
|
|
187
|
+
output.json(response.data);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const result = response.data;
|
|
191
|
+
output.section('Suggested Configuration');
|
|
192
|
+
output.keyValue({
|
|
193
|
+
'Alias': result.alias,
|
|
194
|
+
'Type': result.type,
|
|
195
|
+
'Sub-Type': result.subType ?? '-',
|
|
196
|
+
'Tags': result.tags.length > 0 ? result.tags.join(', ') : 'none',
|
|
197
|
+
'Expiration': result.expiresInDays ? `${result.expiresInDays} days` : '-',
|
|
198
|
+
'Rotation': result.rotationRecommendation ?? '-',
|
|
199
|
+
'Confidence': `${Math.round(result.confidence * 100)}%`,
|
|
200
|
+
});
|
|
201
|
+
if (result.alternativeAliases && result.alternativeAliases.length > 0) {
|
|
202
|
+
output.section('Alternative Names');
|
|
203
|
+
result.alternativeAliases.forEach(a => { console.log(` - ${a}`); });
|
|
204
|
+
}
|
|
205
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
206
|
+
output.section('Warnings');
|
|
207
|
+
result.warnings.forEach(w => { console.log(` ⚠ ${w}`); });
|
|
208
|
+
}
|
|
209
|
+
output.section('Reasoning');
|
|
210
|
+
console.log(` ${result.reasoning}`);
|
|
181
211
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
212
|
+
catch (err) {
|
|
213
|
+
spinner.fail('Failed to get suggestions');
|
|
214
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
215
|
+
process.exit(1);
|
|
185
216
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// ========== llm subgroup (superadmin-only) ==========
|
|
220
|
+
if (ctx === 'superadmin') {
|
|
221
|
+
const llm = advisor
|
|
222
|
+
.command('llm')
|
|
223
|
+
.description('Manage LLM configuration for AI features (system-wide)');
|
|
224
|
+
// Check LLM status
|
|
225
|
+
llm
|
|
226
|
+
.command('status')
|
|
227
|
+
.description('Check LLM configuration status')
|
|
228
|
+
.option('--json', 'Output as JSON')
|
|
229
|
+
.action(async (options) => {
|
|
230
|
+
const spinner = output.spinner('Checking LLM status...').start();
|
|
231
|
+
try {
|
|
232
|
+
const response = await client.get('/v1/advisor/llm/status');
|
|
233
|
+
spinner.stop();
|
|
234
|
+
if (options.json) {
|
|
235
|
+
output.json(response.data);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const status = response.data;
|
|
239
|
+
output.keyValue({
|
|
240
|
+
'Configured': status.configured ? 'Yes' : 'No',
|
|
241
|
+
'Enabled': status.enabled ? 'Yes' : 'No',
|
|
242
|
+
'Provider': status.provider ?? '-',
|
|
243
|
+
'Model': status.model ?? '-',
|
|
244
|
+
});
|
|
245
|
+
if (!status.configured) {
|
|
246
|
+
output.info('\nTo enable AI features, configure LLM with:');
|
|
247
|
+
output.info(' znvault superadmin advisor llm config --api-key <key> --enabled true');
|
|
248
|
+
}
|
|
214
249
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
'Provider': status.provider ?? '-',
|
|
220
|
-
'Model': status.model ?? '-',
|
|
221
|
-
});
|
|
222
|
-
if (!status.configured) {
|
|
223
|
-
output.info('\nTo enable AI features, configure LLM with:');
|
|
224
|
-
output.info(' znvault advisor llm config --api-key <key> --enabled true');
|
|
250
|
+
catch (err) {
|
|
251
|
+
spinner.fail('Failed to check LLM status');
|
|
252
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
253
|
+
process.exit(1);
|
|
225
254
|
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
255
|
+
});
|
|
256
|
+
// Get LLM config
|
|
257
|
+
llm
|
|
258
|
+
.command('get')
|
|
259
|
+
.description('Get LLM configuration')
|
|
260
|
+
.option('--json', 'Output as JSON')
|
|
261
|
+
.action(async (options) => {
|
|
262
|
+
const spinner = output.spinner('Fetching LLM configuration...').start();
|
|
263
|
+
try {
|
|
264
|
+
const response = await client.get('/v1/admin/advisor/llm/config');
|
|
265
|
+
spinner.stop();
|
|
266
|
+
if (options.json) {
|
|
267
|
+
output.json(response.data);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const config = response.data;
|
|
271
|
+
if (!config) {
|
|
272
|
+
output.info('LLM is not configured');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
output.keyValue({
|
|
276
|
+
'Provider': config.provider,
|
|
277
|
+
'API Key': config.apiKeyConfigured ? `${config.apiKeyPrefix ?? ''}...` : 'Not set',
|
|
278
|
+
'Model': config.model,
|
|
279
|
+
'Max Tokens': config.maxTokens,
|
|
280
|
+
'Enabled': config.enabled ? 'Yes' : 'No',
|
|
281
|
+
});
|
|
246
282
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
output.
|
|
250
|
-
|
|
283
|
+
catch (err) {
|
|
284
|
+
spinner.fail('Failed to get LLM configuration');
|
|
285
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
286
|
+
process.exit(1);
|
|
251
287
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (options.model)
|
|
284
|
-
body.model = options.model;
|
|
285
|
-
if (options.maxTokens)
|
|
286
|
-
body.maxTokens = parseInt(options.maxTokens, 10);
|
|
287
|
-
if (options.enabled !== undefined)
|
|
288
|
-
body.enabled = options.enabled === 'true';
|
|
289
|
-
if (Object.keys(body).length === 0) {
|
|
288
|
+
});
|
|
289
|
+
// Update LLM config
|
|
290
|
+
llm
|
|
291
|
+
.command('config')
|
|
292
|
+
.description('Update LLM configuration')
|
|
293
|
+
.option('--provider <provider>', 'LLM provider (anthropic)')
|
|
294
|
+
.option('--api-key <key>', 'API key for the provider')
|
|
295
|
+
.option('--model <model>', 'Model to use (e.g., claude-3-5-haiku-latest)')
|
|
296
|
+
.option('--max-tokens <tokens>', 'Maximum tokens for responses')
|
|
297
|
+
.option('--enabled <bool>', 'Enable or disable LLM features (true/false)')
|
|
298
|
+
.action(async (options) => {
|
|
299
|
+
const spinner = output.spinner('Updating LLM configuration...').start();
|
|
300
|
+
try {
|
|
301
|
+
const body = {};
|
|
302
|
+
if (options.provider)
|
|
303
|
+
body.provider = options.provider;
|
|
304
|
+
if (options.apiKey)
|
|
305
|
+
body.apiKey = options.apiKey;
|
|
306
|
+
if (options.model)
|
|
307
|
+
body.model = options.model;
|
|
308
|
+
if (options.maxTokens)
|
|
309
|
+
body.maxTokens = parseInt(options.maxTokens, 10);
|
|
310
|
+
if (options.enabled !== undefined)
|
|
311
|
+
body.enabled = options.enabled === 'true';
|
|
312
|
+
if (Object.keys(body).length === 0) {
|
|
313
|
+
spinner.stop();
|
|
314
|
+
output.error('No configuration options provided');
|
|
315
|
+
output.info('Use --help to see available options');
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
const response = await client.put('/v1/admin/advisor/llm/config', body);
|
|
290
319
|
spinner.stop();
|
|
291
|
-
output.
|
|
292
|
-
output.
|
|
320
|
+
output.success('LLM configuration updated');
|
|
321
|
+
output.keyValue({
|
|
322
|
+
'Provider': response.data.provider,
|
|
323
|
+
'API Key': response.data.apiKeyConfigured ? `${response.data.apiKeyPrefix ?? ''}...` : 'Not set',
|
|
324
|
+
'Model': response.data.model,
|
|
325
|
+
'Max Tokens': response.data.maxTokens,
|
|
326
|
+
'Enabled': response.data.enabled ? 'Yes' : 'No',
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
spinner.fail('Failed to update LLM configuration');
|
|
331
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
293
332
|
process.exit(1);
|
|
294
333
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
});
|
|
312
|
-
// Test LLM connection (superadmin only)
|
|
313
|
-
llm
|
|
314
|
-
.command('test')
|
|
315
|
-
.description(superadminDesc('Test LLM connection'))
|
|
316
|
-
.action(async () => {
|
|
317
|
-
const spinner = output.spinner('Testing LLM connection...').start();
|
|
318
|
-
try {
|
|
319
|
-
const response = await client.post('/v1/admin/advisor/llm/test', {});
|
|
320
|
-
spinner.stop();
|
|
321
|
-
const result = response.data;
|
|
322
|
-
if (result.success) {
|
|
323
|
-
output.success(result.message);
|
|
324
|
-
if (result.model) {
|
|
325
|
-
output.info(`Model: ${result.model}`);
|
|
334
|
+
});
|
|
335
|
+
// Test LLM connection
|
|
336
|
+
llm
|
|
337
|
+
.command('test')
|
|
338
|
+
.description('Test LLM connection')
|
|
339
|
+
.action(async () => {
|
|
340
|
+
const spinner = output.spinner('Testing LLM connection...').start();
|
|
341
|
+
try {
|
|
342
|
+
const response = await client.post('/v1/admin/advisor/llm/test', {});
|
|
343
|
+
spinner.stop();
|
|
344
|
+
const result = response.data;
|
|
345
|
+
if (result.success) {
|
|
346
|
+
output.success(result.message);
|
|
347
|
+
if (result.model) {
|
|
348
|
+
output.info(`Model: ${result.model}`);
|
|
349
|
+
}
|
|
326
350
|
}
|
|
351
|
+
else {
|
|
352
|
+
output.error(result.message);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
spinner.fail('Failed to test LLM connection');
|
|
358
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
// Delete LLM config
|
|
363
|
+
llm
|
|
364
|
+
.command('delete')
|
|
365
|
+
.description('Delete LLM configuration')
|
|
366
|
+
.action(async () => {
|
|
367
|
+
const spinner = output.spinner('Deleting LLM configuration...').start();
|
|
368
|
+
try {
|
|
369
|
+
await client.delete('/v1/admin/advisor/llm/config');
|
|
370
|
+
spinner.stop();
|
|
371
|
+
output.success('LLM configuration deleted');
|
|
327
372
|
}
|
|
328
|
-
|
|
329
|
-
|
|
373
|
+
catch (err) {
|
|
374
|
+
spinner.fail('Failed to delete LLM configuration');
|
|
375
|
+
output.error(err instanceof Error ? err.message : String(err));
|
|
330
376
|
process.exit(1);
|
|
331
377
|
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
spinner.fail('Failed to test LLM connection');
|
|
335
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
336
|
-
process.exit(1);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
// Delete LLM config (superadmin only)
|
|
340
|
-
llm
|
|
341
|
-
.command('delete')
|
|
342
|
-
.description(superadminDesc('Delete LLM configuration'))
|
|
343
|
-
.action(async () => {
|
|
344
|
-
const spinner = output.spinner('Deleting LLM configuration...').start();
|
|
345
|
-
try {
|
|
346
|
-
await client.delete('/v1/admin/advisor/llm/config');
|
|
347
|
-
spinner.stop();
|
|
348
|
-
output.success('LLM configuration deleted');
|
|
349
|
-
}
|
|
350
|
-
catch (err) {
|
|
351
|
-
spinner.fail('Failed to delete LLM configuration');
|
|
352
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
});
|
|
378
|
+
});
|
|
379
|
+
}
|
|
356
380
|
}
|
|
357
381
|
//# sourceMappingURL=advisor.js.map
|