@lifestreamdynamics/vault-cli 1.2.0 → 1.3.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/README.md +140 -30
- package/dist/client.d.ts +4 -0
- package/dist/client.js +12 -11
- package/dist/commands/admin.js +5 -5
- package/dist/commands/ai.js +17 -4
- package/dist/commands/auth.js +10 -105
- package/dist/commands/booking.d.ts +2 -0
- package/dist/commands/booking.js +739 -0
- package/dist/commands/calendar.js +725 -6
- package/dist/commands/completion.d.ts +5 -0
- package/dist/commands/completion.js +60 -0
- package/dist/commands/config.js +17 -16
- package/dist/commands/connectors.js +12 -1
- package/dist/commands/custom-domains.js +6 -1
- package/dist/commands/docs.js +12 -5
- package/dist/commands/hooks.js +6 -1
- package/dist/commands/links.js +9 -2
- package/dist/commands/mfa.js +1 -70
- package/dist/commands/plugins.d.ts +2 -0
- package/dist/commands/plugins.js +172 -0
- package/dist/commands/publish.js +13 -3
- package/dist/commands/saml.d.ts +2 -0
- package/dist/commands/saml.js +220 -0
- package/dist/commands/scim.d.ts +2 -0
- package/dist/commands/scim.js +238 -0
- package/dist/commands/shares.js +25 -3
- package/dist/commands/subscription.js +9 -2
- package/dist/commands/sync.js +3 -0
- package/dist/commands/teams.js +141 -8
- package/dist/commands/user.js +122 -9
- package/dist/commands/vaults.js +17 -8
- package/dist/commands/webhooks.js +6 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +7 -3
- package/dist/index.js +20 -1
- package/dist/lib/credential-manager.js +32 -7
- package/dist/lib/migration.js +2 -2
- package/dist/lib/profiles.js +4 -4
- package/dist/sync/config.js +2 -2
- package/dist/sync/daemon-worker.js +13 -6
- package/dist/sync/daemon.js +2 -1
- package/dist/sync/remote-poller.js +7 -3
- package/dist/sync/state.js +2 -2
- package/dist/utils/confirm.d.ts +11 -0
- package/dist/utils/confirm.js +23 -0
- package/dist/utils/format.js +1 -1
- package/dist/utils/output.js +4 -1
- package/dist/utils/prompt.d.ts +29 -0
- package/dist/utils/prompt.js +146 -0
- 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
|
+
}
|
package/dist/commands/config.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
45
|
+
process.stdout.write(value + '\n');
|
|
45
46
|
}
|
|
46
47
|
else {
|
|
47
|
-
|
|
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
|
-
|
|
64
|
+
process.stdout.write(chalk.yellow(`Profile "${profile}" has no configuration values.`) + '\n');
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
|
-
|
|
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
|
|
70
|
-
const display =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
+
process.stdout.write(chalk.green(`Profile "${name}" deleted.`) + '\n');
|
|
118
119
|
}
|
|
119
120
|
else {
|
|
120
|
-
|
|
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
|
-
|
|
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();
|
package/dist/commands/docs.js
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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 =
|
|
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 });
|
package/dist/commands/hooks.js
CHANGED
|
@@ -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();
|
package/dist/commands/links.js
CHANGED
|
@@ -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
|
-
|
|
91
|
-
|
|
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');
|
package/dist/commands/mfa.js
CHANGED
|
@@ -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,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
|
+
}
|
package/dist/commands/publish.js
CHANGED
|
@@ -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();
|