@zincapp/znvault-cli 2.4.0 → 2.6.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 (37) hide show
  1. package/README.md +267 -215
  2. package/dist/commands/agent.d.ts.map +1 -1
  3. package/dist/commands/agent.js +159 -476
  4. package/dist/commands/agent.js.map +1 -1
  5. package/dist/commands/auth.d.ts.map +1 -1
  6. package/dist/commands/auth.js +82 -13
  7. package/dist/commands/auth.js.map +1 -1
  8. package/dist/commands/backup/config.d.ts +10 -0
  9. package/dist/commands/backup/config.d.ts.map +1 -0
  10. package/dist/commands/backup/config.js +246 -0
  11. package/dist/commands/backup/config.js.map +1 -0
  12. package/dist/commands/backup/helpers.d.ts +8 -0
  13. package/dist/commands/backup/helpers.d.ts.map +1 -0
  14. package/dist/commands/backup/helpers.js +81 -0
  15. package/dist/commands/backup/helpers.js.map +1 -0
  16. package/dist/commands/{backup.d.ts → backup/index.d.ts} +2 -1
  17. package/dist/commands/backup/index.d.ts.map +1 -0
  18. package/dist/commands/backup/index.js +129 -0
  19. package/dist/commands/backup/index.js.map +1 -0
  20. package/dist/commands/backup/operations.d.ts +15 -0
  21. package/dist/commands/backup/operations.d.ts.map +1 -0
  22. package/dist/commands/backup/operations.js +390 -0
  23. package/dist/commands/backup/operations.js.map +1 -0
  24. package/dist/commands/backup/types.d.ts +144 -0
  25. package/dist/commands/backup/types.d.ts.map +1 -0
  26. package/dist/commands/backup/types.js +4 -0
  27. package/dist/commands/backup/types.js.map +1 -0
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/lib/config.d.ts +11 -1
  31. package/dist/lib/config.d.ts.map +1 -1
  32. package/dist/lib/config.js +20 -1
  33. package/dist/lib/config.js.map +1 -1
  34. package/package.json +1 -1
  35. package/dist/commands/backup.d.ts.map +0 -1
  36. package/dist/commands/backup.js +0 -646
  37. package/dist/commands/backup.js.map +0 -1
@@ -1,16 +1,7 @@
1
+ // Path: znvault-cli/src/commands/agent.ts
1
2
  import ora from 'ora';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as os from 'os';
5
- import { spawn } from 'child_process';
6
3
  import * as mode from '../lib/mode.js';
7
4
  import * as output from '../lib/output.js';
8
- import * as config from '../lib/config.js';
9
- // Config locations - match standalone agent
10
- const SYSTEM_CONFIG_DIR = '/etc/zn-vault-agent';
11
- const SYSTEM_CONFIG_FILE = path.join(SYSTEM_CONFIG_DIR, 'config.json');
12
- const USER_CONFIG_DIR = path.join(os.homedir(), '.config', 'zn-vault-agent');
13
- const USER_CONFIG_FILE = path.join(USER_CONFIG_DIR, 'config.json');
14
5
  /**
15
6
  * Format relative time for display
16
7
  */
@@ -29,470 +20,10 @@ function formatRelativeTime(dateStr) {
29
20
  return `${diffHours}h ago`;
30
21
  return `${diffDays}d ago`;
31
22
  }
32
- // State file location
33
- const STATE_DIR = path.join(os.homedir(), '.local', 'state', 'zn-vault-agent');
34
- const DEFAULT_STATE_FILE = path.join(STATE_DIR, 'state.json');
35
- /**
36
- * Get the appropriate config file path based on privileges
37
- */
38
- function getConfigPath() {
39
- // If running as root and system config exists, use it
40
- if (process.getuid?.() === 0) {
41
- return SYSTEM_CONFIG_FILE;
42
- }
43
- // Check if system config exists (for non-root reading)
44
- if (fs.existsSync(SYSTEM_CONFIG_FILE)) {
45
- return SYSTEM_CONFIG_FILE;
46
- }
47
- // Fall back to user config
48
- return USER_CONFIG_FILE;
49
- }
50
- /**
51
- * Load agent configuration
52
- */
53
- function loadConfig(configPath) {
54
- const filePath = configPath ?? getConfigPath();
55
- if (!fs.existsSync(filePath)) {
56
- return null;
57
- }
58
- try {
59
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
60
- }
61
- catch {
62
- return null;
63
- }
64
- }
65
- /**
66
- * Save agent configuration
67
- */
68
- function saveConfig(agentConfig, configPath) {
69
- const filePath = configPath ?? getConfigPath();
70
- const dir = path.dirname(filePath);
71
- if (!fs.existsSync(dir)) {
72
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
73
- }
74
- fs.writeFileSync(filePath, JSON.stringify(agentConfig, null, 2), { mode: 0o600 });
75
- }
76
- /**
77
- * Create default config with current CLI credentials
78
- */
79
- function createDefaultConfig() {
80
- const cliConfig = config.getConfig();
81
- const credentials = config.getCredentials();
82
- const envCredentials = config.getEnvCredentials();
83
- const apiKey = config.getApiKey();
84
- // Get tenant from: env > stored credentials > default tenant
85
- const tenantId = process.env.ZNVAULT_TENANT_ID ??
86
- credentials?.tenantId ??
87
- cliConfig.defaultTenant ??
88
- '';
89
- return {
90
- vaultUrl: cliConfig.url,
91
- tenantId,
92
- auth: {
93
- apiKey: apiKey,
94
- username: envCredentials?.username,
95
- password: envCredentials?.password,
96
- },
97
- insecure: cliConfig.insecure,
98
- targets: [],
99
- pollInterval: 3600,
100
- };
101
- }
102
23
  export function registerAgentCommands(program) {
103
24
  const agent = program
104
25
  .command('agent')
105
- .description('Certificate synchronization agent configuration and management');
106
- // Initialize agent configuration
107
- agent
108
- .command('init')
109
- .description('Initialize agent configuration')
110
- .option('-c, --config <path>', 'Config file path')
111
- .action((options) => {
112
- const configPath = options.config ?? getConfigPath();
113
- if (fs.existsSync(configPath)) {
114
- output.error(`Config already exists at ${configPath}`);
115
- output.info('Use "znvault agent add" to add certificates');
116
- process.exit(1);
117
- }
118
- const agentConfig = createDefaultConfig();
119
- saveConfig(agentConfig, configPath);
120
- console.log(`Agent configuration initialized at ${configPath}`);
121
- console.log();
122
- console.log('Next steps:');
123
- console.log(' 1. Add certificates: znvault agent add <cert-id> --combined /path/to/cert.pem');
124
- console.log(' 2. Start the agent: zn-vault-agent start');
125
- console.log();
126
- console.log('Or install as systemd service:');
127
- console.log(' sudo systemctl enable --now zn-vault-agent');
128
- });
129
- // Add certificate to sync
130
- agent
131
- .command('add <cert-id>')
132
- .description('Add a certificate to sync')
133
- .option('-n, --name <name>', 'Human-readable name for the certificate')
134
- .option('--combined <path>', 'Output path for combined cert+key file (HAProxy)')
135
- .option('--cert <path>', 'Output path for certificate file')
136
- .option('--key <path>', 'Output path for private key file')
137
- .option('--chain <path>', 'Output path for CA chain file')
138
- .option('--fullchain <path>', 'Output path for fullchain file (cert+chain)')
139
- .option('--owner <user:group>', 'File ownership (e.g., haproxy:haproxy)')
140
- .option('--mode <mode>', 'File permissions (e.g., 0640)', '0640')
141
- .option('--reload <command>', 'Command to run after cert update')
142
- .option('--health-check <command>', 'Health check command (must return 0)')
143
- .option('-c, --config <path>', 'Config file path')
144
- .action(async (certId, options) => {
145
- const spinner = ora('Validating certificate...').start();
146
- try {
147
- // Validate the certificate exists
148
- const cert = await mode.apiGet(`/v1/certificates/${certId}`);
149
- spinner.stop();
150
- // Load or create config
151
- const configPath = options.config ?? getConfigPath();
152
- let agentConfig = loadConfig(configPath);
153
- if (!agentConfig) {
154
- output.info('No config found, creating with current CLI credentials...');
155
- agentConfig = createDefaultConfig();
156
- }
157
- // Check if already added
158
- if (agentConfig.targets.some(t => t.certId === certId)) {
159
- output.error(`Certificate ${certId} is already configured`);
160
- process.exit(1);
161
- }
162
- // Validate at least one output is specified
163
- if (!options.combined && !options.cert && !options.key && !options.chain && !options.fullchain) {
164
- output.error('At least one output path is required (--combined, --cert, --key, --chain, or --fullchain)');
165
- process.exit(1);
166
- }
167
- const target = {
168
- certId,
169
- name: options.name ?? cert.alias,
170
- outputs: {},
171
- mode: options.mode,
172
- };
173
- if (options.combined)
174
- target.outputs.combined = options.combined;
175
- if (options.cert)
176
- target.outputs.cert = options.cert;
177
- if (options.key)
178
- target.outputs.key = options.key;
179
- if (options.chain)
180
- target.outputs.chain = options.chain;
181
- if (options.fullchain)
182
- target.outputs.fullchain = options.fullchain;
183
- if (options.owner)
184
- target.owner = options.owner;
185
- if (options.reload)
186
- target.reloadCmd = options.reload;
187
- if (options.healthCheck)
188
- target.healthCheckCmd = options.healthCheck;
189
- agentConfig.targets.push(target);
190
- saveConfig(agentConfig, configPath);
191
- console.log(`Added certificate: ${target.name} (${certId})`);
192
- if (target.outputs.combined)
193
- console.log(` Combined: ${target.outputs.combined}`);
194
- if (target.outputs.cert)
195
- console.log(` Certificate: ${target.outputs.cert}`);
196
- if (target.outputs.key)
197
- console.log(` Private key: ${target.outputs.key}`);
198
- if (target.outputs.chain)
199
- console.log(` Chain: ${target.outputs.chain}`);
200
- if (target.outputs.fullchain)
201
- console.log(` Fullchain: ${target.outputs.fullchain}`);
202
- if (target.reloadCmd)
203
- console.log(` Reload: ${target.reloadCmd}`);
204
- }
205
- catch (err) {
206
- spinner.fail('Failed to add certificate');
207
- output.error(err instanceof Error ? err.message : String(err));
208
- process.exit(1);
209
- }
210
- finally {
211
- await mode.closeLocalClient();
212
- }
213
- });
214
- // Remove certificate from sync
215
- agent
216
- .command('remove <cert-id-or-name>')
217
- .description('Remove a certificate from sync')
218
- .option('-c, --config <path>', 'Config file path')
219
- .action((certIdOrName, options) => {
220
- const configPath = options.config ?? getConfigPath();
221
- const agentConfig = loadConfig(configPath);
222
- if (!agentConfig) {
223
- output.error(`Config not found. Run 'znvault agent init' first.`);
224
- process.exit(1);
225
- }
226
- const idx = agentConfig.targets.findIndex(t => t.certId === certIdOrName || t.name === certIdOrName);
227
- if (idx === -1) {
228
- output.error(`Certificate "${certIdOrName}" not found in configuration`);
229
- process.exit(1);
230
- }
231
- const removed = agentConfig.targets.splice(idx, 1)[0];
232
- saveConfig(agentConfig, configPath);
233
- console.log(`Removed certificate: ${removed.name} (${removed.certId})`);
234
- });
235
- // List configured certificates
236
- agent
237
- .command('list')
238
- .description('List configured certificates')
239
- .option('-c, --config <path>', 'Config file path')
240
- .option('--json', 'Output as JSON')
241
- .action((options) => {
242
- const configPath = options.config ?? getConfigPath();
243
- const agentConfig = loadConfig(configPath);
244
- if (!agentConfig) {
245
- output.error(`Config not found. Run 'znvault agent init' first.`);
246
- process.exit(1);
247
- }
248
- if (options.json) {
249
- output.json(agentConfig);
250
- return;
251
- }
252
- console.log(`Config: ${configPath}`);
253
- console.log(`Vault: ${agentConfig.vaultUrl}`);
254
- console.log(`Tenant: ${agentConfig.tenantId}`);
255
- console.log(`Certificates: ${agentConfig.targets.length}`);
256
- console.log();
257
- if (agentConfig.targets.length === 0) {
258
- console.log('No certificates configured. Use "znvault agent add <cert-id>" to add one.');
259
- return;
260
- }
261
- output.table(['Name', 'Cert ID', 'Outputs', 'Reload'], agentConfig.targets.map(t => [
262
- t.name,
263
- t.certId.substring(0, 8) + '...',
264
- Object.entries(t.outputs).filter(([, v]) => v).map(([k]) => k).join(', '),
265
- t.reloadCmd ? t.reloadCmd.substring(0, 30) : '-',
266
- ]));
267
- });
268
- // Sync certificates (one-time)
269
- agent
270
- .command('sync')
271
- .description('Sync all configured certificates (one-time)')
272
- .option('-c, --config <path>', 'Config file path')
273
- .option('-s, --state <path>', 'State file path', DEFAULT_STATE_FILE)
274
- .option('--force', 'Force sync even if unchanged')
275
- .action(async (options) => {
276
- const spinner = ora('Syncing certificates...').start();
277
- try {
278
- const configPath = options.config ?? getConfigPath();
279
- const agentConfig = loadConfig(configPath);
280
- if (!agentConfig) {
281
- spinner.fail('Config not found');
282
- output.error(`Run 'znvault agent init' first.`);
283
- process.exit(1);
284
- }
285
- if (agentConfig.targets.length === 0) {
286
- spinner.fail('No certificates configured');
287
- output.error('Use "znvault agent add <cert-id>" to add certificates.');
288
- process.exit(1);
289
- }
290
- // Load or create state
291
- let state = { certificates: {}, lastUpdate: new Date().toISOString() };
292
- if (fs.existsSync(options.state)) {
293
- state = JSON.parse(fs.readFileSync(options.state, 'utf-8'));
294
- }
295
- let synced = 0;
296
- let skipped = 0;
297
- let failed = 0;
298
- for (const target of agentConfig.targets) {
299
- try {
300
- // Get certificate with decrypted data
301
- const cert = await mode.apiPost(`/v1/certificates/${target.certId}/decrypt`, { purpose: 'agent-sync' });
302
- // Check if changed
303
- const certFingerprint = cert.fingerprintSha256;
304
- const existingState = state.certificates[target.certId];
305
- if (!options.force && existingState.fingerprint === certFingerprint) {
306
- skipped++;
307
- continue;
308
- }
309
- // Decode certificate data
310
- const certData = Buffer.from(cert.certificateData, 'base64').toString('utf-8');
311
- const keyData = cert.privateKeyData ? Buffer.from(cert.privateKeyData, 'base64').toString('utf-8') : null;
312
- const chainData = cert.chainData ? Buffer.from(cert.chainData, 'base64').toString('utf-8') : null;
313
- const fileMode = parseInt(target.mode ?? '0640', 8);
314
- // Write certificate file
315
- if (target.outputs.cert) {
316
- ensureDir(path.dirname(target.outputs.cert));
317
- fs.writeFileSync(target.outputs.cert, certData, { mode: fileMode });
318
- }
319
- // Write private key
320
- if (target.outputs.key && keyData) {
321
- ensureDir(path.dirname(target.outputs.key));
322
- fs.writeFileSync(target.outputs.key, keyData, { mode: 0o600 });
323
- }
324
- // Write chain
325
- if (target.outputs.chain && chainData) {
326
- ensureDir(path.dirname(target.outputs.chain));
327
- fs.writeFileSync(target.outputs.chain, chainData, { mode: fileMode });
328
- }
329
- // Write fullchain (cert + chain)
330
- if (target.outputs.fullchain) {
331
- let fullchain = certData;
332
- if (chainData)
333
- fullchain += '\n' + chainData;
334
- ensureDir(path.dirname(target.outputs.fullchain));
335
- fs.writeFileSync(target.outputs.fullchain, fullchain, { mode: fileMode });
336
- }
337
- // Write combined (cert + key + chain)
338
- if (target.outputs.combined) {
339
- let combined = certData;
340
- if (keyData)
341
- combined += '\n' + keyData;
342
- if (chainData)
343
- combined += '\n' + chainData;
344
- ensureDir(path.dirname(target.outputs.combined));
345
- fs.writeFileSync(target.outputs.combined, combined, { mode: 0o600 });
346
- }
347
- // Update state
348
- state.certificates[target.certId] = {
349
- id: target.certId,
350
- alias: cert.alias,
351
- lastSync: new Date().toISOString(),
352
- version: cert.version,
353
- fingerprint: certFingerprint,
354
- };
355
- synced++;
356
- }
357
- catch (err) {
358
- failed++;
359
- console.error(`\nFailed to sync ${target.name}: ${err instanceof Error ? err.message : String(err)}`);
360
- }
361
- }
362
- // Save state
363
- state.lastUpdate = new Date().toISOString();
364
- ensureDir(path.dirname(options.state));
365
- fs.writeFileSync(options.state, JSON.stringify(state, null, 2));
366
- spinner.stop();
367
- console.log(`Sync complete: ${synced} synced, ${skipped} unchanged, ${failed} failed`);
368
- }
369
- catch (err) {
370
- spinner.fail('Sync failed');
371
- output.error(err instanceof Error ? err.message : String(err));
372
- process.exit(1);
373
- }
374
- finally {
375
- await mode.closeLocalClient();
376
- }
377
- });
378
- // Start agent daemon (delegates to standalone agent)
379
- agent
380
- .command('start')
381
- .description('Start the certificate sync agent daemon')
382
- .option('-c, --config <path>', 'Config file path')
383
- .option('-v, --verbose', 'Enable verbose logging')
384
- .option('--health-port <port>', 'Enable health/metrics HTTP server')
385
- .option('--foreground', 'Run in foreground')
386
- .action((options) => {
387
- const configPath = options.config ?? getConfigPath();
388
- if (!fs.existsSync(configPath)) {
389
- output.error(`Config not found at ${configPath}`);
390
- output.info(`Run 'znvault agent init' first.`);
391
- process.exit(1);
392
- }
393
- // Build command arguments
394
- const args = ['start'];
395
- if (options.verbose)
396
- args.push('--verbose');
397
- if (options.healthPort)
398
- args.push('--health-port', options.healthPort);
399
- // Set config path via environment if not default
400
- const env = { ...process.env };
401
- if (options.config) {
402
- env.ZNVAULT_AGENT_CONFIG_DIR = path.dirname(options.config);
403
- }
404
- console.log('Starting zn-vault-agent daemon...');
405
- console.log();
406
- // Try to find the standalone agent
407
- const agentPaths = [
408
- '/usr/local/bin/zn-vault-agent',
409
- '/usr/bin/zn-vault-agent',
410
- path.join(os.homedir(), '.local', 'bin', 'zn-vault-agent'),
411
- // Development: check sibling directory
412
- path.resolve(__dirname, '..', '..', '..', '..', 'zn-vault-agent', 'dist', 'index.js'),
413
- ];
414
- let agentPath = null;
415
- for (const p of agentPaths) {
416
- if (fs.existsSync(p)) {
417
- agentPath = p;
418
- break;
419
- }
420
- }
421
- if (!agentPath) {
422
- output.error('zn-vault-agent not found');
423
- console.log();
424
- console.log('Install the standalone agent:');
425
- console.log(' cd zn-vault-agent && npm install && npm run build');
426
- console.log(' sudo ./deploy/install.sh');
427
- console.log();
428
- console.log('Or run directly:');
429
- console.log(' cd zn-vault-agent && npm run start -- start');
430
- process.exit(1);
431
- }
432
- // Determine how to run it
433
- const isJsFile = agentPath.endsWith('.js');
434
- const command = isJsFile ? 'node' : agentPath;
435
- const spawnArgs = isJsFile ? [agentPath, ...args] : args;
436
- // Spawn the agent
437
- const child = spawn(command, spawnArgs, {
438
- env,
439
- stdio: 'inherit',
440
- detached: !options.foreground,
441
- });
442
- if (!options.foreground) {
443
- child.unref();
444
- console.log(`Agent started with PID ${String(child.pid)}`);
445
- process.exit(0);
446
- }
447
- // In foreground mode, wait for the process
448
- child.on('exit', (code) => {
449
- process.exit(code ?? 0);
450
- });
451
- });
452
- // Show agent status
453
- agent
454
- .command('status')
455
- .description('Show agent configuration and sync status')
456
- .option('-c, --config <path>', 'Config file path')
457
- .option('-s, --state <path>', 'State file path', DEFAULT_STATE_FILE)
458
- .option('--json', 'Output as JSON')
459
- .action((options) => {
460
- const configPath = options.config ?? getConfigPath();
461
- const agentConfig = loadConfig(configPath);
462
- if (!agentConfig) {
463
- output.error(`Config not found. Run 'znvault agent init' first.`);
464
- process.exit(1);
465
- }
466
- let state = { certificates: {}, lastUpdate: 'never' };
467
- if (fs.existsSync(options.state)) {
468
- state = JSON.parse(fs.readFileSync(options.state, 'utf-8'));
469
- }
470
- if (options.json) {
471
- output.json({ config: agentConfig, state });
472
- return;
473
- }
474
- console.log('Agent Configuration:');
475
- console.log(` Config file: ${configPath}`);
476
- console.log(` Vault URL: ${agentConfig.vaultUrl}`);
477
- console.log(` Tenant: ${agentConfig.tenantId}`);
478
- console.log(` Certificates: ${agentConfig.targets.length}`);
479
- console.log(` Last sync: ${state.lastUpdate}`);
480
- console.log();
481
- if (agentConfig.targets.length === 0) {
482
- console.log('No certificates configured.');
483
- return;
484
- }
485
- output.table(['Name', 'Cert ID', 'Last Sync', 'Version', 'Fingerprint'], agentConfig.targets.map(t => {
486
- const s = state.certificates[t.certId];
487
- return [
488
- t.name,
489
- t.certId.substring(0, 8) + '...',
490
- new Date(s.lastSync).toLocaleString(),
491
- String(s.version),
492
- s.fingerprint.substring(0, 16) + '...',
493
- ];
494
- }));
495
- });
26
+ .description('Manage remote agents and registration tokens');
496
27
  // ===== Remote Agent Management Commands =====
497
28
  const remote = agent
498
29
  .command('remote')
@@ -651,10 +182,162 @@ export function registerAgentCommands(program) {
651
182
  await mode.closeLocalClient();
652
183
  }
653
184
  });
654
- }
655
- function ensureDir(dir) {
656
- if (!fs.existsSync(dir)) {
657
- fs.mkdirSync(dir, { recursive: true });
658
- }
185
+ // ===== Registration Token Commands =====
186
+ const token = agent
187
+ .command('token')
188
+ .description('Manage registration tokens for agent bootstrapping');
189
+ // Create registration token
190
+ token
191
+ .command('create')
192
+ .description('Create a one-time registration token for managed key binding')
193
+ .requiredOption('-k, --managed-key <name>', 'Name of the managed key to bind')
194
+ .option('-e, --expires <duration>', 'Token expiration (e.g., "1h", "24h")', '1h')
195
+ .option('-d, --description <text>', 'Optional description for audit trail')
196
+ .option('--tenant <tenantId>', 'Target tenant ID (superadmin only)')
197
+ .action(async (options) => {
198
+ const spinner = ora('Creating registration token...').start();
199
+ try {
200
+ const tenantQuery = options.tenant ? `?tenantId=${encodeURIComponent(options.tenant)}` : '';
201
+ const response = await mode.apiPost(`/auth/api-keys/managed/${encodeURIComponent(options.managedKey)}/registration-tokens${tenantQuery}`, {
202
+ expiresIn: options.expires,
203
+ description: options.description,
204
+ });
205
+ spinner.succeed('Registration token created');
206
+ console.log();
207
+ console.log('Token (save this - shown only once!):');
208
+ console.log(` ${response.token}`);
209
+ console.log();
210
+ console.log('Details:');
211
+ console.log(` Prefix: ${response.prefix}`);
212
+ console.log(` Managed Key: ${response.managedKeyName}`);
213
+ console.log(` Tenant: ${response.tenantId}`);
214
+ console.log(` Expires: ${new Date(response.expiresAt).toLocaleString()}`);
215
+ if (response.description) {
216
+ console.log(` Description: ${response.description}`);
217
+ }
218
+ console.log();
219
+ console.log('Usage:');
220
+ console.log(` curl -sSL https://vault.example.com/agent/bootstrap.sh | ZNVAULT_TOKEN=${response.token} bash`);
221
+ console.log();
222
+ console.log('Or manually:');
223
+ console.log(` curl -X POST https://vault.example.com/agent/bootstrap \\`);
224
+ console.log(` -H "Content-Type: application/json" \\`);
225
+ console.log(` -d '{"token": "${response.token}"}'`);
226
+ }
227
+ catch (err) {
228
+ spinner.fail('Failed to create registration token');
229
+ output.error(err instanceof Error ? err.message : String(err));
230
+ process.exit(1);
231
+ }
232
+ finally {
233
+ await mode.closeLocalClient();
234
+ }
235
+ });
236
+ // List registration tokens
237
+ token
238
+ .command('list')
239
+ .description('List registration tokens for a managed key')
240
+ .requiredOption('-k, --managed-key <name>', 'Name of the managed key')
241
+ .option('--include-used', 'Include already-used tokens')
242
+ .option('--tenant <tenantId>', 'Target tenant ID (superadmin only)')
243
+ .option('--json', 'Output as JSON')
244
+ .action(async (options) => {
245
+ const spinner = ora('Fetching registration tokens...').start();
246
+ try {
247
+ const params = new URLSearchParams();
248
+ if (options.tenant)
249
+ params.set('tenantId', options.tenant);
250
+ if (options.includeUsed)
251
+ params.set('includeUsed', 'true');
252
+ const query = params.toString();
253
+ const response = await mode.apiGet(`/auth/api-keys/managed/${encodeURIComponent(options.managedKey)}/registration-tokens${query ? `?${query}` : ''}`);
254
+ spinner.stop();
255
+ if (options.json) {
256
+ output.json(response);
257
+ return;
258
+ }
259
+ if (response.tokens.length === 0) {
260
+ console.log('No registration tokens found');
261
+ return;
262
+ }
263
+ console.log(`Registration tokens for ${options.managedKey}:`);
264
+ console.log();
265
+ output.table(['Prefix', 'Status', 'Created', 'Expires', 'Description'], response.tokens.map(t => [
266
+ t.prefix,
267
+ t.status === 'active' ? '● active' :
268
+ t.status === 'used' ? '○ used' :
269
+ t.status === 'expired' ? '○ expired' : '○ revoked',
270
+ formatRelativeTime(t.createdAt),
271
+ formatRelativeTime(t.expiresAt),
272
+ t.description?.substring(0, 30) ?? '-',
273
+ ]));
274
+ }
275
+ catch (err) {
276
+ spinner.fail('Failed to fetch registration tokens');
277
+ output.error(err instanceof Error ? err.message : String(err));
278
+ process.exit(1);
279
+ }
280
+ finally {
281
+ await mode.closeLocalClient();
282
+ }
283
+ });
284
+ // Revoke registration token
285
+ token
286
+ .command('revoke <token-id>')
287
+ .description('Revoke a registration token (prevents future use)')
288
+ .requiredOption('-k, --managed-key <name>', 'Name of the managed key')
289
+ .option('--tenant <tenantId>', 'Target tenant ID (superadmin only)')
290
+ .option('-y, --yes', 'Skip confirmation prompt')
291
+ .action(async (tokenId, options) => {
292
+ if (!options.yes) {
293
+ const readline = await import('readline');
294
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
295
+ const answer = await new Promise(resolve => {
296
+ rl.question(`Revoke token ${tokenId}? This cannot be undone. [y/N] `, resolve);
297
+ });
298
+ rl.close();
299
+ if (answer.toLowerCase() !== 'y') {
300
+ console.log('Cancelled');
301
+ return;
302
+ }
303
+ }
304
+ const spinner = ora('Revoking registration token...').start();
305
+ try {
306
+ const tenantQuery = options.tenant ? `?tenantId=${encodeURIComponent(options.tenant)}` : '';
307
+ await mode.apiDelete(`/auth/api-keys/managed/${encodeURIComponent(options.managedKey)}/registration-tokens/${encodeURIComponent(tokenId)}${tenantQuery}`);
308
+ spinner.succeed('Registration token revoked');
309
+ }
310
+ catch (err) {
311
+ spinner.fail('Failed to revoke registration token');
312
+ output.error(err instanceof Error ? err.message : String(err));
313
+ process.exit(1);
314
+ }
315
+ finally {
316
+ await mode.closeLocalClient();
317
+ }
318
+ });
319
+ // Help text pointing to zn-vault-agent
320
+ agent
321
+ .command('help-local')
322
+ .description('Show help for local agent operations')
323
+ .action(() => {
324
+ console.log('Local Agent Operations');
325
+ console.log('======================');
326
+ console.log();
327
+ console.log('For local agent configuration and sync operations, use the standalone agent:');
328
+ console.log();
329
+ console.log(' zn-vault-agent login # Authenticate with vault');
330
+ console.log(' zn-vault-agent setup # Interactive setup');
331
+ console.log(' zn-vault-agent sync # Sync secrets/certificates');
332
+ console.log(' zn-vault-agent start # Start agent daemon');
333
+ console.log(' zn-vault-agent status # Show agent status');
334
+ console.log(' zn-vault-agent exec # Execute with secrets injected');
335
+ console.log();
336
+ console.log('Install the standalone agent:');
337
+ console.log(' npm install -g @zincapp/zn-vault-agent');
338
+ console.log();
339
+ console.log('For more information:');
340
+ console.log(' zn-vault-agent --help');
341
+ });
659
342
  }
660
343
  //# sourceMappingURL=agent.js.map