@zincapp/znvault-cli 2.16.4 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +6 -0
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/apikey/conditions.d.ts +6 -0
- package/dist/commands/apikey/conditions.d.ts.map +1 -0
- package/dist/commands/apikey/conditions.js +57 -0
- package/dist/commands/apikey/conditions.js.map +1 -0
- package/dist/commands/apikey/create.d.ts +6 -0
- package/dist/commands/apikey/create.d.ts.map +1 -0
- package/dist/commands/apikey/create.js +106 -0
- package/dist/commands/apikey/create.js.map +1 -0
- package/dist/commands/apikey/delete.d.ts +6 -0
- package/dist/commands/apikey/delete.d.ts.map +1 -0
- package/dist/commands/apikey/delete.js +29 -0
- package/dist/commands/apikey/delete.js.map +1 -0
- package/dist/commands/apikey/enable-disable.d.ts +6 -0
- package/dist/commands/apikey/enable-disable.d.ts.map +1 -0
- package/dist/commands/apikey/enable-disable.js +44 -0
- package/dist/commands/apikey/enable-disable.js.map +1 -0
- package/dist/commands/apikey/helpers.d.ts +23 -0
- package/dist/commands/apikey/helpers.d.ts.map +1 -0
- package/dist/commands/apikey/helpers.js +135 -0
- package/dist/commands/apikey/helpers.js.map +1 -0
- package/dist/commands/apikey/index.d.ts +10 -0
- package/dist/commands/apikey/index.d.ts.map +1 -0
- package/dist/commands/apikey/index.js +33 -0
- package/dist/commands/apikey/index.js.map +1 -0
- package/dist/commands/apikey/list.d.ts +6 -0
- package/dist/commands/apikey/list.d.ts.map +1 -0
- package/dist/commands/apikey/list.js +74 -0
- package/dist/commands/apikey/list.js.map +1 -0
- package/dist/commands/apikey/managed/bind.d.ts +6 -0
- package/dist/commands/apikey/managed/bind.d.ts.map +1 -0
- package/dist/commands/apikey/managed/bind.js +52 -0
- package/dist/commands/apikey/managed/bind.js.map +1 -0
- package/dist/commands/apikey/managed/conditions.d.ts +6 -0
- package/dist/commands/apikey/managed/conditions.d.ts.map +1 -0
- package/dist/commands/apikey/managed/conditions.js +62 -0
- package/dist/commands/apikey/managed/conditions.js.map +1 -0
- package/dist/commands/apikey/managed/config.d.ts +6 -0
- package/dist/commands/apikey/managed/config.d.ts.map +1 -0
- package/dist/commands/apikey/managed/config.js +52 -0
- package/dist/commands/apikey/managed/config.js.map +1 -0
- package/dist/commands/apikey/managed/create.d.ts +6 -0
- package/dist/commands/apikey/managed/create.d.ts.map +1 -0
- package/dist/commands/apikey/managed/create.js +82 -0
- package/dist/commands/apikey/managed/create.js.map +1 -0
- package/dist/commands/apikey/managed/delete.d.ts +6 -0
- package/dist/commands/apikey/managed/delete.d.ts.map +1 -0
- package/dist/commands/apikey/managed/delete.js +29 -0
- package/dist/commands/apikey/managed/delete.js.map +1 -0
- package/dist/commands/apikey/managed/get.d.ts +6 -0
- package/dist/commands/apikey/managed/get.d.ts.map +1 -0
- package/dist/commands/apikey/managed/get.js +31 -0
- package/dist/commands/apikey/managed/get.js.map +1 -0
- package/dist/commands/apikey/managed/helpers.d.ts +5 -0
- package/dist/commands/apikey/managed/helpers.d.ts.map +1 -0
- package/dist/commands/apikey/managed/helpers.js +70 -0
- package/dist/commands/apikey/managed/helpers.js.map +1 -0
- package/dist/commands/apikey/managed/index.d.ts +7 -0
- package/dist/commands/apikey/managed/index.d.ts.map +1 -0
- package/dist/commands/apikey/managed/index.js +27 -0
- package/dist/commands/apikey/managed/index.js.map +1 -0
- package/dist/commands/apikey/managed/list.d.ts +6 -0
- package/dist/commands/apikey/managed/list.d.ts.map +1 -0
- package/dist/commands/apikey/managed/list.js +58 -0
- package/dist/commands/apikey/managed/list.js.map +1 -0
- package/dist/commands/apikey/managed/permissions.d.ts +6 -0
- package/dist/commands/apikey/managed/permissions.d.ts.map +1 -0
- package/dist/commands/apikey/managed/permissions.js +73 -0
- package/dist/commands/apikey/managed/permissions.js.map +1 -0
- package/dist/commands/apikey/managed/rotate.d.ts +6 -0
- package/dist/commands/apikey/managed/rotate.d.ts.map +1 -0
- package/dist/commands/apikey/managed/rotate.js +29 -0
- package/dist/commands/apikey/managed/rotate.js.map +1 -0
- package/dist/commands/apikey/managed/types.d.ts +62 -0
- package/dist/commands/apikey/managed/types.d.ts.map +1 -0
- package/dist/commands/apikey/managed/types.js +3 -0
- package/dist/commands/apikey/managed/types.js.map +1 -0
- package/dist/commands/apikey/permissions.d.ts +6 -0
- package/dist/commands/apikey/permissions.d.ts.map +1 -0
- package/dist/commands/apikey/permissions.js +70 -0
- package/dist/commands/apikey/permissions.js.map +1 -0
- package/dist/commands/apikey/policies.d.ts +6 -0
- package/dist/commands/apikey/policies.d.ts.map +1 -0
- package/dist/commands/apikey/policies.js +82 -0
- package/dist/commands/apikey/policies.js.map +1 -0
- package/dist/commands/apikey/rotate.d.ts +6 -0
- package/dist/commands/apikey/rotate.d.ts.map +1 -0
- package/dist/commands/apikey/rotate.js +42 -0
- package/dist/commands/apikey/rotate.js.map +1 -0
- package/dist/commands/apikey/self.d.ts +6 -0
- package/dist/commands/apikey/self.d.ts.map +1 -0
- package/dist/commands/apikey/self.js +96 -0
- package/dist/commands/apikey/self.js.map +1 -0
- package/dist/commands/apikey/show.d.ts +6 -0
- package/dist/commands/apikey/show.d.ts.map +1 -0
- package/dist/commands/apikey/show.js +79 -0
- package/dist/commands/apikey/show.js.map +1 -0
- package/dist/commands/apikey/types.d.ts +83 -0
- package/dist/commands/apikey/types.d.ts.map +1 -0
- package/dist/commands/apikey/types.js +3 -0
- package/dist/commands/apikey/types.js.map +1 -0
- package/dist/commands/apikey.d.ts +8 -2
- package/dist/commands/apikey.d.ts.map +1 -1
- package/dist/commands/apikey.js +9 -1296
- package/dist/commands/apikey.js.map +1 -1
- package/dist/commands/device.d.ts.map +1 -1
- package/dist/commands/device.js +8 -5
- package/dist/commands/device.js.map +1 -1
- package/dist/commands/plugin.d.ts.map +1 -1
- package/dist/commands/plugin.js +29 -7
- package/dist/commands/plugin.js.map +1 -1
- package/dist/commands/secret/copy.d.ts +6 -0
- package/dist/commands/secret/copy.d.ts.map +1 -0
- package/dist/commands/secret/copy.js +43 -0
- package/dist/commands/secret/copy.js.map +1 -0
- package/dist/commands/secret/create.d.ts +6 -0
- package/dist/commands/secret/create.d.ts.map +1 -0
- package/dist/commands/secret/create.js +297 -0
- package/dist/commands/secret/create.js.map +1 -0
- package/dist/commands/secret/decrypt.d.ts +6 -0
- package/dist/commands/secret/decrypt.d.ts.map +1 -0
- package/dist/commands/secret/decrypt.js +104 -0
- package/dist/commands/secret/decrypt.js.map +1 -0
- package/dist/commands/secret/delete.d.ts +6 -0
- package/dist/commands/secret/delete.d.ts.map +1 -0
- package/dist/commands/secret/delete.js +60 -0
- package/dist/commands/secret/delete.js.map +1 -0
- package/dist/commands/secret/get.d.ts +6 -0
- package/dist/commands/secret/get.d.ts.map +1 -0
- package/dist/commands/secret/get.js +60 -0
- package/dist/commands/secret/get.js.map +1 -0
- package/dist/commands/secret/helpers.d.ts +11 -0
- package/dist/commands/secret/helpers.d.ts.map +1 -0
- package/dist/commands/secret/helpers.js +59 -0
- package/dist/commands/secret/helpers.js.map +1 -0
- package/dist/commands/secret/history.d.ts +6 -0
- package/dist/commands/secret/history.d.ts.map +1 -0
- package/dist/commands/secret/history.js +52 -0
- package/dist/commands/secret/history.js.map +1 -0
- package/dist/commands/secret/index.d.ts +12 -0
- package/dist/commands/secret/index.d.ts.map +1 -0
- package/dist/commands/secret/index.js +49 -0
- package/dist/commands/secret/index.js.map +1 -0
- package/dist/commands/secret/list.d.ts +6 -0
- package/dist/commands/secret/list.d.ts.map +1 -0
- package/dist/commands/secret/list.js +72 -0
- package/dist/commands/secret/list.js.map +1 -0
- package/dist/commands/secret/pem-analysis.d.ts +32 -0
- package/dist/commands/secret/pem-analysis.d.ts.map +1 -0
- package/dist/commands/secret/pem-analysis.js +190 -0
- package/dist/commands/secret/pem-analysis.js.map +1 -0
- package/dist/commands/secret/resolve.d.ts +17 -0
- package/dist/commands/secret/resolve.d.ts.map +1 -0
- package/dist/commands/secret/resolve.js +36 -0
- package/dist/commands/secret/resolve.js.map +1 -0
- package/dist/commands/secret/rotate.d.ts +6 -0
- package/dist/commands/secret/rotate.d.ts.map +1 -0
- package/dist/commands/secret/rotate.js +72 -0
- package/dist/commands/secret/rotate.js.map +1 -0
- package/dist/commands/secret/types.d.ts +123 -0
- package/dist/commands/secret/types.d.ts.map +1 -0
- package/dist/commands/secret/types.js +3 -0
- package/dist/commands/secret/types.js.map +1 -0
- package/dist/commands/secret/update.d.ts +6 -0
- package/dist/commands/secret/update.d.ts.map +1 -0
- package/dist/commands/secret/update.js +124 -0
- package/dist/commands/secret/update.js.map +1 -0
- package/dist/commands/secret.d.ts +8 -2
- package/dist/commands/secret.d.ts.map +1 -1
- package/dist/commands/secret.js +6 -1131
- package/dist/commands/secret.js.map +1 -1
- package/dist/index.js +48 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/client/apikeys.d.ts +34 -0
- package/dist/lib/client/apikeys.d.ts.map +1 -0
- package/dist/lib/client/apikeys.js +113 -0
- package/dist/lib/client/apikeys.js.map +1 -0
- package/dist/lib/client/audit.d.ts +21 -0
- package/dist/lib/client/audit.d.ts.map +1 -0
- package/dist/lib/client/audit.js +40 -0
- package/dist/lib/client/audit.js.map +1 -0
- package/dist/lib/client/health.d.ts +30 -0
- package/dist/lib/client/health.d.ts.map +1 -0
- package/dist/lib/client/health.js +55 -0
- package/dist/lib/client/health.js.map +1 -0
- package/dist/lib/client/http.d.ts +50 -0
- package/dist/lib/client/http.d.ts.map +1 -0
- package/dist/lib/client/http.js +333 -0
- package/dist/lib/client/http.js.map +1 -0
- package/dist/lib/client/index.d.ts +156 -0
- package/dist/lib/client/index.d.ts.map +1 -0
- package/dist/lib/client/index.js +172 -0
- package/dist/lib/client/index.js.map +1 -0
- package/dist/lib/client/lockdown.d.ts +23 -0
- package/dist/lib/client/lockdown.d.ts.map +1 -0
- package/dist/lib/client/lockdown.js +48 -0
- package/dist/lib/client/lockdown.js.map +1 -0
- package/dist/lib/client/managed-keys.d.ts +18 -0
- package/dist/lib/client/managed-keys.d.ts.map +1 -0
- package/dist/lib/client/managed-keys.js +190 -0
- package/dist/lib/client/managed-keys.js.map +1 -0
- package/dist/lib/client/policies.d.ts +35 -0
- package/dist/lib/client/policies.d.ts.map +1 -0
- package/dist/lib/client/policies.js +131 -0
- package/dist/lib/client/policies.js.map +1 -0
- package/dist/lib/client/tenants.d.ts +29 -0
- package/dist/lib/client/tenants.d.ts.map +1 -0
- package/dist/lib/client/tenants.js +56 -0
- package/dist/lib/client/tenants.js.map +1 -0
- package/dist/lib/client/types.d.ts +45 -0
- package/dist/lib/client/types.d.ts.map +1 -0
- package/dist/lib/client/types.js +3 -0
- package/dist/lib/client/types.js.map +1 -0
- package/dist/lib/client/users.d.ts +44 -0
- package/dist/lib/client/users.d.ts.map +1 -0
- package/dist/lib/client/users.js +116 -0
- package/dist/lib/client/users.js.map +1 -0
- package/dist/lib/client.d.ts +11 -246
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +10 -996
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/command-error-handler.d.ts +99 -0
- package/dist/lib/command-error-handler.d.ts.map +1 -0
- package/dist/lib/command-error-handler.js +108 -0
- package/dist/lib/command-error-handler.js.map +1 -0
- package/dist/lib/config/apikey.d.ts +29 -0
- package/dist/lib/config/apikey.d.ts.map +1 -0
- package/dist/lib/config/apikey.js +58 -0
- package/dist/lib/config/apikey.js.map +1 -0
- package/dist/lib/config/cache.d.ts +47 -0
- package/dist/lib/config/cache.d.ts.map +1 -0
- package/dist/lib/config/cache.js +84 -0
- package/dist/lib/config/cache.js.map +1 -0
- package/dist/lib/config/credentials.d.ts +33 -0
- package/dist/lib/config/credentials.d.ts.map +1 -0
- package/dist/lib/config/credentials.js +70 -0
- package/dist/lib/config/credentials.js.map +1 -0
- package/dist/lib/config/getters.d.ts +28 -0
- package/dist/lib/config/getters.d.ts.map +1 -0
- package/dist/lib/config/getters.js +65 -0
- package/dist/lib/config/getters.js.map +1 -0
- package/dist/lib/config/index.d.ts +17 -0
- package/dist/lib/config/index.d.ts.map +1 -0
- package/dist/lib/config/index.js +21 -0
- package/dist/lib/config/index.js.map +1 -0
- package/dist/lib/config/migration.d.ts +10 -0
- package/dist/lib/config/migration.d.ts.map +1 -0
- package/dist/lib/config/migration.js +59 -0
- package/dist/lib/config/migration.js.map +1 -0
- package/dist/lib/config/plugins.d.ts +25 -0
- package/dist/lib/config/plugins.d.ts.map +1 -0
- package/dist/lib/config/plugins.js +58 -0
- package/dist/lib/config/plugins.js.map +1 -0
- package/dist/lib/config/profile.d.ts +42 -0
- package/dist/lib/config/profile.d.ts.map +1 -0
- package/dist/lib/config/profile.js +154 -0
- package/dist/lib/config/profile.js.map +1 -0
- package/dist/lib/config/store.d.ts +23 -0
- package/dist/lib/config/store.d.ts.map +1 -0
- package/dist/lib/config/store.js +41 -0
- package/dist/lib/config/store.js.map +1 -0
- package/dist/lib/config/types.d.ts +44 -0
- package/dist/lib/config/types.d.ts.map +1 -0
- package/dist/lib/config/types.js +8 -0
- package/dist/lib/config/types.js.map +1 -0
- package/dist/lib/config/validation.d.ts +38 -0
- package/dist/lib/config/validation.d.ts.map +1 -0
- package/dist/lib/config/validation.js +146 -0
- package/dist/lib/config/validation.js.map +1 -0
- package/dist/lib/config.d.ts +5 -158
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +26 -424
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/constants.d.ts +65 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +90 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/db.d.ts +4 -0
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +77 -46
- package/dist/lib/db.js.map +1 -1
- package/dist/lib/format-helpers.d.ts +63 -0
- package/dist/lib/format-helpers.d.ts.map +1 -0
- package/dist/lib/format-helpers.js +219 -0
- package/dist/lib/format-helpers.js.map +1 -0
- package/dist/lib/visual.d.ts +22 -0
- package/dist/lib/visual.d.ts.map +1 -1
- package/dist/lib/visual.js +83 -0
- package/dist/lib/visual.js.map +1 -1
- package/dist/services/signature-verifier.d.ts.map +1 -1
- package/dist/services/signature-verifier.js +11 -4
- package/dist/services/signature-verifier.js.map +1 -1
- package/dist/services/update-installer.d.ts +7 -0
- package/dist/services/update-installer.d.ts.map +1 -1
- package/dist/services/update-installer.js +58 -19
- package/dist/services/update-installer.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/secret.js
CHANGED
|
@@ -1,1135 +1,10 @@
|
|
|
1
|
-
// Path:
|
|
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
|
-
import { getAuthContext } from '../lib/auth-context.js';
|
|
9
|
-
// ============================================================================
|
|
10
|
-
// Helper Functions
|
|
11
|
-
// ============================================================================
|
|
12
|
-
function formatDate(dateStr) {
|
|
13
|
-
if (!dateStr)
|
|
14
|
-
return '-';
|
|
15
|
-
return new Date(dateStr).toLocaleString();
|
|
16
|
-
}
|
|
17
|
-
function formatType(type, subType) {
|
|
18
|
-
if (subType)
|
|
19
|
-
return `${type}/${subType}`;
|
|
20
|
-
return type;
|
|
21
|
-
}
|
|
22
|
-
function formatTags(tags) {
|
|
23
|
-
if (!tags || tags.length === 0)
|
|
24
|
-
return '-';
|
|
25
|
-
if (tags.length <= 3)
|
|
26
|
-
return tags.join(', ');
|
|
27
|
-
return `${tags.slice(0, 2).join(', ')} +${tags.length - 2} more`;
|
|
28
|
-
}
|
|
29
|
-
function truncateAlias(alias, maxLen = 40) {
|
|
30
|
-
if (alias.length <= maxLen)
|
|
31
|
-
return alias;
|
|
32
|
-
return '...' + alias.slice(-(maxLen - 3));
|
|
33
|
-
}
|
|
34
|
-
function formatBytes(bytes) {
|
|
35
|
-
if (!bytes)
|
|
36
|
-
return '-';
|
|
37
|
-
if (bytes < 1024)
|
|
38
|
-
return `${bytes} B`;
|
|
39
|
-
if (bytes < 1024 * 1024)
|
|
40
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
41
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
42
|
-
}
|
|
43
|
-
function getDaysUntilExpiry(expiresAt) {
|
|
44
|
-
if (!expiresAt)
|
|
45
|
-
return null;
|
|
46
|
-
const expires = new Date(expiresAt);
|
|
47
|
-
const now = new Date();
|
|
48
|
-
return Math.ceil((expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
49
|
-
}
|
|
50
|
-
// ============================================================================
|
|
51
|
-
// PEM File Analysis (for --suggest with --file)
|
|
52
|
-
// ============================================================================
|
|
53
|
-
const PEM_HEADER_MAP = {
|
|
54
|
-
'PRIVATE KEY': { type: 'private-key' },
|
|
55
|
-
'RSA PRIVATE KEY': { type: 'private-key', algorithm: 'rsa' },
|
|
56
|
-
'EC PRIVATE KEY': { type: 'private-key', algorithm: 'ec' },
|
|
57
|
-
'DSA PRIVATE KEY': { type: 'private-key', algorithm: 'dsa' },
|
|
58
|
-
'OPENSSH PRIVATE KEY': { type: 'private-key' },
|
|
59
|
-
'ENCRYPTED PRIVATE KEY': { type: 'encrypted-key' },
|
|
60
|
-
'PUBLIC KEY': { type: 'public-key' },
|
|
61
|
-
'RSA PUBLIC KEY': { type: 'public-key', algorithm: 'rsa' },
|
|
62
|
-
'EC PUBLIC KEY': { type: 'public-key', algorithm: 'ec' },
|
|
63
|
-
'CERTIFICATE': { type: 'certificate' },
|
|
64
|
-
'X509 CERTIFICATE': { type: 'certificate' },
|
|
65
|
-
'CERTIFICATE REQUEST': { type: 'csr' },
|
|
66
|
-
};
|
|
67
|
-
function detectKeyAlgorithm(content) {
|
|
68
|
-
if (content.includes('EC PRIVATE KEY') || content.includes('EC PUBLIC KEY'))
|
|
69
|
-
return 'ec';
|
|
70
|
-
if (content.includes('RSA PRIVATE KEY') || content.includes('RSA PUBLIC KEY'))
|
|
71
|
-
return 'rsa';
|
|
72
|
-
// Check for EC curve OIDs
|
|
73
|
-
const ecOidPatterns = ['BggqhkjOPQMBBw', 'BgUrgQQAIg', 'BgUrgQQAIw'];
|
|
74
|
-
for (const pattern of ecOidPatterns) {
|
|
75
|
-
if (content.includes(pattern))
|
|
76
|
-
return 'ec';
|
|
77
|
-
}
|
|
78
|
-
// Size-based heuristic for generic keys
|
|
79
|
-
const keyMatch = /-----BEGIN (?:PRIVATE KEY|PUBLIC KEY)-----\s*([\s\S]*?)\s*-----END/;
|
|
80
|
-
const keyContent = keyMatch.exec(content);
|
|
81
|
-
if (keyContent) {
|
|
82
|
-
const keyBase64 = keyContent[1].replace(/\s/g, '');
|
|
83
|
-
if (keyBase64.length < 400)
|
|
84
|
-
return 'ec';
|
|
85
|
-
if (keyBase64.length > 1000)
|
|
86
|
-
return 'rsa';
|
|
87
|
-
}
|
|
88
|
-
return 'unknown';
|
|
89
|
-
}
|
|
90
|
-
function detectPurpose(filename, type, algorithm, headers) {
|
|
91
|
-
const lowerFilename = filename.toLowerCase();
|
|
92
|
-
if (lowerFilename.endsWith('.p8') || lowerFilename.includes('authkey')) {
|
|
93
|
-
if (type === 'private-key' && algorithm === 'ec') {
|
|
94
|
-
return 'Apple Push Notification Service (APNS) authentication key';
|
|
95
|
-
}
|
|
96
|
-
return 'Apple authentication key (.p8)';
|
|
97
|
-
}
|
|
98
|
-
if (lowerFilename.includes('ssl') || lowerFilename.includes('tls')) {
|
|
99
|
-
if (type === 'certificate')
|
|
100
|
-
return 'SSL/TLS certificate';
|
|
101
|
-
if (type === 'private-key')
|
|
102
|
-
return 'SSL/TLS private key';
|
|
103
|
-
if (type === 'bundle')
|
|
104
|
-
return 'SSL/TLS certificate bundle';
|
|
105
|
-
}
|
|
106
|
-
if (lowerFilename.includes('ca') || lowerFilename.includes('root') || lowerFilename.includes('intermediate')) {
|
|
107
|
-
if (type === 'certificate')
|
|
108
|
-
return 'Certificate Authority (CA) certificate';
|
|
109
|
-
if (type === 'bundle')
|
|
110
|
-
return 'CA certificate chain';
|
|
111
|
-
}
|
|
112
|
-
// JWT/API signing (check before generic "sign" to avoid false matches)
|
|
113
|
-
if (lowerFilename.includes('jwt') || lowerFilename.includes('signing')) {
|
|
114
|
-
if (type === 'private-key')
|
|
115
|
-
return 'JWT/API signing key';
|
|
116
|
-
if (type === 'public-key')
|
|
117
|
-
return 'JWT/API verification key';
|
|
118
|
-
}
|
|
119
|
-
// Code signing (codesign specifically, not just "sign")
|
|
120
|
-
if (lowerFilename.includes('codesign') || (lowerFilename.includes('sign') && !lowerFilename.includes('signing'))) {
|
|
121
|
-
if (type === 'certificate')
|
|
122
|
-
return 'Code signing certificate';
|
|
123
|
-
if (type === 'private-key')
|
|
124
|
-
return 'Code signing private key';
|
|
125
|
-
}
|
|
126
|
-
if (lowerFilename.includes('ssh') || lowerFilename.startsWith('id_') || headers?.some(h => h.includes('OPENSSH'))) {
|
|
127
|
-
if (type === 'private-key')
|
|
128
|
-
return 'SSH private key';
|
|
129
|
-
if (type === 'public-key')
|
|
130
|
-
return 'SSH public key';
|
|
131
|
-
}
|
|
132
|
-
if (type === 'certificate')
|
|
133
|
-
return 'X.509 certificate';
|
|
134
|
-
if (type === 'bundle')
|
|
135
|
-
return 'Certificate bundle/chain';
|
|
136
|
-
if (type === 'csr')
|
|
137
|
-
return 'Certificate Signing Request (CSR)';
|
|
138
|
-
if (type === 'encrypted-key')
|
|
139
|
-
return 'Encrypted private key (password protected)';
|
|
140
|
-
return undefined;
|
|
141
|
-
}
|
|
142
|
-
function analyzePEMContent(content, filename) {
|
|
143
|
-
const headerRegex = /-----BEGIN ([A-Z0-9 ]+)-----/g;
|
|
144
|
-
const headers = [];
|
|
145
|
-
let match;
|
|
146
|
-
while ((match = headerRegex.exec(content)) !== null) {
|
|
147
|
-
headers.push(match[1]);
|
|
148
|
-
}
|
|
149
|
-
if (headers.length === 0)
|
|
150
|
-
return null;
|
|
151
|
-
const certificateCount = headers.filter(h => h.includes('CERTIFICATE')).length;
|
|
152
|
-
const privateKeyHeaders = headers.filter(h => h.includes('PRIVATE KEY'));
|
|
153
|
-
const publicKeyHeaders = headers.filter(h => h.includes('PUBLIC KEY'));
|
|
154
|
-
const csrHeaders = headers.filter(h => h.includes('CERTIFICATE REQUEST'));
|
|
155
|
-
let type = 'unknown';
|
|
156
|
-
let algorithm;
|
|
157
|
-
if (certificateCount > 1 || (certificateCount >= 1 && privateKeyHeaders.length >= 1)) {
|
|
158
|
-
type = 'bundle';
|
|
159
|
-
}
|
|
160
|
-
else if (privateKeyHeaders.length > 0) {
|
|
161
|
-
const keyHeader = privateKeyHeaders[0];
|
|
162
|
-
const mapping = PEM_HEADER_MAP[keyHeader];
|
|
163
|
-
type = mapping?.type ?? 'private-key';
|
|
164
|
-
algorithm = mapping?.algorithm;
|
|
165
|
-
if (keyHeader.includes('ENCRYPTED'))
|
|
166
|
-
type = 'encrypted-key';
|
|
167
|
-
}
|
|
168
|
-
else if (publicKeyHeaders.length > 0) {
|
|
169
|
-
const keyHeader = publicKeyHeaders[0];
|
|
170
|
-
const mapping = PEM_HEADER_MAP[keyHeader];
|
|
171
|
-
type = mapping?.type ?? 'public-key';
|
|
172
|
-
algorithm = mapping?.algorithm;
|
|
173
|
-
}
|
|
174
|
-
else if (csrHeaders.length > 0) {
|
|
175
|
-
type = 'csr';
|
|
176
|
-
}
|
|
177
|
-
else if (certificateCount > 0) {
|
|
178
|
-
type = 'certificate';
|
|
179
|
-
}
|
|
180
|
-
if (!algorithm && (type === 'private-key' || type === 'public-key')) {
|
|
181
|
-
algorithm = detectKeyAlgorithm(content);
|
|
182
|
-
}
|
|
183
|
-
const detectedPurpose = detectPurpose(filename, type, algorithm, headers);
|
|
184
|
-
const lowerFilename = filename.toLowerCase();
|
|
185
|
-
const isAppleP8 = type === 'private-key' && algorithm === 'ec' &&
|
|
186
|
-
(lowerFilename.endsWith('.p8') || lowerFilename.includes('authkey'));
|
|
187
|
-
return {
|
|
188
|
-
type,
|
|
189
|
-
algorithm,
|
|
190
|
-
pemHeaders: headers,
|
|
191
|
-
blockCount: headers.length,
|
|
192
|
-
certificateCount: certificateCount > 0 ? certificateCount : undefined,
|
|
193
|
-
detectedPurpose,
|
|
194
|
-
isAppleP8: isAppleP8 || undefined,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function detectMimeType(content) {
|
|
198
|
-
const text = content.toString('utf8', 0, 100);
|
|
199
|
-
if (text.includes('-----BEGIN'))
|
|
200
|
-
return 'application/x-pem-file';
|
|
201
|
-
return 'application/octet-stream';
|
|
202
|
-
}
|
|
203
|
-
async function analyzeFileForSuggestion(filePath) {
|
|
204
|
-
const fs = await import('fs');
|
|
205
|
-
const pathModule = await import('path');
|
|
206
|
-
if (!fs.existsSync(filePath))
|
|
207
|
-
return null;
|
|
208
|
-
const content = fs.readFileSync(filePath);
|
|
209
|
-
const filename = pathModule.basename(filePath);
|
|
210
|
-
const extension = pathModule.extname(filePath).toLowerCase();
|
|
211
|
-
const mimeType = detectMimeType(content);
|
|
212
|
-
const result = {
|
|
213
|
-
filename,
|
|
214
|
-
extension,
|
|
215
|
-
mimeType,
|
|
216
|
-
size: content.length,
|
|
217
|
-
};
|
|
218
|
-
// Analyze PEM content for relevant file types
|
|
219
|
-
const pemExtensions = ['.pem', '.crt', '.cer', '.key', '.p8', '.p12', '.pfx', '.pub'];
|
|
220
|
-
if (mimeType === 'application/x-pem-file' || pemExtensions.includes(extension)) {
|
|
221
|
-
const textContent = content.toString('utf8');
|
|
222
|
-
const pemInfo = analyzePEMContent(textContent, filename);
|
|
223
|
-
if (pemInfo) {
|
|
224
|
-
result.pemInfo = pemInfo;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
function formatPemType(type) {
|
|
230
|
-
const typeMap = {
|
|
231
|
-
'private-key': 'Private Key',
|
|
232
|
-
'public-key': 'Public Key',
|
|
233
|
-
'certificate': 'X.509 Certificate',
|
|
234
|
-
'csr': 'Certificate Signing Request',
|
|
235
|
-
'bundle': 'Certificate Bundle/Chain',
|
|
236
|
-
'encrypted-key': 'Encrypted Private Key',
|
|
237
|
-
'unknown': 'Unknown PEM format',
|
|
238
|
-
};
|
|
239
|
-
return typeMap[type] ?? type;
|
|
240
|
-
}
|
|
241
|
-
function formatExpiry(expiresAt) {
|
|
242
|
-
if (!expiresAt)
|
|
243
|
-
return '-';
|
|
244
|
-
const days = getDaysUntilExpiry(expiresAt);
|
|
245
|
-
if (days === null)
|
|
246
|
-
return '-';
|
|
247
|
-
if (days < 0)
|
|
248
|
-
return `Expired ${Math.abs(days)}d ago`;
|
|
249
|
-
if (days === 0)
|
|
250
|
-
return 'Expires today';
|
|
251
|
-
if (days <= 7)
|
|
252
|
-
return `${days}d (!)`;
|
|
253
|
-
if (days <= 30)
|
|
254
|
-
return `${days}d`;
|
|
255
|
-
return `${days}d`;
|
|
256
|
-
}
|
|
1
|
+
// Path: src/commands/secret.ts
|
|
257
2
|
/**
|
|
258
|
-
*
|
|
259
|
-
*/
|
|
260
|
-
function isUUID(str) {
|
|
261
|
-
return /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(str);
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Resolve a secret identifier to a UUID.
|
|
265
|
-
* Supports formats:
|
|
266
|
-
* - UUID: pass through directly
|
|
267
|
-
* - alias:path: resolve via /v1/secrets/alias/:alias (tenant from JWT)
|
|
268
|
-
* - path/to/secret: resolve via /v1/secrets/alias/:alias (tenant from JWT)
|
|
269
|
-
* - simple-name: resolve via /v1/secrets/alias/:alias (tenant from JWT)
|
|
3
|
+
* Secret commands - backward compatibility re-export
|
|
270
4
|
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
5
|
+
* This file maintains backward compatibility by re-exporting from the
|
|
6
|
+
* modularized secret/ directory structure.
|
|
273
7
|
*/
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (isUUID(idOrAlias)) {
|
|
277
|
-
return idOrAlias;
|
|
278
|
-
}
|
|
279
|
-
// Strip optional "alias:" prefix
|
|
280
|
-
const alias = idOrAlias.startsWith('alias:')
|
|
281
|
-
? idOrAlias.slice(6)
|
|
282
|
-
: idOrAlias;
|
|
283
|
-
// Resolve alias to UUID via API (tenant derived from JWT)
|
|
284
|
-
const metadata = await client.get(`/v1/secrets/alias/${encodeURIComponent(alias)}`);
|
|
285
|
-
return metadata.id;
|
|
286
|
-
}
|
|
287
|
-
// ============================================================================
|
|
288
|
-
// Command Implementations
|
|
289
|
-
// ============================================================================
|
|
290
|
-
async function listSecrets(options) {
|
|
291
|
-
const spinner = ora('Fetching secrets...').start();
|
|
292
|
-
try {
|
|
293
|
-
const query = {};
|
|
294
|
-
if (options.tenant)
|
|
295
|
-
query.tenant = options.tenant;
|
|
296
|
-
if (options.type)
|
|
297
|
-
query.type = options.type;
|
|
298
|
-
if (options.subType)
|
|
299
|
-
query.subType = options.subType;
|
|
300
|
-
if (options.aliasPrefix)
|
|
301
|
-
query.aliasPrefix = options.aliasPrefix;
|
|
302
|
-
if (options.expiring) {
|
|
303
|
-
const days = parseInt(options.expiring, 10);
|
|
304
|
-
const expiringBefore = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
|
|
305
|
-
query.expiringBefore = expiringBefore;
|
|
306
|
-
}
|
|
307
|
-
const response = await client.get('/v1/secrets?' + new URLSearchParams(query).toString());
|
|
308
|
-
const secrets = response.items;
|
|
309
|
-
spinner.stop();
|
|
310
|
-
if (options.json) {
|
|
311
|
-
output.json(response);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (secrets.length === 0) {
|
|
315
|
-
output.info('No secrets found');
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
const table = new Table({
|
|
319
|
-
head: ['ID', 'Alias', 'Tenant', 'Type', 'Ver', 'Expires', 'Tags', 'Updated'],
|
|
320
|
-
colWidths: [12, 42, 12, 16, 5, 14, 20, 20],
|
|
321
|
-
wordWrap: true,
|
|
322
|
-
});
|
|
323
|
-
for (const secret of secrets) {
|
|
324
|
-
table.push([
|
|
325
|
-
secret.id.slice(0, 10) + '...',
|
|
326
|
-
truncateAlias(secret.alias),
|
|
327
|
-
secret.tenant.slice(0, 10),
|
|
328
|
-
formatType(secret.type, secret.subType),
|
|
329
|
-
String(secret.version),
|
|
330
|
-
formatExpiry(secret.expiresAt || secret.ttlUntil),
|
|
331
|
-
formatTags(secret.tags),
|
|
332
|
-
formatDate(secret.updatedAt).split(',')[0], // Just date
|
|
333
|
-
]);
|
|
334
|
-
}
|
|
335
|
-
console.log(table.toString());
|
|
336
|
-
output.info(`Total: ${response.pagination.total} secret(s)${response.pagination.hasMore ? ' (more available)' : ''}`);
|
|
337
|
-
}
|
|
338
|
-
catch (error) {
|
|
339
|
-
spinner.fail('Failed to list secrets');
|
|
340
|
-
output.error(error.message);
|
|
341
|
-
process.exit(1);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
async function getSecret(idOrAlias, options) {
|
|
345
|
-
const spinner = ora('Resolving secret...').start();
|
|
346
|
-
try {
|
|
347
|
-
// Resolve alias to UUID if needed
|
|
348
|
-
const id = await resolveSecretId(idOrAlias);
|
|
349
|
-
spinner.text = 'Fetching secret metadata...';
|
|
350
|
-
const secret = await client.get(`/v1/secrets/${id}/meta`);
|
|
351
|
-
spinner.stop();
|
|
352
|
-
if (options.json) {
|
|
353
|
-
output.json(secret);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
const table = new Table({
|
|
357
|
-
colWidths: [20, 60],
|
|
358
|
-
});
|
|
359
|
-
table.push(['ID', secret.id], ['Alias', secret.alias], ['Tenant', secret.tenant], ['Type', formatType(secret.type, secret.subType)], ['Version', String(secret.version)]);
|
|
360
|
-
if (secret.fileName) {
|
|
361
|
-
table.push(['File Name', secret.fileName]);
|
|
362
|
-
}
|
|
363
|
-
if (secret.fileSize) {
|
|
364
|
-
table.push(['File Size', formatBytes(secret.fileSize)]);
|
|
365
|
-
}
|
|
366
|
-
if (secret.fileMime) {
|
|
367
|
-
table.push(['MIME Type', secret.fileMime]);
|
|
368
|
-
}
|
|
369
|
-
if (secret.contentType) {
|
|
370
|
-
table.push(['Content Type', secret.contentType]);
|
|
371
|
-
}
|
|
372
|
-
if (secret.expiresAt) {
|
|
373
|
-
table.push(['Expires At', formatDate(secret.expiresAt)]);
|
|
374
|
-
}
|
|
375
|
-
if (secret.ttlUntil) {
|
|
376
|
-
table.push(['TTL Until', formatDate(secret.ttlUntil)]);
|
|
377
|
-
}
|
|
378
|
-
if (secret.tags && secret.tags.length > 0) {
|
|
379
|
-
table.push(['Tags', secret.tags.join(', ')]);
|
|
380
|
-
}
|
|
381
|
-
table.push(['Created By', secret.createdBy || '-'], ['Created At', formatDate(secret.createdAt)], ['Updated At', formatDate(secret.updatedAt)]);
|
|
382
|
-
console.log(table.toString());
|
|
383
|
-
}
|
|
384
|
-
catch (error) {
|
|
385
|
-
spinner.fail('Failed to get secret');
|
|
386
|
-
output.error(error.message);
|
|
387
|
-
process.exit(1);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
async function decryptSecret(idOrAlias, options) {
|
|
391
|
-
const spinner = ora('Resolving secret...').start();
|
|
392
|
-
try {
|
|
393
|
-
// Resolve alias to UUID if needed
|
|
394
|
-
const id = await resolveSecretId(idOrAlias);
|
|
395
|
-
spinner.text = 'Decrypting secret...';
|
|
396
|
-
const secret = await client.post(`/v1/secrets/${id}/decrypt`, {});
|
|
397
|
-
spinner.stop();
|
|
398
|
-
if (options.json) {
|
|
399
|
-
output.json(secret);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
// If output file specified and it's a file-based secret
|
|
403
|
-
if (options.output && secret.data) {
|
|
404
|
-
const fs = await import('fs');
|
|
405
|
-
// Check if it's a file-based secret
|
|
406
|
-
if ('content' in secret.data && typeof secret.data.content === 'string') {
|
|
407
|
-
const content = Buffer.from(secret.data.content, 'base64');
|
|
408
|
-
fs.writeFileSync(options.output, content);
|
|
409
|
-
output.success(`File written to: ${options.output}`);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
// Otherwise write JSON
|
|
413
|
-
fs.writeFileSync(options.output, JSON.stringify(secret.data, null, 2));
|
|
414
|
-
output.success(`Data written to: ${options.output}`);
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
// Display metadata
|
|
418
|
-
console.log('\n--- Secret Metadata ---');
|
|
419
|
-
console.log(`ID: ${secret.id}`);
|
|
420
|
-
console.log(`Alias: ${secret.alias}`);
|
|
421
|
-
console.log(`Tenant: ${secret.tenant}`);
|
|
422
|
-
console.log(`Type: ${formatType(secret.type, secret.subType)}`);
|
|
423
|
-
console.log(`Version: ${secret.version}`);
|
|
424
|
-
// Display data based on type
|
|
425
|
-
console.log('\n--- Secret Data ---');
|
|
426
|
-
if (secret.type === 'credential' && secret.data) {
|
|
427
|
-
if ('username' in secret.data)
|
|
428
|
-
console.log(`Username: ${secret.data.username}`);
|
|
429
|
-
if ('password' in secret.data)
|
|
430
|
-
console.log(`Password: ${secret.data.password}`);
|
|
431
|
-
// Show any additional fields
|
|
432
|
-
const knownFields = ['username', 'password'];
|
|
433
|
-
for (const [key, value] of Object.entries(secret.data)) {
|
|
434
|
-
if (!knownFields.includes(key)) {
|
|
435
|
-
console.log(`${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
else if (secret.data && 'text' in secret.data) {
|
|
440
|
-
// Plain text secret
|
|
441
|
-
console.log(secret.data.text);
|
|
442
|
-
}
|
|
443
|
-
else if (secret.data && 'content' in secret.data && 'filename' in secret.data) {
|
|
444
|
-
// File-based secret
|
|
445
|
-
console.log(`File: ${secret.data.filename}`);
|
|
446
|
-
console.log(`Size: ${formatBytes(Buffer.from(secret.data.content, 'base64').length)}`);
|
|
447
|
-
if (secret.data.contentType)
|
|
448
|
-
console.log(`Type: ${secret.data.contentType}`);
|
|
449
|
-
console.log('\nUse --output <file> to save the file content');
|
|
450
|
-
}
|
|
451
|
-
else if (secret.data && 'privateKey' in secret.data) {
|
|
452
|
-
// Key pair secret
|
|
453
|
-
console.log('Key Pair Secret:');
|
|
454
|
-
const pk = secret.data.privateKey;
|
|
455
|
-
const pub = secret.data.publicKey;
|
|
456
|
-
if (pk?.filename)
|
|
457
|
-
console.log(` Private Key: ${pk.filename}`);
|
|
458
|
-
if (pub?.filename)
|
|
459
|
-
console.log(` Public Key: ${pub.filename}`);
|
|
460
|
-
console.log('\nUse --output <file> to save the keys');
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
// Generic key-value
|
|
464
|
-
console.log(JSON.stringify(secret.data, null, 2));
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
catch (error) {
|
|
468
|
-
spinner.fail('Failed to decrypt secret');
|
|
469
|
-
output.error(error.message);
|
|
470
|
-
process.exit(1);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
async function createSecret(aliasOrDescription, options) {
|
|
474
|
-
let alias = aliasOrDescription;
|
|
475
|
-
let actualType = options.type || 'opaque';
|
|
476
|
-
let actualSubType = options.subType;
|
|
477
|
-
let actualTags = options.tags;
|
|
478
|
-
// AI Suggestion flow
|
|
479
|
-
if (options.suggest) {
|
|
480
|
-
const tenant = options.tenant || 'me';
|
|
481
|
-
// Analyze file if --file is provided
|
|
482
|
-
let fileInfo = null;
|
|
483
|
-
if (options.file) {
|
|
484
|
-
const analyzeSpinner = ora('Analyzing file...').start();
|
|
485
|
-
fileInfo = await analyzeFileForSuggestion(options.file);
|
|
486
|
-
analyzeSpinner.stop();
|
|
487
|
-
if (fileInfo) {
|
|
488
|
-
output.section('File Analysis');
|
|
489
|
-
const analysisInfo = {
|
|
490
|
-
'Filename': fileInfo.filename,
|
|
491
|
-
'Extension': fileInfo.extension,
|
|
492
|
-
'MIME Type': fileInfo.mimeType,
|
|
493
|
-
'Size': formatBytes(fileInfo.size),
|
|
494
|
-
};
|
|
495
|
-
if (fileInfo.pemInfo) {
|
|
496
|
-
analysisInfo['PEM Type'] = formatPemType(fileInfo.pemInfo.type);
|
|
497
|
-
if (fileInfo.pemInfo.algorithm) {
|
|
498
|
-
analysisInfo['Algorithm'] = fileInfo.pemInfo.algorithm.toUpperCase();
|
|
499
|
-
}
|
|
500
|
-
if (fileInfo.pemInfo.detectedPurpose) {
|
|
501
|
-
analysisInfo['Detected Purpose'] = fileInfo.pemInfo.detectedPurpose;
|
|
502
|
-
}
|
|
503
|
-
if (fileInfo.pemInfo.isAppleP8) {
|
|
504
|
-
analysisInfo['Special'] = 'Apple .p8 authentication key';
|
|
505
|
-
}
|
|
506
|
-
if (fileInfo.pemInfo.certificateCount && fileInfo.pemInfo.certificateCount > 1) {
|
|
507
|
-
analysisInfo['Certificates'] = `${fileInfo.pemInfo.certificateCount} (chain/bundle)`;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
output.keyValue(analysisInfo);
|
|
511
|
-
console.log();
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
const spinner = ora('Getting AI suggestions...').start();
|
|
515
|
-
try {
|
|
516
|
-
const body = { description: aliasOrDescription };
|
|
517
|
-
// Include file analysis in the request
|
|
518
|
-
if (fileInfo) {
|
|
519
|
-
body.fileInfo = fileInfo;
|
|
520
|
-
}
|
|
521
|
-
const response = await client.post(`/v1/advisor/${tenant}/suggest`, body);
|
|
522
|
-
spinner.stop();
|
|
523
|
-
const result = response.data;
|
|
524
|
-
// Show suggestions
|
|
525
|
-
output.section('AI Suggestions');
|
|
526
|
-
output.keyValue({
|
|
527
|
-
'Suggested Alias': result.alias,
|
|
528
|
-
'Type': result.type,
|
|
529
|
-
'Sub-Type': result.subType || '-',
|
|
530
|
-
'Tags': result.tags.join(', ') || 'none',
|
|
531
|
-
'Confidence': `${Math.round(result.confidence * 100)}%`,
|
|
532
|
-
});
|
|
533
|
-
if (result.alternativeAliases && result.alternativeAliases.length > 0) {
|
|
534
|
-
output.info(`Alternatives: ${result.alternativeAliases.join(', ')}`);
|
|
535
|
-
}
|
|
536
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
537
|
-
for (const w of result.warnings) {
|
|
538
|
-
console.log(` ⚠ ${w}`);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
console.log(`\nReasoning: ${result.reasoning}\n`);
|
|
542
|
-
// Let user confirm or modify
|
|
543
|
-
const aliasChoices = [
|
|
544
|
-
{ name: `${result.alias} (suggested)`, value: result.alias },
|
|
545
|
-
...(result.alternativeAliases || []).map(a => ({ name: a, value: a })),
|
|
546
|
-
{ name: 'Enter custom alias', value: '__custom__' },
|
|
547
|
-
];
|
|
548
|
-
const { useAlias } = await inquirer.prompt([
|
|
549
|
-
{
|
|
550
|
-
type: 'list',
|
|
551
|
-
name: 'useAlias',
|
|
552
|
-
message: 'Use which alias?',
|
|
553
|
-
choices: aliasChoices,
|
|
554
|
-
},
|
|
555
|
-
]);
|
|
556
|
-
if (useAlias === '__custom__') {
|
|
557
|
-
const { customAlias } = await inquirer.prompt([
|
|
558
|
-
{ type: 'input', name: 'customAlias', message: 'Enter alias:' },
|
|
559
|
-
]);
|
|
560
|
-
alias = customAlias;
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
alias = useAlias;
|
|
564
|
-
}
|
|
565
|
-
// Apply suggested values (unless overridden by CLI options)
|
|
566
|
-
if (!options.type || options.type === 'opaque') {
|
|
567
|
-
actualType = result.type;
|
|
568
|
-
}
|
|
569
|
-
if (!options.subType && result.subType) {
|
|
570
|
-
actualSubType = result.subType;
|
|
571
|
-
}
|
|
572
|
-
if (!options.tags && result.tags.length > 0) {
|
|
573
|
-
actualTags = result.tags.join(',');
|
|
574
|
-
}
|
|
575
|
-
output.success(`Using alias: ${alias}`);
|
|
576
|
-
console.log();
|
|
577
|
-
}
|
|
578
|
-
catch (err) {
|
|
579
|
-
spinner.fail('Failed to get AI suggestions');
|
|
580
|
-
output.error(err instanceof Error ? err.message : String(err));
|
|
581
|
-
// Continue without suggestions?
|
|
582
|
-
const { continueWithout } = await inquirer.prompt([
|
|
583
|
-
{
|
|
584
|
-
type: 'confirm',
|
|
585
|
-
name: 'continueWithout',
|
|
586
|
-
message: 'Continue creating secret without AI suggestions?',
|
|
587
|
-
default: true,
|
|
588
|
-
},
|
|
589
|
-
]);
|
|
590
|
-
if (!continueWithout) {
|
|
591
|
-
process.exit(0);
|
|
592
|
-
}
|
|
593
|
-
// Use the description as alias
|
|
594
|
-
alias = aliasOrDescription;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
// Resolve tenant: use explicit option, or get from stored credentials
|
|
598
|
-
const authContext = getAuthContext();
|
|
599
|
-
const tenantId = options.tenant || authContext.tenantId;
|
|
600
|
-
if (!tenantId) {
|
|
601
|
-
output.error('Tenant is required. Use --tenant <id> or login to a tenant account.');
|
|
602
|
-
process.exit(1);
|
|
603
|
-
}
|
|
604
|
-
let data = {};
|
|
605
|
-
// Check for non-interactive data options first
|
|
606
|
-
const hasNonInteractiveData = options.username || options.password || options.text || options.data || options.file;
|
|
607
|
-
if (hasNonInteractiveData) {
|
|
608
|
-
// Non-interactive mode: use CLI options
|
|
609
|
-
if (options.username || options.password) {
|
|
610
|
-
actualType = 'credential';
|
|
611
|
-
data = {
|
|
612
|
-
username: options.username || '',
|
|
613
|
-
password: options.password || '',
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
else if (options.text) {
|
|
617
|
-
data = { text: options.text };
|
|
618
|
-
}
|
|
619
|
-
else if (options.data) {
|
|
620
|
-
try {
|
|
621
|
-
data = JSON.parse(options.data);
|
|
622
|
-
}
|
|
623
|
-
catch (e) {
|
|
624
|
-
output.error('Invalid JSON in --data option');
|
|
625
|
-
process.exit(1);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
else if (options.file) {
|
|
629
|
-
const fs = await import('fs');
|
|
630
|
-
const pathModule = await import('path');
|
|
631
|
-
if (!fs.existsSync(options.file)) {
|
|
632
|
-
output.error(`File not found: ${options.file}`);
|
|
633
|
-
process.exit(1);
|
|
634
|
-
}
|
|
635
|
-
const content = fs.readFileSync(options.file);
|
|
636
|
-
const filename = pathModule.basename(options.file);
|
|
637
|
-
data = {
|
|
638
|
-
filename,
|
|
639
|
-
content: content.toString('base64'),
|
|
640
|
-
contentType: options.contentType || 'application/octet-stream',
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
else {
|
|
645
|
-
// Interactive mode: prompt for data
|
|
646
|
-
const { dataType } = await inquirer.prompt([
|
|
647
|
-
{
|
|
648
|
-
type: 'list',
|
|
649
|
-
name: 'dataType',
|
|
650
|
-
message: 'What type of data?',
|
|
651
|
-
choices: [
|
|
652
|
-
{ name: 'Credential (username/password)', value: 'credential' },
|
|
653
|
-
{ name: 'Plain Text', value: 'text' },
|
|
654
|
-
{ name: 'Key-Value pairs', value: 'keyvalue' },
|
|
655
|
-
{ name: 'File upload', value: 'file' },
|
|
656
|
-
],
|
|
657
|
-
},
|
|
658
|
-
]);
|
|
659
|
-
if (dataType === 'credential') {
|
|
660
|
-
actualType = 'credential';
|
|
661
|
-
const answers = await inquirer.prompt([
|
|
662
|
-
{ type: 'input', name: 'username', message: 'Username:' },
|
|
663
|
-
{ type: 'password', name: 'password', message: 'Password:', mask: '*' },
|
|
664
|
-
]);
|
|
665
|
-
data = answers;
|
|
666
|
-
}
|
|
667
|
-
else if (dataType === 'text') {
|
|
668
|
-
const { text } = await inquirer.prompt([
|
|
669
|
-
{ type: 'editor', name: 'text', message: 'Enter text content:' },
|
|
670
|
-
]);
|
|
671
|
-
data = { text: text.trim() };
|
|
672
|
-
}
|
|
673
|
-
else if (dataType === 'keyvalue') {
|
|
674
|
-
console.log('Enter key-value pairs (empty key to finish):');
|
|
675
|
-
while (true) {
|
|
676
|
-
const { key } = await inquirer.prompt([
|
|
677
|
-
{ type: 'input', name: 'key', message: 'Key:' },
|
|
678
|
-
]);
|
|
679
|
-
if (!key)
|
|
680
|
-
break;
|
|
681
|
-
const { value } = await inquirer.prompt([
|
|
682
|
-
{ type: 'input', name: 'value', message: `Value for "${key}":` },
|
|
683
|
-
]);
|
|
684
|
-
data[key] = value;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
else if (dataType === 'file') {
|
|
688
|
-
const { filePath } = await inquirer.prompt([
|
|
689
|
-
{ type: 'input', name: 'filePath', message: 'File path:' },
|
|
690
|
-
]);
|
|
691
|
-
const fs = await import('fs');
|
|
692
|
-
const pathModule = await import('path');
|
|
693
|
-
if (!fs.existsSync(filePath)) {
|
|
694
|
-
output.error(`File not found: ${filePath}`);
|
|
695
|
-
process.exit(1);
|
|
696
|
-
}
|
|
697
|
-
const content = fs.readFileSync(filePath);
|
|
698
|
-
const filename = pathModule.basename(filePath);
|
|
699
|
-
data = {
|
|
700
|
-
filename,
|
|
701
|
-
content: content.toString('base64'),
|
|
702
|
-
contentType: options.contentType || 'application/octet-stream',
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
const spinner = ora('Creating secret...').start();
|
|
707
|
-
try {
|
|
708
|
-
const body = {
|
|
709
|
-
alias,
|
|
710
|
-
tenant: tenantId,
|
|
711
|
-
type: actualType,
|
|
712
|
-
data,
|
|
713
|
-
};
|
|
714
|
-
if (actualSubType)
|
|
715
|
-
body.subType = actualSubType;
|
|
716
|
-
if (actualTags)
|
|
717
|
-
body.tags = actualTags.split(',').map(t => t.trim());
|
|
718
|
-
if (options.ttl)
|
|
719
|
-
body.ttlUntil = options.ttl;
|
|
720
|
-
if (options.expires)
|
|
721
|
-
body.expiresAt = options.expires;
|
|
722
|
-
if (options.contentType)
|
|
723
|
-
body.contentType = options.contentType;
|
|
724
|
-
const result = await client.post('/v1/secrets', body);
|
|
725
|
-
spinner.stop();
|
|
726
|
-
if (options.json) {
|
|
727
|
-
output.json(result);
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
output.success(`Secret created successfully!`);
|
|
731
|
-
console.log(` ID: ${result.id}`);
|
|
732
|
-
console.log(` Alias: ${result.alias}`);
|
|
733
|
-
console.log(` Tenant: ${result.tenant}`);
|
|
734
|
-
}
|
|
735
|
-
catch (error) {
|
|
736
|
-
spinner.fail('Failed to create secret');
|
|
737
|
-
output.error(error.message);
|
|
738
|
-
process.exit(1);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
async function updateSecret(idOrAlias, options) {
|
|
742
|
-
let newData;
|
|
743
|
-
let id;
|
|
744
|
-
// Resolve alias to UUID first
|
|
745
|
-
try {
|
|
746
|
-
id = await resolveSecretId(idOrAlias);
|
|
747
|
-
}
|
|
748
|
-
catch (error) {
|
|
749
|
-
output.error(error.message);
|
|
750
|
-
process.exit(1);
|
|
751
|
-
}
|
|
752
|
-
// Check for non-interactive data option
|
|
753
|
-
if (options.data) {
|
|
754
|
-
// Non-interactive mode: parse JSON data from CLI
|
|
755
|
-
try {
|
|
756
|
-
newData = JSON.parse(options.data);
|
|
757
|
-
}
|
|
758
|
-
catch {
|
|
759
|
-
output.error('Invalid JSON in --data option');
|
|
760
|
-
process.exit(1);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
else {
|
|
764
|
-
// Interactive mode: prompt for data
|
|
765
|
-
const spinner = ora('Fetching current secret...').start();
|
|
766
|
-
try {
|
|
767
|
-
const current = await client.post(`/v1/secrets/${id}/decrypt`, {});
|
|
768
|
-
spinner.stop();
|
|
769
|
-
const { updateData } = await inquirer.prompt([
|
|
770
|
-
{
|
|
771
|
-
type: 'confirm',
|
|
772
|
-
name: 'updateData',
|
|
773
|
-
message: 'Update the secret data?',
|
|
774
|
-
default: false,
|
|
775
|
-
},
|
|
776
|
-
]);
|
|
777
|
-
newData = current.data;
|
|
778
|
-
if (updateData) {
|
|
779
|
-
if (current.type === 'credential') {
|
|
780
|
-
const answers = await inquirer.prompt([
|
|
781
|
-
{
|
|
782
|
-
type: 'input',
|
|
783
|
-
name: 'username',
|
|
784
|
-
message: 'Username:',
|
|
785
|
-
default: current.data.username
|
|
786
|
-
},
|
|
787
|
-
{
|
|
788
|
-
type: 'password',
|
|
789
|
-
name: 'password',
|
|
790
|
-
message: 'Password (leave empty to keep current):',
|
|
791
|
-
mask: '*'
|
|
792
|
-
},
|
|
793
|
-
]);
|
|
794
|
-
newData = {
|
|
795
|
-
username: answers.username,
|
|
796
|
-
password: answers.password || current.data.password,
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
else {
|
|
800
|
-
const { dataJson } = await inquirer.prompt([
|
|
801
|
-
{
|
|
802
|
-
type: 'editor',
|
|
803
|
-
name: 'dataJson',
|
|
804
|
-
message: 'Edit data (JSON):',
|
|
805
|
-
default: JSON.stringify(current.data, null, 2),
|
|
806
|
-
},
|
|
807
|
-
]);
|
|
808
|
-
try {
|
|
809
|
-
newData = JSON.parse(dataJson);
|
|
810
|
-
}
|
|
811
|
-
catch {
|
|
812
|
-
output.error('Invalid JSON data');
|
|
813
|
-
process.exit(1);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
catch (error) {
|
|
819
|
-
spinner.fail('Failed to fetch current secret');
|
|
820
|
-
output.error(error.message);
|
|
821
|
-
process.exit(1);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
const updateSpinner = ora('Updating secret...').start();
|
|
825
|
-
try {
|
|
826
|
-
const body = { data: newData };
|
|
827
|
-
if (options.tags)
|
|
828
|
-
body.tags = options.tags.split(',').map(t => t.trim());
|
|
829
|
-
if (options.ttl)
|
|
830
|
-
body.ttlUntil = options.ttl;
|
|
831
|
-
if (options.expires)
|
|
832
|
-
body.expiresAt = options.expires;
|
|
833
|
-
const result = await client.put(`/v1/secrets/${id}`, body);
|
|
834
|
-
updateSpinner.stop();
|
|
835
|
-
if (options.json) {
|
|
836
|
-
output.json(result);
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
output.success('Secret updated successfully!');
|
|
840
|
-
console.log(` Version: ${result.version}`);
|
|
841
|
-
}
|
|
842
|
-
catch (error) {
|
|
843
|
-
updateSpinner.fail('Failed to update secret');
|
|
844
|
-
output.error(error.message);
|
|
845
|
-
process.exit(1);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
async function deleteSecret(idOrAlias, options) {
|
|
849
|
-
let id;
|
|
850
|
-
// Resolve alias to UUID first
|
|
851
|
-
try {
|
|
852
|
-
id = await resolveSecretId(idOrAlias);
|
|
853
|
-
}
|
|
854
|
-
catch (error) {
|
|
855
|
-
output.error(error.message);
|
|
856
|
-
process.exit(1);
|
|
857
|
-
}
|
|
858
|
-
if (!options.force) {
|
|
859
|
-
// Get metadata first
|
|
860
|
-
const spinner = ora('Fetching secret...').start();
|
|
861
|
-
try {
|
|
862
|
-
const secret = await client.get(`/v1/secrets/${id}/meta`);
|
|
863
|
-
spinner.stop();
|
|
864
|
-
const { confirm } = await inquirer.prompt([
|
|
865
|
-
{
|
|
866
|
-
type: 'confirm',
|
|
867
|
-
name: 'confirm',
|
|
868
|
-
message: `Delete secret "${secret.alias}" (${id})? This cannot be undone.`,
|
|
869
|
-
default: false,
|
|
870
|
-
},
|
|
871
|
-
]);
|
|
872
|
-
if (!confirm) {
|
|
873
|
-
output.info('Deletion cancelled');
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
catch (error) {
|
|
878
|
-
spinner.fail('Failed to fetch secret');
|
|
879
|
-
output.error(error.message);
|
|
880
|
-
process.exit(1);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
const deleteSpinner = ora('Deleting secret...').start();
|
|
884
|
-
try {
|
|
885
|
-
await client.delete(`/v1/secrets/${id}`);
|
|
886
|
-
deleteSpinner.stop();
|
|
887
|
-
output.success('Secret deleted successfully');
|
|
888
|
-
}
|
|
889
|
-
catch (error) {
|
|
890
|
-
deleteSpinner.fail('Failed to delete secret');
|
|
891
|
-
output.error(error.message);
|
|
892
|
-
process.exit(1);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
async function rotateSecret(idOrAlias, options) {
|
|
896
|
-
const spinner = ora('Resolving secret...').start();
|
|
897
|
-
try {
|
|
898
|
-
// Resolve alias to UUID if needed
|
|
899
|
-
const id = await resolveSecretId(idOrAlias);
|
|
900
|
-
spinner.text = 'Fetching current secret...';
|
|
901
|
-
const current = await client.post(`/v1/secrets/${id}/decrypt`, {});
|
|
902
|
-
spinner.stop();
|
|
903
|
-
console.log(`Current secret: ${current.alias} (v${current.version})`);
|
|
904
|
-
// Prompt for new data
|
|
905
|
-
let newData;
|
|
906
|
-
if (current.type === 'credential') {
|
|
907
|
-
const answers = await inquirer.prompt([
|
|
908
|
-
{
|
|
909
|
-
type: 'password',
|
|
910
|
-
name: 'password',
|
|
911
|
-
message: 'New password:',
|
|
912
|
-
mask: '*',
|
|
913
|
-
validate: (input) => input.length > 0 || 'Password is required',
|
|
914
|
-
},
|
|
915
|
-
]);
|
|
916
|
-
newData = {
|
|
917
|
-
username: current.data.username,
|
|
918
|
-
password: answers.password,
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
else {
|
|
922
|
-
const { dataJson } = await inquirer.prompt([
|
|
923
|
-
{
|
|
924
|
-
type: 'editor',
|
|
925
|
-
name: 'dataJson',
|
|
926
|
-
message: 'Enter new data (JSON):',
|
|
927
|
-
default: JSON.stringify(current.data, null, 2),
|
|
928
|
-
},
|
|
929
|
-
]);
|
|
930
|
-
try {
|
|
931
|
-
newData = JSON.parse(dataJson);
|
|
932
|
-
}
|
|
933
|
-
catch {
|
|
934
|
-
output.error('Invalid JSON data');
|
|
935
|
-
process.exit(1);
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
const rotateSpinner = ora('Rotating secret...').start();
|
|
939
|
-
const result = await client.post(`/v1/secrets/${id}/rotate`, { data: newData });
|
|
940
|
-
rotateSpinner.stop();
|
|
941
|
-
if (options.json) {
|
|
942
|
-
output.json(result);
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
output.success('Secret rotated successfully!');
|
|
946
|
-
console.log(` New Version: ${result.version}`);
|
|
947
|
-
}
|
|
948
|
-
catch (error) {
|
|
949
|
-
spinner.fail('Failed to rotate secret');
|
|
950
|
-
output.error(error.message);
|
|
951
|
-
process.exit(1);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
async function showHistory(idOrAlias, options) {
|
|
955
|
-
const spinner = ora('Resolving secret...').start();
|
|
956
|
-
try {
|
|
957
|
-
// Resolve alias to UUID if needed
|
|
958
|
-
const id = await resolveSecretId(idOrAlias);
|
|
959
|
-
spinner.text = 'Fetching secret history...';
|
|
960
|
-
const response = await client.get(`/v1/secrets/${id}/history`);
|
|
961
|
-
spinner.stop();
|
|
962
|
-
const history = response.history || [];
|
|
963
|
-
if (options.json) {
|
|
964
|
-
output.json(history);
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
if (history.length === 0) {
|
|
968
|
-
output.info('No version history found');
|
|
969
|
-
return;
|
|
970
|
-
}
|
|
971
|
-
const table = new Table({
|
|
972
|
-
head: ['Version', 'Created At', 'Superseded At', 'Created By'],
|
|
973
|
-
colWidths: [10, 25, 25, 30],
|
|
974
|
-
});
|
|
975
|
-
for (const entry of history) {
|
|
976
|
-
table.push([
|
|
977
|
-
String(entry.version),
|
|
978
|
-
formatDate(entry.createdAt),
|
|
979
|
-
entry.supersededAt ? formatDate(entry.supersededAt) : '-',
|
|
980
|
-
entry.createdBy || '-',
|
|
981
|
-
]);
|
|
982
|
-
}
|
|
983
|
-
console.log(table.toString());
|
|
984
|
-
console.log(`Total: ${response.count} version(s)`);
|
|
985
|
-
}
|
|
986
|
-
catch (error) {
|
|
987
|
-
spinner.fail('Failed to fetch history');
|
|
988
|
-
output.error(error.message);
|
|
989
|
-
process.exit(1);
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
async function copySecret(source, destinationAlias, options) {
|
|
993
|
-
const spinner = ora('Copying secret...').start();
|
|
994
|
-
try {
|
|
995
|
-
const body = {
|
|
996
|
-
source,
|
|
997
|
-
destinationAlias,
|
|
998
|
-
includeMetadata: !options.noMetadata,
|
|
999
|
-
};
|
|
1000
|
-
const result = await client.post('/v1/secrets/copy', body);
|
|
1001
|
-
spinner.stop();
|
|
1002
|
-
if (options.json) {
|
|
1003
|
-
output.json(result);
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
output.success('Secret copied successfully!');
|
|
1007
|
-
console.log(` New ID: ${result.id}`);
|
|
1008
|
-
console.log(` New Alias: ${result.alias}`);
|
|
1009
|
-
console.log(` Tenant: ${result.tenant}`);
|
|
1010
|
-
console.log(` Type: ${formatType(result.type, result.subType)}`);
|
|
1011
|
-
console.log(` Copied From:`);
|
|
1012
|
-
console.log(` ID: ${result.copiedFrom.id}`);
|
|
1013
|
-
console.log(` Alias: ${result.copiedFrom.alias}`);
|
|
1014
|
-
console.log(` Version: ${result.copiedFrom.version}`);
|
|
1015
|
-
}
|
|
1016
|
-
catch (error) {
|
|
1017
|
-
spinner.fail('Failed to copy secret');
|
|
1018
|
-
output.error(error.message);
|
|
1019
|
-
process.exit(1);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
// ============================================================================
|
|
1023
|
-
// Command Registration
|
|
1024
|
-
// ============================================================================
|
|
1025
|
-
// Help text for secret identifier format
|
|
1026
|
-
const SECRET_ID_HELP = `
|
|
1027
|
-
Secret Identifier Formats:
|
|
1028
|
-
Commands that accept <id-or-alias> support these formats:
|
|
1029
|
-
|
|
1030
|
-
1. UUID: abc12345-1234-5678-9abc-def012345678
|
|
1031
|
-
2. Alias: zn-admin/config, web/api-key, smtp-credentials
|
|
1032
|
-
3. Prefix: alias:zn-admin/config (optional "alias:" prefix)
|
|
1033
|
-
|
|
1034
|
-
Note: Tenant is derived from your authenticated user (JWT).
|
|
1035
|
-
You can only access secrets within your assigned tenant.
|
|
1036
|
-
|
|
1037
|
-
Examples:
|
|
1038
|
-
znvault secret decrypt zn-admin/config
|
|
1039
|
-
znvault secret get web/production/api-key
|
|
1040
|
-
znvault secret history alias:database/credentials
|
|
1041
|
-
znvault secret delete abc12345-1234-5678-9abc-def012345678
|
|
1042
|
-
`;
|
|
1043
|
-
export function registerSecretCommands(program) {
|
|
1044
|
-
const secret = program
|
|
1045
|
-
.command('secret')
|
|
1046
|
-
.description('Manage secrets')
|
|
1047
|
-
.addHelpText('after', SECRET_ID_HELP);
|
|
1048
|
-
// List secrets
|
|
1049
|
-
secret
|
|
1050
|
-
.command('list')
|
|
1051
|
-
.description('List secrets (metadata only)')
|
|
1052
|
-
.option('-t, --tenant <id>', 'Filter by tenant')
|
|
1053
|
-
.option('--type <type>', 'Filter by type (opaque, credential, setting)')
|
|
1054
|
-
.option('--sub-type <subType>', 'Filter by sub-type')
|
|
1055
|
-
.option('--alias-prefix <prefix>', 'Filter by alias prefix')
|
|
1056
|
-
.option('--expiring <days>', 'Show secrets expiring within N days')
|
|
1057
|
-
.option('--json', 'Output as JSON')
|
|
1058
|
-
.action(listSecrets);
|
|
1059
|
-
// Get secret metadata
|
|
1060
|
-
secret
|
|
1061
|
-
.command('get <id-or-alias>')
|
|
1062
|
-
.description('Get secret metadata (supports UUID or tenant/alias format)')
|
|
1063
|
-
.option('--json', 'Output as JSON')
|
|
1064
|
-
.action(getSecret);
|
|
1065
|
-
// Decrypt secret
|
|
1066
|
-
secret
|
|
1067
|
-
.command('decrypt <id-or-alias>')
|
|
1068
|
-
.description('Decrypt and show secret value (supports UUID or tenant/alias format)')
|
|
1069
|
-
.option('-o, --output <file>', 'Write content to file')
|
|
1070
|
-
.option('--json', 'Output as JSON')
|
|
1071
|
-
.addHelpText('after', `
|
|
1072
|
-
Examples:
|
|
1073
|
-
znvault secret decrypt zn-admin/config # by alias path
|
|
1074
|
-
znvault secret decrypt alias:web/api-key # with alias: prefix
|
|
1075
|
-
znvault secret decrypt abc12345-... # by UUID
|
|
1076
|
-
znvault secret decrypt certs/server-key -o key.pem # save to file
|
|
1077
|
-
`)
|
|
1078
|
-
.action(decryptSecret);
|
|
1079
|
-
// Create secret
|
|
1080
|
-
secret
|
|
1081
|
-
.command('create <alias>')
|
|
1082
|
-
.description('Create a new secret (use --suggest for AI naming help)')
|
|
1083
|
-
.option('-t, --tenant <id>', 'Tenant ID (defaults to current user tenant)')
|
|
1084
|
-
.option('--type <type>', 'Secret type (opaque, credential, setting)', 'opaque')
|
|
1085
|
-
.option('--sub-type <subType>', 'Semantic sub-type')
|
|
1086
|
-
.option('--tags <tags>', 'Comma-separated tags')
|
|
1087
|
-
.option('--ttl <datetime>', 'TTL expiration (ISO 8601)')
|
|
1088
|
-
.option('--expires <datetime>', 'Natural expiration (ISO 8601)')
|
|
1089
|
-
.option('--content-type <mime>', 'Content type for settings')
|
|
1090
|
-
.option('--json', 'Output as JSON')
|
|
1091
|
-
.option('--suggest', 'Get AI suggestions for naming (alias becomes description)')
|
|
1092
|
-
// Non-interactive data options
|
|
1093
|
-
.option('--username <username>', 'Username for credential type (non-interactive)')
|
|
1094
|
-
.option('--password <password>', 'Password for credential type (non-interactive)')
|
|
1095
|
-
.option('--text <text>', 'Text content (non-interactive)')
|
|
1096
|
-
.option('--data <json>', 'JSON data (non-interactive)')
|
|
1097
|
-
.option('--file <path>', 'File to upload (non-interactive)')
|
|
1098
|
-
.action(createSecret);
|
|
1099
|
-
// Update secret
|
|
1100
|
-
secret
|
|
1101
|
-
.command('update <id-or-alias>')
|
|
1102
|
-
.description('Update a secret (supports UUID or tenant/alias format)')
|
|
1103
|
-
.option('--tags <tags>', 'Comma-separated tags')
|
|
1104
|
-
.option('--ttl <datetime>', 'TTL expiration (ISO 8601)')
|
|
1105
|
-
.option('--expires <datetime>', 'Natural expiration (ISO 8601)')
|
|
1106
|
-
.option('--json', 'Output as JSON')
|
|
1107
|
-
.option('--data <json>', 'New data as JSON (non-interactive)')
|
|
1108
|
-
.action(updateSecret);
|
|
1109
|
-
// Delete secret
|
|
1110
|
-
secret
|
|
1111
|
-
.command('delete <id-or-alias>')
|
|
1112
|
-
.description('Delete a secret (supports UUID or tenant/alias format)')
|
|
1113
|
-
.option('-f, --force', 'Skip confirmation')
|
|
1114
|
-
.action(deleteSecret);
|
|
1115
|
-
// Rotate secret
|
|
1116
|
-
secret
|
|
1117
|
-
.command('rotate <id-or-alias>')
|
|
1118
|
-
.description('Rotate secret (supports UUID or tenant/alias format)')
|
|
1119
|
-
.option('--json', 'Output as JSON')
|
|
1120
|
-
.action(rotateSecret);
|
|
1121
|
-
// Show history
|
|
1122
|
-
secret
|
|
1123
|
-
.command('history <id-or-alias>')
|
|
1124
|
-
.description('Show secret version history (supports UUID or tenant/alias format)')
|
|
1125
|
-
.option('--json', 'Output as JSON')
|
|
1126
|
-
.action(showHistory);
|
|
1127
|
-
// Copy secret
|
|
1128
|
-
secret
|
|
1129
|
-
.command('copy <source> <destination-alias>')
|
|
1130
|
-
.description('Copy a secret to a new location')
|
|
1131
|
-
.option('--no-metadata', 'Do not copy tags/metadata')
|
|
1132
|
-
.option('--json', 'Output as JSON')
|
|
1133
|
-
.action(copySecret);
|
|
1134
|
-
}
|
|
8
|
+
export { registerSecretCommands } from './secret/index.js';
|
|
9
|
+
export * from './secret/types.js';
|
|
1135
10
|
//# sourceMappingURL=secret.js.map
|