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