@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.
Files changed (221) 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 +355 -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/conditions.d.ts.map +1 -1
  11. package/dist/commands/apikey/conditions.js +4 -3
  12. package/dist/commands/apikey/conditions.js.map +1 -1
  13. package/dist/commands/apikey/create.d.ts.map +1 -1
  14. package/dist/commands/apikey/create.js +4 -2
  15. package/dist/commands/apikey/create.js.map +1 -1
  16. package/dist/commands/apikey/delete.d.ts.map +1 -1
  17. package/dist/commands/apikey/delete.js +4 -2
  18. package/dist/commands/apikey/delete.js.map +1 -1
  19. package/dist/commands/apikey/enable-disable.d.ts.map +1 -1
  20. package/dist/commands/apikey/enable-disable.js +7 -4
  21. package/dist/commands/apikey/enable-disable.js.map +1 -1
  22. package/dist/commands/apikey/helpers.d.ts +16 -0
  23. package/dist/commands/apikey/helpers.d.ts.map +1 -1
  24. package/dist/commands/apikey/helpers.js +9 -0
  25. package/dist/commands/apikey/helpers.js.map +1 -1
  26. package/dist/commands/apikey/index.d.ts +2 -1
  27. package/dist/commands/apikey/index.d.ts.map +1 -1
  28. package/dist/commands/apikey/index.js +23 -14
  29. package/dist/commands/apikey/index.js.map +1 -1
  30. package/dist/commands/apikey/list.d.ts.map +1 -1
  31. package/dist/commands/apikey/list.js +4 -3
  32. package/dist/commands/apikey/list.js.map +1 -1
  33. package/dist/commands/apikey/managed/bind.d.ts.map +1 -1
  34. package/dist/commands/apikey/managed/bind.js +4 -3
  35. package/dist/commands/apikey/managed/bind.js.map +1 -1
  36. package/dist/commands/apikey/managed/conditions.d.ts.map +1 -1
  37. package/dist/commands/apikey/managed/conditions.js +5 -4
  38. package/dist/commands/apikey/managed/conditions.js.map +1 -1
  39. package/dist/commands/apikey/managed/config.d.ts.map +1 -1
  40. package/dist/commands/apikey/managed/config.js +4 -3
  41. package/dist/commands/apikey/managed/config.js.map +1 -1
  42. package/dist/commands/apikey/managed/create.d.ts.map +1 -1
  43. package/dist/commands/apikey/managed/create.js +4 -1
  44. package/dist/commands/apikey/managed/create.js.map +1 -1
  45. package/dist/commands/apikey/managed/delete.d.ts.map +1 -1
  46. package/dist/commands/apikey/managed/delete.js +4 -2
  47. package/dist/commands/apikey/managed/delete.js.map +1 -1
  48. package/dist/commands/apikey/managed/get.d.ts.map +1 -1
  49. package/dist/commands/apikey/managed/get.js +4 -2
  50. package/dist/commands/apikey/managed/get.js.map +1 -1
  51. package/dist/commands/apikey/managed/list.d.ts.map +1 -1
  52. package/dist/commands/apikey/managed/list.js +4 -2
  53. package/dist/commands/apikey/managed/list.js.map +1 -1
  54. package/dist/commands/apikey/managed/permissions.d.ts.map +1 -1
  55. package/dist/commands/apikey/managed/permissions.js +5 -3
  56. package/dist/commands/apikey/managed/permissions.js.map +1 -1
  57. package/dist/commands/apikey/managed/rotate.d.ts.map +1 -1
  58. package/dist/commands/apikey/managed/rotate.js +4 -3
  59. package/dist/commands/apikey/managed/rotate.js.map +1 -1
  60. package/dist/commands/apikey/permissions.d.ts.map +1 -1
  61. package/dist/commands/apikey/permissions.js +5 -3
  62. package/dist/commands/apikey/permissions.js.map +1 -1
  63. package/dist/commands/apikey/policies.d.ts.map +1 -1
  64. package/dist/commands/apikey/policies.js +10 -7
  65. package/dist/commands/apikey/policies.js.map +1 -1
  66. package/dist/commands/apikey/rotate.d.ts.map +1 -1
  67. package/dist/commands/apikey/rotate.js +4 -3
  68. package/dist/commands/apikey/rotate.js.map +1 -1
  69. package/dist/commands/apikey/show.d.ts.map +1 -1
  70. package/dist/commands/apikey/show.js +5 -4
  71. package/dist/commands/apikey/show.js.map +1 -1
  72. package/dist/commands/audit.d.ts +2 -1
  73. package/dist/commands/audit.d.ts.map +1 -1
  74. package/dist/commands/audit.js +15 -3
  75. package/dist/commands/audit.js.map +1 -1
  76. package/dist/commands/cluster.d.ts.map +1 -1
  77. package/dist/commands/cluster.js +31 -14
  78. package/dist/commands/cluster.js.map +1 -1
  79. package/dist/commands/group.d.ts +2 -1
  80. package/dist/commands/group.d.ts.map +1 -1
  81. package/dist/commands/group.js +61 -39
  82. package/dist/commands/group.js.map +1 -1
  83. package/dist/commands/health.d.ts.map +1 -1
  84. package/dist/commands/health.js +6 -5
  85. package/dist/commands/health.js.map +1 -1
  86. package/dist/commands/host/index.d.ts +2 -1
  87. package/dist/commands/host/index.d.ts.map +1 -1
  88. package/dist/commands/host/index.js +22 -18
  89. package/dist/commands/host/index.js.map +1 -1
  90. package/dist/commands/kms/crud.d.ts +1 -1
  91. package/dist/commands/kms/crud.d.ts.map +1 -1
  92. package/dist/commands/kms/crud.js +6 -6
  93. package/dist/commands/kms/crud.js.map +1 -1
  94. package/dist/commands/kms/index.d.ts +2 -1
  95. package/dist/commands/kms/index.d.ts.map +1 -1
  96. package/dist/commands/kms/index.js +14 -6
  97. package/dist/commands/kms/index.js.map +1 -1
  98. package/dist/commands/kms/lifecycle.d.ts +1 -1
  99. package/dist/commands/kms/lifecycle.d.ts.map +1 -1
  100. package/dist/commands/kms/lifecycle.js +7 -7
  101. package/dist/commands/kms/lifecycle.js.map +1 -1
  102. package/dist/commands/kms/routing.d.ts +5 -0
  103. package/dist/commands/kms/routing.d.ts.map +1 -1
  104. package/dist/commands/kms/routing.js +41 -5
  105. package/dist/commands/kms/routing.js.map +1 -1
  106. package/dist/commands/policy/attachments.d.ts +9 -5
  107. package/dist/commands/policy/attachments.d.ts.map +1 -1
  108. package/dist/commands/policy/attachments.js +16 -13
  109. package/dist/commands/policy/attachments.js.map +1 -1
  110. package/dist/commands/policy/crud.d.ts +6 -5
  111. package/dist/commands/policy/crud.d.ts.map +1 -1
  112. package/dist/commands/policy/crud.js +16 -11
  113. package/dist/commands/policy/crud.js.map +1 -1
  114. package/dist/commands/policy/helpers.d.ts +16 -0
  115. package/dist/commands/policy/helpers.d.ts.map +1 -1
  116. package/dist/commands/policy/helpers.js +9 -0
  117. package/dist/commands/policy/helpers.js.map +1 -1
  118. package/dist/commands/policy/index.d.ts +2 -1
  119. package/dist/commands/policy/index.d.ts.map +1 -1
  120. package/dist/commands/policy/index.js +149 -101
  121. package/dist/commands/policy/index.js.map +1 -1
  122. package/dist/commands/policy/io.d.ts +3 -2
  123. package/dist/commands/policy/io.d.ts.map +1 -1
  124. package/dist/commands/policy/io.js +7 -5
  125. package/dist/commands/policy/io.js.map +1 -1
  126. package/dist/commands/policy/list.d.ts +6 -2
  127. package/dist/commands/policy/list.d.ts.map +1 -1
  128. package/dist/commands/policy/list.js +7 -6
  129. package/dist/commands/policy/list.js.map +1 -1
  130. package/dist/commands/quarantine.d.ts +2 -1
  131. package/dist/commands/quarantine.d.ts.map +1 -1
  132. package/dist/commands/quarantine.js +394 -339
  133. package/dist/commands/quarantine.js.map +1 -1
  134. package/dist/commands/role.d.ts +2 -1
  135. package/dist/commands/role.d.ts.map +1 -1
  136. package/dist/commands/role.js +92 -49
  137. package/dist/commands/role.js.map +1 -1
  138. package/dist/commands/ssh/index.d.ts +2 -1
  139. package/dist/commands/ssh/index.d.ts.map +1 -1
  140. package/dist/commands/ssh/index.js +18 -13
  141. package/dist/commands/ssh/index.js.map +1 -1
  142. package/dist/commands/sso/crud.d.ts +1 -1
  143. package/dist/commands/sso/crud.d.ts.map +1 -1
  144. package/dist/commands/sso/crud.js +16 -16
  145. package/dist/commands/sso/crud.js.map +1 -1
  146. package/dist/commands/sso/helpers.d.ts +7 -1
  147. package/dist/commands/sso/helpers.d.ts.map +1 -1
  148. package/dist/commands/sso/helpers.js +32 -1
  149. package/dist/commands/sso/helpers.js.map +1 -1
  150. package/dist/commands/sso/index.d.ts +2 -1
  151. package/dist/commands/sso/index.d.ts.map +1 -1
  152. package/dist/commands/sso/index.js +13 -5
  153. package/dist/commands/sso/index.js.map +1 -1
  154. package/dist/commands/superadmin/accounts.d.ts +10 -0
  155. package/dist/commands/superadmin/accounts.d.ts.map +1 -0
  156. package/dist/commands/{superadmin.js → superadmin/accounts.js} +16 -15
  157. package/dist/commands/superadmin/accounts.js.map +1 -0
  158. package/dist/commands/superadmin/index.d.ts +20 -0
  159. package/dist/commands/superadmin/index.d.ts.map +1 -0
  160. package/dist/commands/superadmin/index.js +60 -0
  161. package/dist/commands/superadmin/index.js.map +1 -0
  162. package/dist/commands/user.d.ts +2 -1
  163. package/dist/commands/user.d.ts.map +1 -1
  164. package/dist/commands/user.js +26 -13
  165. package/dist/commands/user.js.map +1 -1
  166. package/dist/index.js +18 -13
  167. package/dist/index.js.map +1 -1
  168. package/dist/lib/client/apikeys.d.ts +21 -13
  169. package/dist/lib/client/apikeys.d.ts.map +1 -1
  170. package/dist/lib/client/apikeys.js +30 -26
  171. package/dist/lib/client/apikeys.js.map +1 -1
  172. package/dist/lib/client/audit.d.ts +54 -1
  173. package/dist/lib/client/audit.d.ts.map +1 -1
  174. package/dist/lib/client/audit.js +83 -9
  175. package/dist/lib/client/audit.js.map +1 -1
  176. package/dist/lib/client/index.d.ts +37 -36
  177. package/dist/lib/client/index.d.ts.map +1 -1
  178. package/dist/lib/client/index.js +37 -36
  179. package/dist/lib/client/index.js.map +1 -1
  180. package/dist/lib/client/managed-keys.d.ts +10 -7
  181. package/dist/lib/client/managed-keys.d.ts.map +1 -1
  182. package/dist/lib/client/managed-keys.js +16 -16
  183. package/dist/lib/client/managed-keys.js.map +1 -1
  184. package/dist/lib/client/policies.d.ts +14 -10
  185. package/dist/lib/client/policies.d.ts.map +1 -1
  186. package/dist/lib/client/policies.js +22 -22
  187. package/dist/lib/client/policies.js.map +1 -1
  188. package/dist/lib/client/quarantine.d.ts +25 -9
  189. package/dist/lib/client/quarantine.d.ts.map +1 -1
  190. package/dist/lib/client/quarantine.js +44 -27
  191. package/dist/lib/client/quarantine.js.map +1 -1
  192. package/dist/lib/client/users.d.ts +30 -3
  193. package/dist/lib/client/users.d.ts.map +1 -1
  194. package/dist/lib/client/users.js +39 -15
  195. package/dist/lib/client/users.js.map +1 -1
  196. package/dist/lib/command-context.d.ts +64 -0
  197. package/dist/lib/command-context.d.ts.map +1 -0
  198. package/dist/lib/command-context.js +90 -0
  199. package/dist/lib/command-context.js.map +1 -0
  200. package/dist/lib/context-help.d.ts.map +1 -1
  201. package/dist/lib/context-help.js +5 -0
  202. package/dist/lib/context-help.js.map +1 -1
  203. package/dist/lib/db/health.d.ts.map +1 -1
  204. package/dist/lib/db/health.js +13 -4
  205. package/dist/lib/db/health.js.map +1 -1
  206. package/dist/lib/mode.d.ts +18 -4
  207. package/dist/lib/mode.d.ts.map +1 -1
  208. package/dist/lib/mode.js +13 -6
  209. package/dist/lib/mode.js.map +1 -1
  210. package/dist/tui/hooks/useApi.d.ts +14 -7
  211. package/dist/tui/hooks/useApi.d.ts.map +1 -1
  212. package/dist/tui/hooks/useApi.js.map +1 -1
  213. package/dist/tui/screens/Dashboard.d.ts.map +1 -1
  214. package/dist/tui/screens/Dashboard.js +2 -3
  215. package/dist/tui/screens/Dashboard.js.map +1 -1
  216. package/dist/types/index.d.ts +59 -12
  217. package/dist/types/index.d.ts.map +1 -1
  218. package/package.json +1 -1
  219. package/dist/commands/superadmin.d.ts +0 -3
  220. package/dist/commands/superadmin.d.ts.map +0 -1
  221. package/dist/commands/superadmin.js.map +0 -1
@@ -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 { 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 asSuperadmin = ctx === 'superadmin';
23
+ const advisor = parent
7
24
  .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}`);
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
- // 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`);
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
- 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);
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
- 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;
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
- 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;
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
- 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}`); });
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
- if (result.warnings && result.warnings.length > 0) {
183
- output.section('Warnings');
184
- result.warnings.forEach(w => { console.log(` ⚠ ${w}`); });
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
- 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;
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
- 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');
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
- 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;
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
- const config = response.data;
248
- if (!config) {
249
- output.info('LLM is not configured');
250
- return;
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
- 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) {
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.error('No configuration options provided');
292
- output.info('Use --help to see available options');
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
- 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}`);
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
- else {
329
- output.error(result.message);
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
- 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
- });
378
+ });
379
+ }
356
380
  }
357
381
  //# sourceMappingURL=advisor.js.map