@zincapp/znvault-cli 2.1.2 → 2.2.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 (176) hide show
  1. package/README.md +84 -0
  2. package/dist/commands/agent.d.ts +1 -1
  3. package/dist/commands/agent.d.ts.map +1 -1
  4. package/dist/commands/agent.js +71 -71
  5. package/dist/commands/agent.js.map +1 -1
  6. package/dist/commands/apikey.d.ts +1 -1
  7. package/dist/commands/apikey.d.ts.map +1 -1
  8. package/dist/commands/apikey.js +47 -85
  9. package/dist/commands/apikey.js.map +1 -1
  10. package/dist/commands/audit.d.ts +1 -1
  11. package/dist/commands/audit.d.ts.map +1 -1
  12. package/dist/commands/audit.js +2 -2
  13. package/dist/commands/audit.js.map +1 -1
  14. package/dist/commands/auth.d.ts +1 -1
  15. package/dist/commands/auth.d.ts.map +1 -1
  16. package/dist/commands/auth.js +18 -14
  17. package/dist/commands/auth.js.map +1 -1
  18. package/dist/commands/backup.d.ts +3 -0
  19. package/dist/commands/backup.d.ts.map +1 -0
  20. package/dist/commands/backup.js +393 -0
  21. package/dist/commands/backup.js.map +1 -0
  22. package/dist/commands/cert.d.ts +1 -1
  23. package/dist/commands/cert.d.ts.map +1 -1
  24. package/dist/commands/cert.js +2 -2
  25. package/dist/commands/cert.js.map +1 -1
  26. package/dist/commands/cluster.d.ts +1 -1
  27. package/dist/commands/cluster.d.ts.map +1 -1
  28. package/dist/commands/cluster.js +2 -2
  29. package/dist/commands/cluster.js.map +1 -1
  30. package/dist/commands/emergency.d.ts +1 -1
  31. package/dist/commands/emergency.d.ts.map +1 -1
  32. package/dist/commands/emergency.js +19 -18
  33. package/dist/commands/emergency.js.map +1 -1
  34. package/dist/commands/health.d.ts +1 -1
  35. package/dist/commands/health.d.ts.map +1 -1
  36. package/dist/commands/health.js +75 -77
  37. package/dist/commands/health.js.map +1 -1
  38. package/dist/commands/kms.d.ts +3 -0
  39. package/dist/commands/kms.d.ts.map +1 -0
  40. package/dist/commands/kms.js +536 -0
  41. package/dist/commands/kms.js.map +1 -0
  42. package/dist/commands/lockdown.d.ts +1 -1
  43. package/dist/commands/lockdown.d.ts.map +1 -1
  44. package/dist/commands/lockdown.js +6 -6
  45. package/dist/commands/lockdown.js.map +1 -1
  46. package/dist/commands/notification.d.ts +3 -0
  47. package/dist/commands/notification.d.ts.map +1 -0
  48. package/dist/commands/notification.js +394 -0
  49. package/dist/commands/notification.js.map +1 -0
  50. package/dist/commands/permissions.d.ts +1 -1
  51. package/dist/commands/permissions.d.ts.map +1 -1
  52. package/dist/commands/permissions.js +7 -7
  53. package/dist/commands/permissions.js.map +1 -1
  54. package/dist/commands/policy.d.ts +1 -1
  55. package/dist/commands/policy.d.ts.map +1 -1
  56. package/dist/commands/policy.js +14 -14
  57. package/dist/commands/policy.js.map +1 -1
  58. package/dist/commands/role.d.ts +3 -0
  59. package/dist/commands/role.d.ts.map +1 -0
  60. package/dist/commands/role.js +400 -0
  61. package/dist/commands/role.js.map +1 -0
  62. package/dist/commands/secret.d.ts +3 -0
  63. package/dist/commands/secret.d.ts.map +1 -0
  64. package/dist/commands/secret.js +694 -0
  65. package/dist/commands/secret.js.map +1 -0
  66. package/dist/commands/self-update.d.ts +8 -0
  67. package/dist/commands/self-update.d.ts.map +1 -0
  68. package/dist/commands/self-update.js +114 -0
  69. package/dist/commands/self-update.js.map +1 -0
  70. package/dist/commands/superadmin.d.ts +1 -1
  71. package/dist/commands/superadmin.d.ts.map +1 -1
  72. package/dist/commands/superadmin.js +3 -3
  73. package/dist/commands/superadmin.js.map +1 -1
  74. package/dist/commands/tenant.d.ts +1 -1
  75. package/dist/commands/tenant.d.ts.map +1 -1
  76. package/dist/commands/tenant.js +16 -18
  77. package/dist/commands/tenant.js.map +1 -1
  78. package/dist/commands/tui.d.ts +3 -0
  79. package/dist/commands/tui.d.ts.map +1 -0
  80. package/dist/commands/tui.js +66 -0
  81. package/dist/commands/tui.js.map +1 -0
  82. package/dist/commands/update.d.ts +1 -1
  83. package/dist/commands/update.d.ts.map +1 -1
  84. package/dist/commands/update.js +8 -8
  85. package/dist/commands/update.js.map +1 -1
  86. package/dist/commands/user.d.ts +1 -1
  87. package/dist/commands/user.d.ts.map +1 -1
  88. package/dist/commands/user.js +7 -7
  89. package/dist/commands/user.js.map +1 -1
  90. package/dist/index.js +33 -3
  91. package/dist/index.js.map +1 -1
  92. package/dist/lib/cli-update.d.ts +43 -0
  93. package/dist/lib/cli-update.d.ts.map +1 -0
  94. package/dist/lib/cli-update.js +257 -0
  95. package/dist/lib/cli-update.js.map +1 -0
  96. package/dist/lib/client.d.ts +48 -61
  97. package/dist/lib/client.d.ts.map +1 -1
  98. package/dist/lib/client.js +57 -30
  99. package/dist/lib/client.js.map +1 -1
  100. package/dist/lib/config.d.ts.map +1 -1
  101. package/dist/lib/config.js +42 -37
  102. package/dist/lib/config.js.map +1 -1
  103. package/dist/lib/db.d.ts.map +1 -1
  104. package/dist/lib/db.js +23 -23
  105. package/dist/lib/db.js.map +1 -1
  106. package/dist/lib/local.d.ts.map +1 -1
  107. package/dist/lib/local.js +9 -9
  108. package/dist/lib/local.js.map +1 -1
  109. package/dist/lib/mode.d.ts +1 -1
  110. package/dist/lib/mode.d.ts.map +1 -1
  111. package/dist/lib/mode.js +4 -7
  112. package/dist/lib/mode.js.map +1 -1
  113. package/dist/lib/output-mode.d.ts +31 -0
  114. package/dist/lib/output-mode.d.ts.map +1 -0
  115. package/dist/lib/output-mode.js +81 -0
  116. package/dist/lib/output-mode.js.map +1 -0
  117. package/dist/lib/output.d.ts +55 -5
  118. package/dist/lib/output.d.ts.map +1 -1
  119. package/dist/lib/output.js +279 -30
  120. package/dist/lib/output.js.map +1 -1
  121. package/dist/lib/prompts.d.ts +2 -2
  122. package/dist/lib/prompts.d.ts.map +1 -1
  123. package/dist/lib/prompts.js.map +1 -1
  124. package/dist/lib/visual.d.ts +71 -0
  125. package/dist/lib/visual.d.ts.map +1 -0
  126. package/dist/lib/visual.js +266 -0
  127. package/dist/lib/visual.js.map +1 -0
  128. package/dist/services/auto-update-daemon.d.ts.map +1 -1
  129. package/dist/services/auto-update-daemon.js +39 -19
  130. package/dist/services/auto-update-daemon.js.map +1 -1
  131. package/dist/services/signature-verifier.d.ts +0 -1
  132. package/dist/services/signature-verifier.d.ts.map +1 -1
  133. package/dist/services/signature-verifier.js +4 -8
  134. package/dist/services/signature-verifier.js.map +1 -1
  135. package/dist/services/update-checker.d.ts.map +1 -1
  136. package/dist/services/update-checker.js +3 -3
  137. package/dist/services/update-checker.js.map +1 -1
  138. package/dist/services/update-installer.d.ts.map +1 -1
  139. package/dist/services/update-installer.js +5 -5
  140. package/dist/services/update-installer.js.map +1 -1
  141. package/dist/tui/App.d.ts +9 -0
  142. package/dist/tui/App.d.ts.map +1 -0
  143. package/dist/tui/App.js +63 -0
  144. package/dist/tui/App.js.map +1 -0
  145. package/dist/tui/components/Header.d.ts +9 -0
  146. package/dist/tui/components/Header.d.ts.map +1 -0
  147. package/dist/tui/components/Header.js +9 -0
  148. package/dist/tui/components/Header.js.map +1 -0
  149. package/dist/tui/components/List.d.ts +61 -0
  150. package/dist/tui/components/List.d.ts.map +1 -0
  151. package/dist/tui/components/List.js +85 -0
  152. package/dist/tui/components/List.js.map +1 -0
  153. package/dist/tui/components/StatusCard.d.ts +29 -0
  154. package/dist/tui/components/StatusCard.d.ts.map +1 -0
  155. package/dist/tui/components/StatusCard.js +46 -0
  156. package/dist/tui/components/StatusCard.js.map +1 -0
  157. package/dist/tui/components/Table.d.ts +29 -0
  158. package/dist/tui/components/Table.d.ts.map +1 -0
  159. package/dist/tui/components/Table.js +131 -0
  160. package/dist/tui/components/Table.js.map +1 -0
  161. package/dist/tui/components/UpdateBanner.d.ts +9 -0
  162. package/dist/tui/components/UpdateBanner.d.ts.map +1 -0
  163. package/dist/tui/components/UpdateBanner.js +6 -0
  164. package/dist/tui/components/UpdateBanner.js.map +1 -0
  165. package/dist/tui/hooks/useApi.d.ts +75 -0
  166. package/dist/tui/hooks/useApi.d.ts.map +1 -0
  167. package/dist/tui/hooks/useApi.js +79 -0
  168. package/dist/tui/hooks/useApi.js.map +1 -0
  169. package/dist/tui/screens/Dashboard.d.ts +9 -0
  170. package/dist/tui/screens/Dashboard.d.ts.map +1 -0
  171. package/dist/tui/screens/Dashboard.js +66 -0
  172. package/dist/tui/screens/Dashboard.js.map +1 -0
  173. package/dist/types/index.d.ts +5 -5
  174. package/dist/types/index.d.ts.map +1 -1
  175. package/dist/utils/platform.js +1 -1
  176. package/package.json +29 -13
@@ -0,0 +1,694 @@
1
+ // Path: znvault-cli/src/commands/secret.ts
2
+ // CLI commands for secrets management
3
+ import ora from 'ora';
4
+ import Table from 'cli-table3';
5
+ import inquirer from 'inquirer';
6
+ import { client } from '../lib/client.js';
7
+ import * as output from '../lib/output.js';
8
+ // ============================================================================
9
+ // Helper Functions
10
+ // ============================================================================
11
+ function formatDate(dateStr) {
12
+ if (!dateStr)
13
+ return '-';
14
+ return new Date(dateStr).toLocaleString();
15
+ }
16
+ function formatType(type, subType) {
17
+ if (subType)
18
+ return `${type}/${subType}`;
19
+ return type;
20
+ }
21
+ function formatTags(tags) {
22
+ if (!tags || tags.length === 0)
23
+ return '-';
24
+ if (tags.length <= 3)
25
+ return tags.join(', ');
26
+ return `${tags.slice(0, 2).join(', ')} +${tags.length - 2} more`;
27
+ }
28
+ function truncateAlias(alias, maxLen = 40) {
29
+ if (alias.length <= maxLen)
30
+ return alias;
31
+ return '...' + alias.slice(-(maxLen - 3));
32
+ }
33
+ function formatBytes(bytes) {
34
+ if (!bytes)
35
+ return '-';
36
+ if (bytes < 1024)
37
+ return `${bytes} B`;
38
+ if (bytes < 1024 * 1024)
39
+ return `${(bytes / 1024).toFixed(1)} KB`;
40
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
41
+ }
42
+ function getDaysUntilExpiry(expiresAt) {
43
+ if (!expiresAt)
44
+ return null;
45
+ const expires = new Date(expiresAt);
46
+ const now = new Date();
47
+ return Math.ceil((expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
48
+ }
49
+ function formatExpiry(expiresAt) {
50
+ if (!expiresAt)
51
+ return '-';
52
+ const days = getDaysUntilExpiry(expiresAt);
53
+ if (days === null)
54
+ return '-';
55
+ if (days < 0)
56
+ return `Expired ${Math.abs(days)}d ago`;
57
+ if (days === 0)
58
+ return 'Expires today';
59
+ if (days <= 7)
60
+ return `${days}d (!)`;
61
+ if (days <= 30)
62
+ return `${days}d`;
63
+ return `${days}d`;
64
+ }
65
+ // ============================================================================
66
+ // Command Implementations
67
+ // ============================================================================
68
+ async function listSecrets(options) {
69
+ const spinner = ora('Fetching secrets...').start();
70
+ try {
71
+ const query = {};
72
+ if (options.tenant)
73
+ query.tenant = options.tenant;
74
+ if (options.type)
75
+ query.type = options.type;
76
+ if (options.subType)
77
+ query.subType = options.subType;
78
+ if (options.aliasPrefix)
79
+ query.aliasPrefix = options.aliasPrefix;
80
+ if (options.expiring) {
81
+ const days = parseInt(options.expiring, 10);
82
+ const expiringBefore = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
83
+ query.expiringBefore = expiringBefore;
84
+ }
85
+ const secrets = await client.get('/v1/secrets?' + new URLSearchParams(query).toString());
86
+ spinner.stop();
87
+ if (options.json) {
88
+ output.json(secrets);
89
+ return;
90
+ }
91
+ if (secrets.length === 0) {
92
+ output.info('No secrets found');
93
+ return;
94
+ }
95
+ const table = new Table({
96
+ head: ['ID', 'Alias', 'Tenant', 'Type', 'Ver', 'Expires', 'Tags', 'Updated'],
97
+ colWidths: [12, 42, 12, 16, 5, 14, 20, 20],
98
+ wordWrap: true,
99
+ });
100
+ for (const secret of secrets) {
101
+ table.push([
102
+ secret.id.slice(0, 10) + '...',
103
+ truncateAlias(secret.alias),
104
+ secret.tenant.slice(0, 10),
105
+ formatType(secret.type, secret.subType),
106
+ String(secret.version),
107
+ formatExpiry(secret.expiresAt || secret.ttlUntil),
108
+ formatTags(secret.tags),
109
+ formatDate(secret.updatedAt).split(',')[0], // Just date
110
+ ]);
111
+ }
112
+ console.log(table.toString());
113
+ output.info(`Total: ${secrets.length} secret(s)`);
114
+ }
115
+ catch (error) {
116
+ spinner.fail('Failed to list secrets');
117
+ output.error(error.message);
118
+ process.exit(1);
119
+ }
120
+ }
121
+ async function getSecret(id, options) {
122
+ const spinner = ora('Fetching secret metadata...').start();
123
+ try {
124
+ const secret = await client.get(`/v1/secrets/${id}/meta`);
125
+ spinner.stop();
126
+ if (options.json) {
127
+ output.json(secret);
128
+ return;
129
+ }
130
+ const table = new Table({
131
+ colWidths: [20, 60],
132
+ });
133
+ table.push(['ID', secret.id], ['Alias', secret.alias], ['Tenant', secret.tenant], ['Type', formatType(secret.type, secret.subType)], ['Version', String(secret.version)]);
134
+ if (secret.fileName) {
135
+ table.push(['File Name', secret.fileName]);
136
+ }
137
+ if (secret.fileSize) {
138
+ table.push(['File Size', formatBytes(secret.fileSize)]);
139
+ }
140
+ if (secret.fileMime) {
141
+ table.push(['MIME Type', secret.fileMime]);
142
+ }
143
+ if (secret.contentType) {
144
+ table.push(['Content Type', secret.contentType]);
145
+ }
146
+ if (secret.expiresAt) {
147
+ table.push(['Expires At', formatDate(secret.expiresAt)]);
148
+ }
149
+ if (secret.ttlUntil) {
150
+ table.push(['TTL Until', formatDate(secret.ttlUntil)]);
151
+ }
152
+ if (secret.tags && secret.tags.length > 0) {
153
+ table.push(['Tags', secret.tags.join(', ')]);
154
+ }
155
+ table.push(['Created By', secret.createdBy || '-'], ['Created At', formatDate(secret.createdAt)], ['Updated At', formatDate(secret.updatedAt)]);
156
+ console.log(table.toString());
157
+ }
158
+ catch (error) {
159
+ spinner.fail('Failed to get secret');
160
+ output.error(error.message);
161
+ process.exit(1);
162
+ }
163
+ }
164
+ async function decryptSecret(id, options) {
165
+ const spinner = ora('Decrypting secret...').start();
166
+ try {
167
+ const secret = await client.post(`/v1/secrets/${id}/decrypt`, {});
168
+ spinner.stop();
169
+ if (options.json) {
170
+ output.json(secret);
171
+ return;
172
+ }
173
+ // If output file specified and it's a file-based secret
174
+ if (options.output && secret.data) {
175
+ const fs = await import('fs');
176
+ // Check if it's a file-based secret
177
+ if ('content' in secret.data && typeof secret.data.content === 'string') {
178
+ const content = Buffer.from(secret.data.content, 'base64');
179
+ fs.writeFileSync(options.output, content);
180
+ output.success(`File written to: ${options.output}`);
181
+ return;
182
+ }
183
+ // Otherwise write JSON
184
+ fs.writeFileSync(options.output, JSON.stringify(secret.data, null, 2));
185
+ output.success(`Data written to: ${options.output}`);
186
+ return;
187
+ }
188
+ // Display metadata
189
+ console.log('\n--- Secret Metadata ---');
190
+ console.log(`ID: ${secret.id}`);
191
+ console.log(`Alias: ${secret.alias}`);
192
+ console.log(`Tenant: ${secret.tenant}`);
193
+ console.log(`Type: ${formatType(secret.type, secret.subType)}`);
194
+ console.log(`Version: ${secret.version}`);
195
+ // Display data based on type
196
+ console.log('\n--- Secret Data ---');
197
+ if (secret.type === 'credential' && secret.data) {
198
+ if ('username' in secret.data)
199
+ console.log(`Username: ${secret.data.username}`);
200
+ if ('password' in secret.data)
201
+ console.log(`Password: ${secret.data.password}`);
202
+ // Show any additional fields
203
+ const knownFields = ['username', 'password'];
204
+ for (const [key, value] of Object.entries(secret.data)) {
205
+ if (!knownFields.includes(key)) {
206
+ console.log(`${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`);
207
+ }
208
+ }
209
+ }
210
+ else if (secret.data && 'text' in secret.data) {
211
+ // Plain text secret
212
+ console.log(secret.data.text);
213
+ }
214
+ else if (secret.data && 'content' in secret.data && 'filename' in secret.data) {
215
+ // File-based secret
216
+ console.log(`File: ${secret.data.filename}`);
217
+ console.log(`Size: ${formatBytes(Buffer.from(secret.data.content, 'base64').length)}`);
218
+ if (secret.data.contentType)
219
+ console.log(`Type: ${secret.data.contentType}`);
220
+ console.log('\nUse --output <file> to save the file content');
221
+ }
222
+ else if (secret.data && 'privateKey' in secret.data) {
223
+ // Key pair secret
224
+ console.log('Key Pair Secret:');
225
+ const pk = secret.data.privateKey;
226
+ const pub = secret.data.publicKey;
227
+ if (pk?.filename)
228
+ console.log(` Private Key: ${pk.filename}`);
229
+ if (pub?.filename)
230
+ console.log(` Public Key: ${pub.filename}`);
231
+ console.log('\nUse --output <file> to save the keys');
232
+ }
233
+ else {
234
+ // Generic key-value
235
+ console.log(JSON.stringify(secret.data, null, 2));
236
+ }
237
+ }
238
+ catch (error) {
239
+ spinner.fail('Failed to decrypt secret');
240
+ output.error(error.message);
241
+ process.exit(1);
242
+ }
243
+ }
244
+ async function createSecret(alias, options) {
245
+ if (!options.tenant) {
246
+ output.error('Tenant is required. Use --tenant <id>');
247
+ process.exit(1);
248
+ }
249
+ let data = {};
250
+ let actualType = options.type || 'opaque';
251
+ // Check for non-interactive data options first
252
+ const hasNonInteractiveData = options.username || options.password || options.text || options.data || options.file;
253
+ if (hasNonInteractiveData) {
254
+ // Non-interactive mode: use CLI options
255
+ if (options.username || options.password) {
256
+ actualType = 'credential';
257
+ data = {
258
+ username: options.username || '',
259
+ password: options.password || '',
260
+ };
261
+ }
262
+ else if (options.text) {
263
+ data = { text: options.text };
264
+ }
265
+ else if (options.data) {
266
+ try {
267
+ data = JSON.parse(options.data);
268
+ }
269
+ catch (e) {
270
+ output.error('Invalid JSON in --data option');
271
+ process.exit(1);
272
+ }
273
+ }
274
+ else if (options.file) {
275
+ const fs = await import('fs');
276
+ const pathModule = await import('path');
277
+ if (!fs.existsSync(options.file)) {
278
+ output.error(`File not found: ${options.file}`);
279
+ process.exit(1);
280
+ }
281
+ const content = fs.readFileSync(options.file);
282
+ const filename = pathModule.basename(options.file);
283
+ data = {
284
+ filename,
285
+ content: content.toString('base64'),
286
+ contentType: options.contentType || 'application/octet-stream',
287
+ };
288
+ }
289
+ }
290
+ else {
291
+ // Interactive mode: prompt for data
292
+ const { dataType } = await inquirer.prompt([
293
+ {
294
+ type: 'list',
295
+ name: 'dataType',
296
+ message: 'What type of data?',
297
+ choices: [
298
+ { name: 'Credential (username/password)', value: 'credential' },
299
+ { name: 'Plain Text', value: 'text' },
300
+ { name: 'Key-Value pairs', value: 'keyvalue' },
301
+ { name: 'File upload', value: 'file' },
302
+ ],
303
+ },
304
+ ]);
305
+ if (dataType === 'credential') {
306
+ actualType = 'credential';
307
+ const answers = await inquirer.prompt([
308
+ { type: 'input', name: 'username', message: 'Username:' },
309
+ { type: 'password', name: 'password', message: 'Password:', mask: '*' },
310
+ ]);
311
+ data = answers;
312
+ }
313
+ else if (dataType === 'text') {
314
+ const { text } = await inquirer.prompt([
315
+ { type: 'editor', name: 'text', message: 'Enter text content:' },
316
+ ]);
317
+ data = { text: text.trim() };
318
+ }
319
+ else if (dataType === 'keyvalue') {
320
+ console.log('Enter key-value pairs (empty key to finish):');
321
+ while (true) {
322
+ const { key } = await inquirer.prompt([
323
+ { type: 'input', name: 'key', message: 'Key:' },
324
+ ]);
325
+ if (!key)
326
+ break;
327
+ const { value } = await inquirer.prompt([
328
+ { type: 'input', name: 'value', message: `Value for "${key}":` },
329
+ ]);
330
+ data[key] = value;
331
+ }
332
+ }
333
+ else if (dataType === 'file') {
334
+ const { filePath } = await inquirer.prompt([
335
+ { type: 'input', name: 'filePath', message: 'File path:' },
336
+ ]);
337
+ const fs = await import('fs');
338
+ const pathModule = await import('path');
339
+ if (!fs.existsSync(filePath)) {
340
+ output.error(`File not found: ${filePath}`);
341
+ process.exit(1);
342
+ }
343
+ const content = fs.readFileSync(filePath);
344
+ const filename = pathModule.basename(filePath);
345
+ data = {
346
+ filename,
347
+ content: content.toString('base64'),
348
+ contentType: options.contentType || 'application/octet-stream',
349
+ };
350
+ }
351
+ }
352
+ const spinner = ora('Creating secret...').start();
353
+ try {
354
+ const body = {
355
+ alias,
356
+ tenant: options.tenant,
357
+ type: actualType,
358
+ data,
359
+ };
360
+ if (options.subType)
361
+ body.subType = options.subType;
362
+ if (options.tags)
363
+ body.tags = options.tags.split(',').map(t => t.trim());
364
+ if (options.ttl)
365
+ body.ttlUntil = options.ttl;
366
+ if (options.expires)
367
+ body.expiresAt = options.expires;
368
+ if (options.contentType)
369
+ body.contentType = options.contentType;
370
+ const result = await client.post('/v1/secrets', body);
371
+ spinner.stop();
372
+ if (options.json) {
373
+ output.json(result);
374
+ return;
375
+ }
376
+ output.success(`Secret created successfully!`);
377
+ console.log(` ID: ${result.id}`);
378
+ console.log(` Alias: ${result.alias}`);
379
+ console.log(` Tenant: ${result.tenant}`);
380
+ }
381
+ catch (error) {
382
+ spinner.fail('Failed to create secret');
383
+ output.error(error.message);
384
+ process.exit(1);
385
+ }
386
+ }
387
+ async function updateSecret(id, options) {
388
+ let newData;
389
+ // Check for non-interactive data option
390
+ if (options.data) {
391
+ // Non-interactive mode: parse JSON data from CLI
392
+ try {
393
+ newData = JSON.parse(options.data);
394
+ }
395
+ catch {
396
+ output.error('Invalid JSON in --data option');
397
+ process.exit(1);
398
+ }
399
+ }
400
+ else {
401
+ // Interactive mode: prompt for data
402
+ const spinner = ora('Fetching current secret...').start();
403
+ try {
404
+ const current = await client.post(`/v1/secrets/${id}/decrypt`, {});
405
+ spinner.stop();
406
+ const { updateData } = await inquirer.prompt([
407
+ {
408
+ type: 'confirm',
409
+ name: 'updateData',
410
+ message: 'Update the secret data?',
411
+ default: false,
412
+ },
413
+ ]);
414
+ newData = current.data;
415
+ if (updateData) {
416
+ if (current.type === 'credential') {
417
+ const answers = await inquirer.prompt([
418
+ {
419
+ type: 'input',
420
+ name: 'username',
421
+ message: 'Username:',
422
+ default: current.data.username
423
+ },
424
+ {
425
+ type: 'password',
426
+ name: 'password',
427
+ message: 'Password (leave empty to keep current):',
428
+ mask: '*'
429
+ },
430
+ ]);
431
+ newData = {
432
+ username: answers.username,
433
+ password: answers.password || current.data.password,
434
+ };
435
+ }
436
+ else {
437
+ const { dataJson } = await inquirer.prompt([
438
+ {
439
+ type: 'editor',
440
+ name: 'dataJson',
441
+ message: 'Edit data (JSON):',
442
+ default: JSON.stringify(current.data, null, 2),
443
+ },
444
+ ]);
445
+ try {
446
+ newData = JSON.parse(dataJson);
447
+ }
448
+ catch {
449
+ output.error('Invalid JSON data');
450
+ process.exit(1);
451
+ }
452
+ }
453
+ }
454
+ }
455
+ catch (error) {
456
+ spinner.fail('Failed to fetch current secret');
457
+ output.error(error.message);
458
+ process.exit(1);
459
+ }
460
+ }
461
+ const updateSpinner = ora('Updating secret...').start();
462
+ try {
463
+ const body = { data: newData };
464
+ if (options.tags)
465
+ body.tags = options.tags.split(',').map(t => t.trim());
466
+ if (options.ttl)
467
+ body.ttlUntil = options.ttl;
468
+ if (options.expires)
469
+ body.expiresAt = options.expires;
470
+ const result = await client.put(`/v1/secrets/${id}`, body);
471
+ updateSpinner.stop();
472
+ if (options.json) {
473
+ output.json(result);
474
+ return;
475
+ }
476
+ output.success('Secret updated successfully!');
477
+ console.log(` Version: ${result.version}`);
478
+ }
479
+ catch (error) {
480
+ updateSpinner.fail('Failed to update secret');
481
+ output.error(error.message);
482
+ process.exit(1);
483
+ }
484
+ }
485
+ async function deleteSecret(id, options) {
486
+ if (!options.force) {
487
+ // Get metadata first
488
+ const spinner = ora('Fetching secret...').start();
489
+ try {
490
+ const secret = await client.get(`/v1/secrets/${id}/meta`);
491
+ spinner.stop();
492
+ const { confirm } = await inquirer.prompt([
493
+ {
494
+ type: 'confirm',
495
+ name: 'confirm',
496
+ message: `Delete secret "${secret.alias}" (${id})? This cannot be undone.`,
497
+ default: false,
498
+ },
499
+ ]);
500
+ if (!confirm) {
501
+ output.info('Deletion cancelled');
502
+ return;
503
+ }
504
+ }
505
+ catch (error) {
506
+ spinner.fail('Failed to fetch secret');
507
+ output.error(error.message);
508
+ process.exit(1);
509
+ }
510
+ }
511
+ const deleteSpinner = ora('Deleting secret...').start();
512
+ try {
513
+ await client.delete(`/v1/secrets/${id}`);
514
+ deleteSpinner.stop();
515
+ output.success('Secret deleted successfully');
516
+ }
517
+ catch (error) {
518
+ deleteSpinner.fail('Failed to delete secret');
519
+ output.error(error.message);
520
+ process.exit(1);
521
+ }
522
+ }
523
+ async function rotateSecret(id, options) {
524
+ // Get current secret first
525
+ const spinner = ora('Fetching current secret...').start();
526
+ try {
527
+ const current = await client.post(`/v1/secrets/${id}/decrypt`, {});
528
+ spinner.stop();
529
+ console.log(`Current secret: ${current.alias} (v${current.version})`);
530
+ // Prompt for new data
531
+ let newData;
532
+ if (current.type === 'credential') {
533
+ const answers = await inquirer.prompt([
534
+ {
535
+ type: 'password',
536
+ name: 'password',
537
+ message: 'New password:',
538
+ mask: '*',
539
+ validate: (input) => input.length > 0 || 'Password is required',
540
+ },
541
+ ]);
542
+ newData = {
543
+ username: current.data.username,
544
+ password: answers.password,
545
+ };
546
+ }
547
+ else {
548
+ const { dataJson } = await inquirer.prompt([
549
+ {
550
+ type: 'editor',
551
+ name: 'dataJson',
552
+ message: 'Enter new data (JSON):',
553
+ default: JSON.stringify(current.data, null, 2),
554
+ },
555
+ ]);
556
+ try {
557
+ newData = JSON.parse(dataJson);
558
+ }
559
+ catch {
560
+ output.error('Invalid JSON data');
561
+ process.exit(1);
562
+ }
563
+ }
564
+ const rotateSpinner = ora('Rotating secret...').start();
565
+ const result = await client.post(`/v1/secrets/${id}/rotate`, { data: newData });
566
+ rotateSpinner.stop();
567
+ if (options.json) {
568
+ output.json(result);
569
+ return;
570
+ }
571
+ output.success('Secret rotated successfully!');
572
+ console.log(` New Version: ${result.version}`);
573
+ }
574
+ catch (error) {
575
+ spinner.fail('Failed to rotate secret');
576
+ output.error(error.message);
577
+ process.exit(1);
578
+ }
579
+ }
580
+ async function showHistory(id, options) {
581
+ const spinner = ora('Fetching secret history...').start();
582
+ try {
583
+ const response = await client.get(`/v1/secrets/${id}/history`);
584
+ spinner.stop();
585
+ const history = response.history || [];
586
+ if (options.json) {
587
+ output.json(history);
588
+ return;
589
+ }
590
+ if (history.length === 0) {
591
+ output.info('No version history found');
592
+ return;
593
+ }
594
+ const table = new Table({
595
+ head: ['Version', 'Created At', 'Superseded At', 'Created By'],
596
+ colWidths: [10, 25, 25, 30],
597
+ });
598
+ for (const entry of history) {
599
+ table.push([
600
+ String(entry.version),
601
+ formatDate(entry.createdAt),
602
+ entry.supersededAt ? formatDate(entry.supersededAt) : '-',
603
+ entry.createdBy || '-',
604
+ ]);
605
+ }
606
+ console.log(table.toString());
607
+ console.log(`Total: ${response.count} version(s)`);
608
+ }
609
+ catch (error) {
610
+ spinner.fail('Failed to fetch history');
611
+ output.error(error.message);
612
+ process.exit(1);
613
+ }
614
+ }
615
+ // ============================================================================
616
+ // Command Registration
617
+ // ============================================================================
618
+ export function registerSecretCommands(program) {
619
+ const secret = program
620
+ .command('secret')
621
+ .description('Manage secrets');
622
+ // List secrets
623
+ secret
624
+ .command('list')
625
+ .description('List secrets (metadata only)')
626
+ .option('-t, --tenant <id>', 'Filter by tenant')
627
+ .option('--type <type>', 'Filter by type (opaque, credential, setting)')
628
+ .option('--sub-type <subType>', 'Filter by sub-type')
629
+ .option('--alias-prefix <prefix>', 'Filter by alias prefix')
630
+ .option('--expiring <days>', 'Show secrets expiring within N days')
631
+ .option('--json', 'Output as JSON')
632
+ .action(listSecrets);
633
+ // Get secret metadata
634
+ secret
635
+ .command('get <id>')
636
+ .description('Get secret metadata (no value)')
637
+ .option('--json', 'Output as JSON')
638
+ .action(getSecret);
639
+ // Decrypt secret
640
+ secret
641
+ .command('decrypt <id>')
642
+ .description('Decrypt and show secret value')
643
+ .option('-o, --output <file>', 'Write content to file')
644
+ .option('--json', 'Output as JSON')
645
+ .action(decryptSecret);
646
+ // Create secret
647
+ secret
648
+ .command('create <alias>')
649
+ .description('Create a new secret')
650
+ .requiredOption('-t, --tenant <id>', 'Tenant ID')
651
+ .option('--type <type>', 'Secret type (opaque, credential, setting)', 'opaque')
652
+ .option('--sub-type <subType>', 'Semantic sub-type')
653
+ .option('--tags <tags>', 'Comma-separated tags')
654
+ .option('--ttl <datetime>', 'TTL expiration (ISO 8601)')
655
+ .option('--expires <datetime>', 'Natural expiration (ISO 8601)')
656
+ .option('--content-type <mime>', 'Content type for settings')
657
+ .option('--json', 'Output as JSON')
658
+ // Non-interactive data options
659
+ .option('--username <username>', 'Username for credential type (non-interactive)')
660
+ .option('--password <password>', 'Password for credential type (non-interactive)')
661
+ .option('--text <text>', 'Text content (non-interactive)')
662
+ .option('--data <json>', 'JSON data (non-interactive)')
663
+ .option('--file <path>', 'File to upload (non-interactive)')
664
+ .action(createSecret);
665
+ // Update secret
666
+ secret
667
+ .command('update <id>')
668
+ .description('Update a secret')
669
+ .option('--tags <tags>', 'Comma-separated tags')
670
+ .option('--ttl <datetime>', 'TTL expiration (ISO 8601)')
671
+ .option('--expires <datetime>', 'Natural expiration (ISO 8601)')
672
+ .option('--json', 'Output as JSON')
673
+ .option('--data <json>', 'New data as JSON (non-interactive)')
674
+ .action(updateSecret);
675
+ // Delete secret
676
+ secret
677
+ .command('delete <id>')
678
+ .description('Delete a secret')
679
+ .option('-f, --force', 'Skip confirmation')
680
+ .action(deleteSecret);
681
+ // Rotate secret
682
+ secret
683
+ .command('rotate <id>')
684
+ .description('Rotate secret (create new version)')
685
+ .option('--json', 'Output as JSON')
686
+ .action(rotateSecret);
687
+ // Show history
688
+ secret
689
+ .command('history <id>')
690
+ .description('Show secret version history')
691
+ .option('--json', 'Output as JSON')
692
+ .action(showHistory);
693
+ }
694
+ //# sourceMappingURL=secret.js.map