@lifestreamdynamics/vault-cli 1.2.0 → 1.3.1

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 (50) hide show
  1. package/README.md +140 -30
  2. package/dist/client.d.ts +4 -0
  3. package/dist/client.js +12 -11
  4. package/dist/commands/admin.js +5 -5
  5. package/dist/commands/ai.js +17 -4
  6. package/dist/commands/auth.js +10 -105
  7. package/dist/commands/booking.d.ts +2 -0
  8. package/dist/commands/booking.js +739 -0
  9. package/dist/commands/calendar.js +725 -6
  10. package/dist/commands/completion.d.ts +5 -0
  11. package/dist/commands/completion.js +60 -0
  12. package/dist/commands/config.js +17 -16
  13. package/dist/commands/connectors.js +12 -1
  14. package/dist/commands/custom-domains.js +6 -1
  15. package/dist/commands/docs.js +12 -5
  16. package/dist/commands/hooks.js +6 -1
  17. package/dist/commands/links.js +9 -2
  18. package/dist/commands/mfa.js +1 -70
  19. package/dist/commands/plugins.d.ts +2 -0
  20. package/dist/commands/plugins.js +172 -0
  21. package/dist/commands/publish.js +13 -3
  22. package/dist/commands/saml.d.ts +2 -0
  23. package/dist/commands/saml.js +220 -0
  24. package/dist/commands/scim.d.ts +2 -0
  25. package/dist/commands/scim.js +238 -0
  26. package/dist/commands/shares.js +25 -3
  27. package/dist/commands/subscription.js +9 -2
  28. package/dist/commands/sync.js +3 -0
  29. package/dist/commands/teams.js +141 -8
  30. package/dist/commands/user.js +122 -9
  31. package/dist/commands/vaults.js +17 -8
  32. package/dist/commands/webhooks.js +6 -1
  33. package/dist/config.d.ts +2 -0
  34. package/dist/config.js +7 -3
  35. package/dist/index.js +20 -1
  36. package/dist/lib/credential-manager.js +32 -7
  37. package/dist/lib/migration.js +2 -2
  38. package/dist/lib/profiles.js +4 -4
  39. package/dist/sync/config.js +2 -2
  40. package/dist/sync/daemon-worker.js +13 -6
  41. package/dist/sync/daemon.js +2 -1
  42. package/dist/sync/remote-poller.js +7 -3
  43. package/dist/sync/state.js +2 -2
  44. package/dist/utils/confirm.d.ts +11 -0
  45. package/dist/utils/confirm.js +23 -0
  46. package/dist/utils/format.js +1 -1
  47. package/dist/utils/output.js +4 -1
  48. package/dist/utils/prompt.d.ts +29 -0
  49. package/dist/utils/prompt.js +146 -0
  50. package/package.json +2 -2
@@ -0,0 +1,5 @@
1
+ import type { Command } from 'commander';
2
+ export declare function generateBashCompletion(program: Command): string;
3
+ export declare function generateZshCompletion(program: Command): string;
4
+ export declare function generateFishCompletion(program: Command): string;
5
+ export declare function registerCompletionCommands(program: Command): void;
@@ -0,0 +1,60 @@
1
+ const SAFE_COMMAND_NAME = /^[a-z][a-z0-9-]*$/;
2
+ function collectCommandNames(cmd, prefix = '') {
3
+ const names = [];
4
+ for (const sub of cmd.commands) {
5
+ const name = sub.name();
6
+ // Skip any name that doesn't match the safe pattern to prevent shell injection
7
+ if (!SAFE_COMMAND_NAME.test(name))
8
+ continue;
9
+ const full = prefix ? `${prefix} ${name}` : name;
10
+ names.push(name);
11
+ names.push(...collectCommandNames(sub, full));
12
+ }
13
+ return names;
14
+ }
15
+ export function generateBashCompletion(program) {
16
+ const commands = collectCommandNames(program);
17
+ return `# Bash completion for lsvault
18
+ # Add to ~/.bashrc: eval "$(lsvault completion bash)"
19
+ _lsvault_completions() {
20
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
21
+ local commands="${commands.join(' ')}"
22
+ COMPREPLY=($(compgen -W "\${commands}" -- "\${cur}"))
23
+ }
24
+ complete -F _lsvault_completions lsvault
25
+ `;
26
+ }
27
+ export function generateZshCompletion(program) {
28
+ const commands = collectCommandNames(program);
29
+ return `# Zsh completion for lsvault
30
+ # Add to ~/.zshrc: eval "$(lsvault completion zsh)"
31
+ _lsvault() {
32
+ local -a commands
33
+ commands=(${commands.map(c => `'${c}'`).join(' ')})
34
+ _describe 'lsvault commands' commands
35
+ }
36
+ compdef _lsvault lsvault
37
+ `;
38
+ }
39
+ export function generateFishCompletion(program) {
40
+ const commands = collectCommandNames(program);
41
+ return commands.map(c => `complete -c lsvault -n '__fish_use_subcommand' -a '${c}' -d '${c} command'`).join('\n') + '\n';
42
+ }
43
+ export function registerCompletionCommands(program) {
44
+ const completion = program.command('completion').description('Generate shell completion scripts');
45
+ completion.command('bash')
46
+ .description('Generate bash completion script')
47
+ .action(() => {
48
+ process.stdout.write(generateBashCompletion(program));
49
+ });
50
+ completion.command('zsh')
51
+ .description('Generate zsh completion script')
52
+ .action(() => {
53
+ process.stdout.write(generateZshCompletion(program));
54
+ });
55
+ completion.command('fish')
56
+ .description('Generate fish completion script')
57
+ .action(() => {
58
+ process.stdout.write(generateFishCompletion(program));
59
+ });
60
+ }
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { resolveProfileName, getActiveProfile, setActiveProfile, loadProfile, setProfileValue, getProfileValue, listProfiles, deleteProfile, } from '../lib/profiles.js';
3
+ const SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
3
4
  export function registerConfigCommands(program) {
4
5
  const config = program
5
6
  .command('config')
@@ -26,7 +27,7 @@ EXAMPLES
26
27
  .action((key, value, opts) => {
27
28
  const profile = resolveProfileName(opts.profile);
28
29
  setProfileValue(profile, key, value);
29
- console.log(chalk.green(`Set ${chalk.bold(key)} in profile ${chalk.bold(profile)}`));
30
+ process.stdout.write(chalk.green(`Set ${chalk.bold(key)} in profile ${chalk.bold(profile)}`) + '\n');
30
31
  });
31
32
  config
32
33
  .command('get')
@@ -41,10 +42,10 @@ EXAMPLES
41
42
  const profile = resolveProfileName(opts.profile);
42
43
  const value = getProfileValue(profile, key);
43
44
  if (value !== undefined) {
44
- console.log(value);
45
+ process.stdout.write(value + '\n');
45
46
  }
46
47
  else {
47
- console.log(chalk.yellow(`Key "${key}" not set in profile "${profile}"`));
48
+ process.stdout.write(chalk.yellow(`Key "${key}" not set in profile "${profile}"`) + '\n');
48
49
  }
49
50
  });
50
51
  config
@@ -60,17 +61,17 @@ EXAMPLES
60
61
  const profileConfig = loadProfile(profile);
61
62
  const keys = Object.keys(profileConfig);
62
63
  if (keys.length === 0) {
63
- console.log(chalk.yellow(`Profile "${profile}" has no configuration values.`));
64
+ process.stdout.write(chalk.yellow(`Profile "${profile}" has no configuration values.`) + '\n');
64
65
  return;
65
66
  }
66
- console.log(chalk.bold(`Profile: ${profile}\n`));
67
+ process.stdout.write(chalk.bold(`Profile: ${profile}\n`) + '\n');
67
68
  for (const key of keys) {
68
69
  const value = profileConfig[key];
69
- // Mask API keys for display
70
- const display = key.toLowerCase().includes('key') && value
70
+ // Mask sensitive values for display
71
+ const display = SENSITIVE_KEY_PATTERN.test(key) && value
71
72
  ? value.slice(0, 12) + '...'
72
73
  : value;
73
- console.log(` ${chalk.cyan(key)}: ${display}`);
74
+ process.stdout.write(` ${chalk.cyan(key)}: ${display}\n`);
74
75
  }
75
76
  });
76
77
  config
@@ -83,7 +84,7 @@ EXAMPLES
83
84
  lsvault config use dev`)
84
85
  .action((name) => {
85
86
  setActiveProfile(name);
86
- console.log(chalk.green(`Active profile set to ${chalk.bold(name)}`));
87
+ process.stdout.write(chalk.green(`Active profile set to ${chalk.bold(name)}`) + '\n');
87
88
  });
88
89
  config
89
90
  .command('profiles')
@@ -95,14 +96,14 @@ EXAMPLES
95
96
  const profiles = listProfiles();
96
97
  const active = getActiveProfile();
97
98
  if (profiles.length === 0) {
98
- console.log(chalk.yellow('No profiles configured.'));
99
- console.log(chalk.dim('Create one with: lsvault config set <key> <value> --profile <name>'));
99
+ process.stdout.write(chalk.yellow('No profiles configured.') + '\n');
100
+ process.stdout.write(chalk.dim('Create one with: lsvault config set <key> <value> --profile <name>') + '\n');
100
101
  return;
101
102
  }
102
- console.log(chalk.bold('Available profiles:\n'));
103
+ process.stdout.write(chalk.bold('Available profiles:\n') + '\n');
103
104
  for (const name of profiles) {
104
105
  const marker = name === active ? chalk.green(' (active)') : '';
105
- console.log(` ${chalk.cyan(name)}${marker}`);
106
+ process.stdout.write(` ${chalk.cyan(name)}${marker}\n`);
106
107
  }
107
108
  });
108
109
  config
@@ -114,10 +115,10 @@ EXAMPLES
114
115
  lsvault config delete staging`)
115
116
  .action((name) => {
116
117
  if (deleteProfile(name)) {
117
- console.log(chalk.green(`Profile "${name}" deleted.`));
118
+ process.stdout.write(chalk.green(`Profile "${name}" deleted.`) + '\n');
118
119
  }
119
120
  else {
120
- console.log(chalk.yellow(`Profile "${name}" not found.`));
121
+ process.stdout.write(chalk.yellow(`Profile "${name}" not found.`) + '\n');
121
122
  }
122
123
  });
123
124
  config
@@ -125,6 +126,6 @@ EXAMPLES
125
126
  .description('Show the active profile name')
126
127
  .action(() => {
127
128
  const active = getActiveProfile();
128
- console.log(active);
129
+ process.stdout.write(active + '\n');
129
130
  });
130
131
  }
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import { getClientAsync } from '../client.js';
3
3
  import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
4
  import { createOutput, handleError } from '../utils/output.js';
5
+ const VALID_PROVIDERS = ['google_drive', 'dropbox', 'onedrive'];
5
6
  export function registerConnectorCommands(program) {
6
7
  const connectors = program.command('connectors').description('Manage external service connectors (e.g., Google Drive)');
7
8
  addGlobalFlags(connectors.command('list')
@@ -79,6 +80,11 @@ export function registerConnectorCommands(program) {
79
80
  .action(async (provider, name, _opts) => {
80
81
  const flags = resolveFlags(_opts);
81
82
  const out = createOutput(flags);
83
+ if (!VALID_PROVIDERS.includes(provider)) {
84
+ out.error(`Invalid provider "${provider}". Must be one of: ${VALID_PROVIDERS.join(', ')}`);
85
+ process.exitCode = 1;
86
+ return;
87
+ }
82
88
  out.startSpinner('Creating connector...');
83
89
  try {
84
90
  const client = await getClientAsync();
@@ -127,10 +133,15 @@ export function registerConnectorCommands(program) {
127
133
  });
128
134
  addGlobalFlags(connectors.command('delete')
129
135
  .description('Delete a connector')
130
- .argument('<connectorId>', 'Connector ID'))
136
+ .argument('<connectorId>', 'Connector ID')
137
+ .option('-y, --yes', 'Skip confirmation prompt'))
131
138
  .action(async (connectorId, _opts) => {
132
139
  const flags = resolveFlags(_opts);
133
140
  const out = createOutput(flags);
141
+ if (!_opts.yes) {
142
+ out.status(chalk.yellow(`Pass --yes to delete connector ${connectorId}.`));
143
+ return;
144
+ }
134
145
  out.startSpinner('Deleting connector...');
135
146
  try {
136
147
  const client = await getClientAsync();
@@ -86,10 +86,15 @@ export function registerCustomDomainCommands(program) {
86
86
  });
87
87
  addGlobalFlags(domains.command('remove')
88
88
  .description('Remove a custom domain')
89
- .argument('<domainId>', 'Domain ID'))
89
+ .argument('<domainId>', 'Domain ID')
90
+ .option('-y, --yes', 'Skip confirmation prompt'))
90
91
  .action(async (domainId, _opts) => {
91
92
  const flags = resolveFlags(_opts);
92
93
  const out = createOutput(flags);
94
+ if (!_opts.yes) {
95
+ out.status(chalk.yellow(`Pass --yes to remove custom domain ${domainId}.`));
96
+ return;
97
+ }
93
98
  out.startSpinner('Removing custom domain...');
94
99
  try {
95
100
  const client = await getClientAsync();
@@ -2,7 +2,8 @@ import chalk from 'chalk';
2
2
  import { getClientAsync } from '../client.js';
3
3
  import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
4
  import { createOutput, handleError } from '../utils/output.js';
5
- import { createCredentialManager } from '../lib/credential-manager.js';
5
+ import { getCredentialManager } from '../config.js';
6
+ import { confirmAction } from '../utils/confirm.js';
6
7
  export function registerDocCommands(program) {
7
8
  const docs = program.command('docs').description('Read, write, move, and delete documents in a vault');
8
9
  addGlobalFlags(docs.command('list')
@@ -66,7 +67,7 @@ EXAMPLES
66
67
  const result = await client.documents.get(vaultId, docPath);
67
68
  // Auto-decrypt if the document is encrypted
68
69
  if (result.document.encrypted && !_opts.meta) {
69
- const credManager = createCredentialManager();
70
+ const credManager = getCredentialManager();
70
71
  const vaultKey = await credManager.getVaultKey(vaultId);
71
72
  if (!vaultKey) {
72
73
  out.error('Document is encrypted but no vault key found.');
@@ -124,7 +125,7 @@ EXAMPLES
124
125
  const vault = await client.vaults.get(vaultId);
125
126
  let doc;
126
127
  if (vault.encryptionEnabled) {
127
- const credManager = createCredentialManager();
128
+ const credManager = getCredentialManager();
128
129
  const vaultKey = await credManager.getVaultKey(vaultId);
129
130
  if (!vaultKey) {
130
131
  out.failSpinner('Failed to save document');
@@ -151,12 +152,18 @@ EXAMPLES
151
152
  addGlobalFlags(docs.command('delete')
152
153
  .description('Permanently delete a document from a vault')
153
154
  .argument('<vaultId>', 'Vault ID')
154
- .argument('<path>', 'Document path to delete'))
155
+ .argument('<path>', 'Document path to delete')
156
+ .option('-y, --yes', 'Skip confirmation prompt'))
155
157
  .action(async (vaultId, docPath, _opts) => {
156
158
  const flags = resolveFlags(_opts);
157
159
  const out = createOutput(flags);
158
- out.startSpinner('Deleting document...');
159
160
  try {
161
+ const confirmed = await confirmAction(`Permanently delete "${docPath}" from vault ${vaultId}?`, { yes: _opts.yes });
162
+ if (!confirmed) {
163
+ out.status('Deletion cancelled.');
164
+ return;
165
+ }
166
+ out.startSpinner('Deleting document...');
160
167
  const client = await getClientAsync();
161
168
  await client.documents.delete(vaultId, docPath);
162
169
  out.success(`Deleted: ${chalk.cyan(docPath)}`, { path: docPath, deleted: true });
@@ -100,10 +100,15 @@ export function registerHookCommands(program) {
100
100
  addGlobalFlags(hooks.command('delete')
101
101
  .description('Delete a hook')
102
102
  .argument('<vaultId>', 'Vault ID')
103
- .argument('<hookId>', 'Hook ID'))
103
+ .argument('<hookId>', 'Hook ID')
104
+ .option('-y, --yes', 'Skip confirmation prompt'))
104
105
  .action(async (vaultId, hookId, _opts) => {
105
106
  const flags = resolveFlags(_opts);
106
107
  const out = createOutput(flags);
108
+ if (!_opts.yes) {
109
+ out.status(chalk.yellow(`Pass --yes to delete hook ${hookId}.`));
110
+ return;
111
+ }
107
112
  out.startSpinner('Deleting hook...');
108
113
  try {
109
114
  const client = await getClientAsync();
@@ -87,8 +87,15 @@ export function registerLinkCommands(program) {
87
87
  const client = await getClientAsync();
88
88
  const graph = await client.vaults.getGraph(vaultId);
89
89
  out.stopSpinner();
90
- // For graph, output as JSON structure
91
- process.stdout.write(JSON.stringify({ nodes: graph.nodes, edges: graph.edges }) + '\n');
90
+ if (flags.output === 'json') {
91
+ process.stdout.write(JSON.stringify({ nodes: graph.nodes, edges: graph.edges }) + '\n');
92
+ }
93
+ else {
94
+ process.stdout.write(chalk.bold(`Nodes: ${graph.nodes.length} Edges: ${graph.edges.length}\n`));
95
+ for (const node of graph.nodes) {
96
+ process.stdout.write(` ${chalk.cyan(String(node.path ?? node.id))}\n`);
97
+ }
98
+ }
92
99
  }
93
100
  catch (err) {
94
101
  handleError(out, err, 'Failed to fetch link graph');
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { getClientAsync } from '../client.js';
4
+ import { promptPassword, promptMfaCode } from '../utils/prompt.js';
4
5
  export function registerMfaCommands(program) {
5
6
  const mfa = program.command('mfa').description('Multi-factor authentication management');
6
7
  mfa.command('status')
@@ -152,73 +153,3 @@ export function registerMfaCommands(program) {
152
153
  }
153
154
  });
154
155
  }
155
- /**
156
- * Prompt for a password from stdin (non-echoing).
157
- * Returns the password or null if stdin is not a TTY.
158
- */
159
- async function promptPassword() {
160
- if (!process.stdin.isTTY) {
161
- return null;
162
- }
163
- const readline = await import('node:readline');
164
- return new Promise((resolve) => {
165
- const rl = readline.createInterface({
166
- input: process.stdin,
167
- output: process.stderr,
168
- terminal: true,
169
- });
170
- process.stderr.write('Password: ');
171
- process.stdin.setRawMode?.(true);
172
- let password = '';
173
- const onData = (chunk) => {
174
- const char = chunk.toString('utf-8');
175
- if (char === '\n' || char === '\r' || char === '\u0004') {
176
- process.stderr.write('\n');
177
- process.stdin.setRawMode?.(false);
178
- process.stdin.removeListener('data', onData);
179
- rl.close();
180
- resolve(password);
181
- }
182
- else if (char === '\u0003') {
183
- // Ctrl+C
184
- process.stderr.write('\n');
185
- process.stdin.setRawMode?.(false);
186
- process.stdin.removeListener('data', onData);
187
- rl.close();
188
- resolve(null);
189
- }
190
- else if (char === '\u007F' || char === '\b') {
191
- // Backspace
192
- if (password.length > 0) {
193
- password = password.slice(0, -1);
194
- }
195
- }
196
- else {
197
- password += char;
198
- }
199
- };
200
- process.stdin.on('data', onData);
201
- process.stdin.resume();
202
- });
203
- }
204
- /**
205
- * Prompt for an MFA code from stdin (6 digits, echoed for visibility).
206
- * Returns the code or null if stdin is not a TTY.
207
- */
208
- async function promptMfaCode() {
209
- if (!process.stdin.isTTY) {
210
- return null;
211
- }
212
- const readline = await import('node:readline');
213
- return new Promise((resolve) => {
214
- const rl = readline.createInterface({
215
- input: process.stdin,
216
- output: process.stderr,
217
- terminal: true,
218
- });
219
- rl.question('Enter 6-digit code from authenticator app: ', (answer) => {
220
- rl.close();
221
- resolve(answer.trim() || null);
222
- });
223
- });
224
- }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerPluginCommands(program: Command): void;
@@ -0,0 +1,172 @@
1
+ import chalk from 'chalk';
2
+ import { getClientAsync } from '../client.js';
3
+ import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
+ import { createOutput, handleError } from '../utils/output.js';
5
+ export function registerPluginCommands(program) {
6
+ const plugins = program.command('plugins').description('Plugin/extension marketplace management');
7
+ // ── list ──────────────────────────────────────────────────────────────────
8
+ addGlobalFlags(plugins.command('list')
9
+ .description('List all installed plugins'))
10
+ .action(async (_opts) => {
11
+ const flags = resolveFlags(_opts);
12
+ const out = createOutput(flags);
13
+ out.startSpinner('Loading plugins...');
14
+ try {
15
+ const client = await getClientAsync();
16
+ const list = await client.plugins.list();
17
+ out.stopSpinner();
18
+ if (list.length === 0 && flags.output !== 'json') {
19
+ out.raw('No plugins installed.\n');
20
+ }
21
+ else {
22
+ out.list(list.map((p) => ({
23
+ pluginId: p.pluginId,
24
+ version: p.version,
25
+ enabled: p.enabled ? 'yes' : 'no',
26
+ settings: Object.keys(p.settings).length > 0 ? JSON.stringify(p.settings) : '',
27
+ installedAt: p.installedAt.slice(0, 10),
28
+ })), {
29
+ columns: [
30
+ { key: 'pluginId', header: 'Plugin ID' },
31
+ { key: 'version', header: 'Version' },
32
+ { key: 'enabled', header: 'Enabled' },
33
+ { key: 'installedAt', header: 'Installed' },
34
+ ],
35
+ textFn: (p) => `${chalk.cyan(String(p.pluginId))}@${p.version} ${p.enabled === 'yes' ? chalk.green('enabled') : chalk.dim('disabled')} ${chalk.dim(String(p.installedAt))}`,
36
+ });
37
+ }
38
+ }
39
+ catch (err) {
40
+ handleError(out, err, 'Failed to list plugins');
41
+ }
42
+ });
43
+ // ── install ───────────────────────────────────────────────────────────────
44
+ addGlobalFlags(plugins.command('install')
45
+ .description('Install a plugin from the marketplace')
46
+ .requiredOption('--plugin-id <pluginId>', 'Plugin marketplace identifier (e.g. org/plugin-name)')
47
+ .requiredOption('--version <version>', 'Version to install'))
48
+ .action(async (_opts) => {
49
+ const flags = resolveFlags(_opts);
50
+ const out = createOutput(flags);
51
+ out.startSpinner('Installing plugin...');
52
+ try {
53
+ const client = await getClientAsync();
54
+ const installed = await client.plugins.install({
55
+ pluginId: _opts.pluginId,
56
+ version: _opts.version,
57
+ });
58
+ out.stopSpinner();
59
+ if (flags.output === 'json') {
60
+ out.raw(JSON.stringify(installed, null, 2) + '\n');
61
+ }
62
+ else {
63
+ out.raw(chalk.green(`Plugin installed: ${installed.pluginId}@${installed.version}`) + '\n');
64
+ }
65
+ }
66
+ catch (err) {
67
+ handleError(out, err, 'Failed to install plugin');
68
+ }
69
+ });
70
+ // ── uninstall ─────────────────────────────────────────────────────────────
71
+ addGlobalFlags(plugins.command('uninstall')
72
+ .description('Uninstall a plugin')
73
+ .argument('<pluginId>', 'Plugin marketplace identifier')
74
+ .option('--confirm', 'Skip confirmation prompt'))
75
+ .action(async (pluginId, _opts) => {
76
+ const flags = resolveFlags(_opts);
77
+ const out = createOutput(flags);
78
+ if (!_opts.confirm) {
79
+ out.raw(chalk.yellow(`Pass --confirm to uninstall plugin ${pluginId}.`) + '\n');
80
+ return;
81
+ }
82
+ out.startSpinner('Uninstalling plugin...');
83
+ try {
84
+ const client = await getClientAsync();
85
+ await client.plugins.uninstall(pluginId);
86
+ out.stopSpinner();
87
+ out.raw(chalk.green(`Plugin ${pluginId} uninstalled.`) + '\n');
88
+ }
89
+ catch (err) {
90
+ handleError(out, err, 'Failed to uninstall plugin');
91
+ }
92
+ });
93
+ // ── enable ────────────────────────────────────────────────────────────────
94
+ addGlobalFlags(plugins.command('enable')
95
+ .description('Enable a plugin')
96
+ .argument('<pluginId>', 'Plugin marketplace identifier'))
97
+ .action(async (pluginId, _opts) => {
98
+ const flags = resolveFlags(_opts);
99
+ const out = createOutput(flags);
100
+ out.startSpinner('Enabling plugin...');
101
+ try {
102
+ const client = await getClientAsync();
103
+ const updated = await client.plugins.enable(pluginId);
104
+ out.stopSpinner();
105
+ if (flags.output === 'json') {
106
+ out.raw(JSON.stringify(updated, null, 2) + '\n');
107
+ }
108
+ else {
109
+ out.raw(chalk.green(`Plugin ${updated.pluginId} enabled.`) + '\n');
110
+ }
111
+ }
112
+ catch (err) {
113
+ handleError(out, err, 'Failed to enable plugin');
114
+ }
115
+ });
116
+ // ── disable ───────────────────────────────────────────────────────────────
117
+ addGlobalFlags(plugins.command('disable')
118
+ .description('Disable a plugin')
119
+ .argument('<pluginId>', 'Plugin marketplace identifier'))
120
+ .action(async (pluginId, _opts) => {
121
+ const flags = resolveFlags(_opts);
122
+ const out = createOutput(flags);
123
+ out.startSpinner('Disabling plugin...');
124
+ try {
125
+ const client = await getClientAsync();
126
+ const updated = await client.plugins.disable(pluginId);
127
+ out.stopSpinner();
128
+ if (flags.output === 'json') {
129
+ out.raw(JSON.stringify(updated, null, 2) + '\n');
130
+ }
131
+ else {
132
+ out.raw(chalk.dim(`Plugin ${updated.pluginId} disabled.`) + '\n');
133
+ }
134
+ }
135
+ catch (err) {
136
+ handleError(out, err, 'Failed to disable plugin');
137
+ }
138
+ });
139
+ // ── update-settings ───────────────────────────────────────────────────────
140
+ addGlobalFlags(plugins.command('update-settings')
141
+ .description('Update plugin-specific settings')
142
+ .argument('<pluginId>', 'Plugin marketplace identifier')
143
+ .requiredOption('--settings <json>', 'Settings as a JSON string (e.g. \'{"theme":"dark"}\')'))
144
+ .action(async (pluginId, _opts) => {
145
+ const flags = resolveFlags(_opts);
146
+ const out = createOutput(flags);
147
+ let settings;
148
+ try {
149
+ settings = JSON.parse(_opts.settings);
150
+ }
151
+ catch {
152
+ out.error('Invalid JSON for --settings. Provide a valid JSON object.');
153
+ process.exitCode = 2;
154
+ return;
155
+ }
156
+ out.startSpinner('Updating plugin settings...');
157
+ try {
158
+ const client = await getClientAsync();
159
+ const updated = await client.plugins.updateSettings(pluginId, settings);
160
+ out.stopSpinner();
161
+ if (flags.output === 'json') {
162
+ out.raw(JSON.stringify(updated, null, 2) + '\n');
163
+ }
164
+ else {
165
+ out.raw(chalk.green(`Settings updated for ${updated.pluginId}.`) + '\n');
166
+ }
167
+ }
168
+ catch (err) {
169
+ handleError(out, err, 'Failed to update plugin settings');
170
+ }
171
+ });
172
+ }
@@ -1,7 +1,7 @@
1
+ import chalk from 'chalk';
1
2
  import { getClientAsync } from '../client.js';
2
3
  import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
3
4
  import { createOutput, handleError } from '../utils/output.js';
4
- import chalk from 'chalk';
5
5
  export function registerPublishCommands(program) {
6
6
  const publish = program.command('publish').description('Publish documents to public profile pages');
7
7
  addGlobalFlags(publish.command('list')
@@ -121,10 +121,15 @@ export function registerPublishCommands(program) {
121
121
  addGlobalFlags(publish.command('delete')
122
122
  .description('Unpublish a document')
123
123
  .argument('<vaultId>', 'Vault ID')
124
- .argument('<docPath>', 'Document path (e.g., blog/post.md)'))
124
+ .argument('<docPath>', 'Document path (e.g., blog/post.md)')
125
+ .option('-y, --yes', 'Skip confirmation prompt'))
125
126
  .action(async (vaultId, docPath, _opts) => {
126
127
  const flags = resolveFlags(_opts);
127
128
  const out = createOutput(flags);
129
+ if (!_opts.yes) {
130
+ out.status(chalk.yellow(`Pass --yes to unpublish document ${docPath}.`));
131
+ return;
132
+ }
128
133
  out.startSpinner('Unpublishing document...');
129
134
  try {
130
135
  const client = await getClientAsync();
@@ -172,10 +177,15 @@ export function registerPublishCommands(program) {
172
177
  });
173
178
  addGlobalFlags(subdomain.command('delete')
174
179
  .description('Remove the subdomain for a published vault')
175
- .argument('<vaultId>', 'Vault ID'))
180
+ .argument('<vaultId>', 'Vault ID')
181
+ .option('-y, --yes', 'Skip confirmation prompt'))
176
182
  .action(async (vaultId, _opts) => {
177
183
  const flags = resolveFlags(_opts);
178
184
  const out = createOutput(flags);
185
+ if (!_opts.yes) {
186
+ out.status(chalk.yellow(`Pass --yes to remove the subdomain for vault ${vaultId}.`));
187
+ return;
188
+ }
179
189
  out.startSpinner('Removing subdomain...');
180
190
  try {
181
191
  const client = await getClientAsync();
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerSamlCommands(program: Command): void;