@zincapp/znvault-cli 3.3.1 → 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 (74) 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 -331
  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 -339
  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/ssh/index.d.ts +2 -1
  43. package/dist/commands/ssh/index.d.ts.map +1 -1
  44. package/dist/commands/ssh/index.js +18 -13
  45. package/dist/commands/ssh/index.js.map +1 -1
  46. package/dist/commands/sso/index.d.ts +2 -1
  47. package/dist/commands/sso/index.d.ts.map +1 -1
  48. package/dist/commands/sso/index.js +8 -5
  49. package/dist/commands/sso/index.js.map +1 -1
  50. package/dist/commands/superadmin/accounts.d.ts +10 -0
  51. package/dist/commands/superadmin/accounts.d.ts.map +1 -0
  52. package/dist/commands/{superadmin.js → superadmin/accounts.js} +16 -15
  53. package/dist/commands/superadmin/accounts.js.map +1 -0
  54. package/dist/commands/superadmin/index.d.ts +20 -0
  55. package/dist/commands/superadmin/index.d.ts.map +1 -0
  56. package/dist/commands/superadmin/index.js +60 -0
  57. package/dist/commands/superadmin/index.js.map +1 -0
  58. package/dist/commands/user.d.ts +2 -1
  59. package/dist/commands/user.d.ts.map +1 -1
  60. package/dist/commands/user.js +15 -8
  61. package/dist/commands/user.js.map +1 -1
  62. package/dist/index.js +18 -13
  63. package/dist/index.js.map +1 -1
  64. package/dist/lib/command-context.d.ts +49 -0
  65. package/dist/lib/command-context.d.ts.map +1 -0
  66. package/dist/lib/command-context.js +81 -0
  67. package/dist/lib/command-context.js.map +1 -0
  68. package/dist/lib/context-help.d.ts.map +1 -1
  69. package/dist/lib/context-help.js +5 -0
  70. package/dist/lib/context-help.js.map +1 -1
  71. package/package.json +1 -1
  72. package/dist/commands/superadmin.d.ts +0 -3
  73. package/dist/commands/superadmin.d.ts.map +0 -1
  74. package/dist/commands/superadmin.js.map +0 -1
@@ -1,357 +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 { configureContextHelp, addTenantOption, superadminDesc, } from '../lib/context-help.js';
5
- export function registerAdvisorCommands(program) {
6
- const advisor = program
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('AI-powered security advisor commands');
9
- // Configure context-aware help for this command group
10
- configureContextHelp(advisor);
11
- // Run security audit
12
- const auditCmd = advisor
13
- .command('audit')
14
- .description('Run a security audit (tenant inferred from auth)')
15
- .option('--category <category>', 'Filter by category (security, organization, compliance)')
16
- .option('--severity <severity>', 'Filter by severity (critical, high, medium, low)')
17
- .option('--ai-summary', 'Include AI-generated summary')
18
- .option('--json', 'Output as JSON');
19
- // Add tenant option (hidden for tenant users)
20
- addTenantOption(auditCmd);
21
- auditCmd
22
- .action(async (options) => {
23
- const spinner = output.spinner('Running security audit...').start();
24
- try {
25
- const body = {};
26
- if (options.category) {
27
- body.categories = [options.category];
28
- }
29
- if (options.severity) {
30
- body.severity = [options.severity];
31
- }
32
- if (options.aiSummary) {
33
- body.includeAiSummary = true;
34
- }
35
- const path = options.tenant
36
- ? `/v1/superadmin/advisor/audit?tenantId=${encodeURIComponent(options.tenant)}`
37
- : '/v1/advisor/audit';
38
- const response = await client.post(path, body);
39
- spinner.stop();
40
- if (options.json) {
41
- output.json(response.data);
42
- return;
43
- }
44
- const result = response.data;
45
- // Summary header
46
- output.section('Audit Summary');
47
- output.keyValue({
48
- 'Audit ID': result.auditId,
49
- 'Tenant': result.tenantId,
50
- 'Status': result.status,
51
- 'Started': output.formatRelativeTime(result.startedAt),
52
- 'Total Findings': result.summary.total,
53
- });
54
- // Severity breakdown
55
- if (result.summary.total > 0) {
56
- output.section('By Severity');
57
- const severityColors = {
58
- critical: '\x1b[31m', // red
59
- high: '\x1b[33m', // yellow
60
- medium: '\x1b[36m', // cyan
61
- low: '\x1b[37m', // white
62
- };
63
- const reset = '\x1b[0m';
64
- for (const [sev, count] of Object.entries(result.summary.bySeverity)) {
65
- if (count > 0) {
66
- const color = severityColors[sev] || '';
67
- console.log(` ${color}${sev.toUpperCase()}: ${count}${reset}`);
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`);
68
97
  }
69
98
  }
70
- // Findings table
71
- output.section('Findings');
72
- output.table(['Severity', 'Rule', 'Description', 'Resource'], result.findings.slice(0, 20).map(f => [
73
- f.severity.toUpperCase(),
74
- f.ruleId,
75
- f.description.substring(0, 40) + (f.description.length > 40 ? '...' : ''),
76
- `${f.resourceType}:${f.resourceId.substring(0, 8)}`,
77
- ]));
78
- if (result.findings.length > 20) {
79
- 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);
80
106
  }
81
107
  }
82
- else {
83
- output.success('No security findings detected!');
84
- }
85
- // AI Summary
86
- if (result.aiSummary) {
87
- output.section('AI Summary');
88
- 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);
89
112
  }
90
- }
91
- catch (err) {
92
- spinner.fail('Failed to run audit');
93
- output.error(err instanceof Error ? err.message : String(err));
94
- process.exit(1);
95
- }
96
- });
97
- // List available rules
98
- advisor
99
- .command('rules')
100
- .description('List available security rules')
101
- .option('--category <category>', 'Filter by category')
102
- .option('--severity <severity>', 'Filter by severity')
103
- .option('--json', 'Output as JSON')
104
- .action(async (options) => {
105
- const spinner = output.spinner('Fetching rules...').start();
106
- try {
107
- const query = [];
108
- if (options.category)
109
- query.push(`category=${options.category}`);
110
- if (options.severity)
111
- query.push(`severity=${options.severity}`);
112
- const path = query.length > 0 ? `/v1/advisor/rules?${query.join('&')}` : '/v1/advisor/rules';
113
- const response = await client.get(path);
114
- spinner.stop();
115
- if (options.json) {
116
- output.json(response.data);
117
- return;
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`);
118
146
  }
119
- const rules = response.data;
120
- output.table(['ID', 'Name', 'Category', 'Severity'], rules.map(r => [
121
- r.id,
122
- r.name.substring(0, 30),
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;
147
+ catch (err) {
148
+ spinner.fail('Failed to list rules');
149
+ output.error(err instanceof Error ? err.message : String(err));
150
+ process.exit(1);
166
151
  }
167
- const result = response.data;
168
- output.section('Suggested Configuration');
169
- output.keyValue({
170
- 'Alias': result.alias,
171
- 'Type': result.type,
172
- 'Sub-Type': result.subType ?? '-',
173
- 'Tags': result.tags.length > 0 ? result.tags.join(', ') : 'none',
174
- 'Expiration': result.expiresInDays ? `${result.expiresInDays} days` : '-',
175
- 'Rotation': result.rotationRecommendation ?? '-',
176
- 'Confidence': `${Math.round(result.confidence * 100)}%`,
177
- });
178
- if (result.alternativeAliases && result.alternativeAliases.length > 0) {
179
- output.section('Alternative Names');
180
- result.alternativeAliases.forEach(a => { console.log(` - ${a}`); });
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}`);
181
208
  }
182
- if (result.warnings && result.warnings.length > 0) {
183
- output.section('Warnings');
184
- result.warnings.forEach(w => { console.log(` ⚠ ${w}`); });
209
+ catch (err) {
210
+ spinner.fail('Failed to get suggestions');
211
+ output.error(err instanceof Error ? err.message : String(err));
212
+ process.exit(1);
185
213
  }
186
- output.section('Reasoning');
187
- console.log(` ${result.reasoning}`);
188
- }
189
- catch (err) {
190
- spinner.fail('Failed to get suggestions');
191
- output.error(err instanceof Error ? err.message : String(err));
192
- process.exit(1);
193
- }
194
- });
195
- // LLM configuration subcommand
196
- const llm = advisor
197
- .command('llm')
198
- .description('Manage LLM configuration for AI features');
199
- // Configure context-aware help for LLM subcommands
200
- configureContextHelp(llm);
201
- // Check LLM status
202
- llm
203
- .command('status')
204
- .description('Check LLM configuration status')
205
- .option('--json', 'Output as JSON')
206
- .action(async (options) => {
207
- const spinner = output.spinner('Checking LLM status...').start();
208
- try {
209
- const response = await client.get('/v1/advisor/llm/status');
210
- spinner.stop();
211
- if (options.json) {
212
- output.json(response.data);
213
- return;
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
+ }
214
246
  }
215
- const status = response.data;
216
- output.keyValue({
217
- 'Configured': status.configured ? 'Yes' : 'No',
218
- 'Enabled': status.enabled ? 'Yes' : 'No',
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');
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);
225
251
  }
226
- }
227
- catch (err) {
228
- spinner.fail('Failed to check LLM status');
229
- output.error(err instanceof Error ? err.message : String(err));
230
- process.exit(1);
231
- }
232
- });
233
- // Get LLM config (superadmin only)
234
- llm
235
- .command('get')
236
- .description(superadminDesc('Get LLM configuration'))
237
- .option('--json', 'Output as JSON')
238
- .action(async (options) => {
239
- const spinner = output.spinner('Fetching LLM configuration...').start();
240
- try {
241
- const response = await client.get('/v1/admin/advisor/llm/config');
242
- spinner.stop();
243
- if (options.json) {
244
- output.json(response.data);
245
- return;
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
+ });
246
279
  }
247
- const config = response.data;
248
- if (!config) {
249
- output.info('LLM is not configured');
250
- return;
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);
251
284
  }
252
- output.keyValue({
253
- 'Provider': config.provider,
254
- 'API Key': config.apiKeyConfigured ? `${config.apiKeyPrefix ?? ''}...` : 'Not set',
255
- 'Model': config.model,
256
- 'Max Tokens': config.maxTokens,
257
- 'Enabled': config.enabled ? 'Yes' : 'No',
258
- });
259
- }
260
- catch (err) {
261
- spinner.fail('Failed to get LLM configuration');
262
- output.error(err instanceof Error ? err.message : String(err));
263
- process.exit(1);
264
- }
265
- });
266
- // Update LLM config (superadmin only)
267
- llm
268
- .command('config')
269
- .description(superadminDesc('Update LLM configuration'))
270
- .option('--provider <provider>', 'LLM provider (anthropic)')
271
- .option('--api-key <key>', 'API key for the provider')
272
- .option('--model <model>', 'Model to use (e.g., claude-3-5-haiku-latest)')
273
- .option('--max-tokens <tokens>', 'Maximum tokens for responses')
274
- .option('--enabled <bool>', 'Enable or disable LLM features (true/false)')
275
- .action(async (options) => {
276
- const spinner = output.spinner('Updating LLM configuration...').start();
277
- try {
278
- const body = {};
279
- if (options.provider)
280
- body.provider = options.provider;
281
- if (options.apiKey)
282
- body.apiKey = options.apiKey;
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) {
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);
290
316
  spinner.stop();
291
- output.error('No configuration options provided');
292
- output.info('Use --help to see available options');
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));
293
329
  process.exit(1);
294
330
  }
295
- const response = await client.put('/v1/admin/advisor/llm/config', body);
296
- spinner.stop();
297
- output.success('LLM configuration updated');
298
- output.keyValue({
299
- 'Provider': response.data.provider,
300
- 'API Key': response.data.apiKeyConfigured ? `${response.data.apiKeyPrefix ?? ''}...` : 'Not set',
301
- 'Model': response.data.model,
302
- 'Max Tokens': response.data.maxTokens,
303
- 'Enabled': response.data.enabled ? 'Yes' : 'No',
304
- });
305
- }
306
- catch (err) {
307
- spinner.fail('Failed to update LLM configuration');
308
- output.error(err instanceof Error ? err.message : String(err));
309
- process.exit(1);
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}`);
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
+ }
326
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');
327
369
  }
328
- else {
329
- output.error(result.message);
370
+ catch (err) {
371
+ spinner.fail('Failed to delete LLM configuration');
372
+ output.error(err instanceof Error ? err.message : String(err));
330
373
  process.exit(1);
331
374
  }
332
- }
333
- catch (err) {
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
- });
375
+ });
376
+ }
356
377
  }
357
378
  //# sourceMappingURL=advisor.js.map