@zincapp/znvault-cli 3.3.0 → 4.0.0

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