@lifestreamdynamics/vault-cli 1.3.7 → 1.3.9
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/admin.js +3 -1
- package/dist/commands/ai.js +5 -1
- package/dist/commands/analytics.js +1 -1
- package/dist/commands/audit.js +1 -0
- package/dist/commands/auth.js +38 -30
- package/dist/commands/booking.js +3 -2
- package/dist/commands/calendar.js +4 -5
- package/dist/commands/completion.js +188 -16
- package/dist/commands/config.js +8 -0
- package/dist/commands/connectors.js +6 -0
- package/dist/commands/custom-domains.js +6 -4
- package/dist/commands/docs.js +5 -3
- package/dist/commands/links.js +11 -6
- package/dist/commands/mfa.js +143 -93
- package/dist/commands/plugins.js +2 -2
- package/dist/commands/publish-vault.js +11 -4
- package/dist/commands/publish.js +5 -1
- package/dist/commands/saml.js +13 -4
- package/dist/commands/search.js +5 -1
- package/dist/commands/shares.js +9 -2
- package/dist/commands/sync.js +13 -4
- package/dist/commands/teams.js +20 -5
- package/dist/commands/user.js +8 -1
- package/dist/commands/vaults.js +17 -4
- package/dist/utils/output.js +7 -7
- package/dist/utils/resolve-vault.js +1 -1
- package/package.json +1 -1
package/dist/commands/mfa.js
CHANGED
|
@@ -1,158 +1,208 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
2
|
import { getClientAsync } from '../client.js';
|
|
3
|
+
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
|
+
import { createOutput, handleError } from '../utils/output.js';
|
|
4
5
|
import { promptPassword, promptMfaCode } from '../utils/prompt.js';
|
|
5
6
|
export function registerMfaCommands(program) {
|
|
6
7
|
const mfa = program.command('mfa')
|
|
7
8
|
.description('Multi-factor authentication management (requires JWT auth — use "lsvault auth login" first)')
|
|
8
9
|
.addHelpText('after', '\nNOTE: MFA management requires JWT authentication. API key auth is not sufficient.\nRun "lsvault auth login" to authenticate with email/password first.');
|
|
9
|
-
mfa.command('status')
|
|
10
|
-
.description('Show MFA status and configured methods')
|
|
11
|
-
.action(async () => {
|
|
12
|
-
const
|
|
10
|
+
addGlobalFlags(mfa.command('status')
|
|
11
|
+
.description('Show MFA status and configured methods'))
|
|
12
|
+
.action(async (_opts) => {
|
|
13
|
+
const flags = resolveFlags(_opts);
|
|
14
|
+
const out = createOutput(flags);
|
|
15
|
+
out.startSpinner('Fetching MFA status...');
|
|
13
16
|
try {
|
|
14
17
|
const client = await getClientAsync();
|
|
15
18
|
const status = await client.mfa.getStatus();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
out.stopSpinner();
|
|
20
|
+
if (flags.output === 'json') {
|
|
21
|
+
out.record({
|
|
22
|
+
mfaEnabled: status.mfaEnabled,
|
|
23
|
+
totpConfigured: status.totpConfigured,
|
|
24
|
+
passkeyCount: status.passkeyCount,
|
|
25
|
+
backupCodesRemaining: status.backupCodesRemaining,
|
|
26
|
+
passkeys: status.passkeys,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
out.raw(chalk.bold('MFA Status') + '\n');
|
|
31
|
+
out.raw(` Enabled: ${status.mfaEnabled ? chalk.green('Yes') : chalk.dim('No')}\n`);
|
|
32
|
+
out.raw(` TOTP Configured: ${status.totpConfigured ? chalk.green('Yes') : chalk.dim('No')}\n`);
|
|
33
|
+
out.raw(` Passkeys Registered: ${status.passkeyCount > 0 ? chalk.cyan(status.passkeyCount) : chalk.dim('0')}\n`);
|
|
34
|
+
out.raw(` Backup Codes Left: ${status.backupCodesRemaining > 0 ? chalk.cyan(status.backupCodesRemaining) : chalk.yellow('0')}\n`);
|
|
35
|
+
if (status.passkeys.length > 0) {
|
|
36
|
+
out.raw('\n');
|
|
37
|
+
out.raw(chalk.bold('Registered Passkeys:') + '\n');
|
|
38
|
+
for (const passkey of status.passkeys) {
|
|
39
|
+
const lastUsed = passkey.lastUsedAt
|
|
40
|
+
? new Date(passkey.lastUsedAt).toLocaleDateString()
|
|
41
|
+
: chalk.dim('never');
|
|
42
|
+
out.raw(` - ${chalk.cyan(passkey.name)} (last used: ${lastUsed})\n`);
|
|
43
|
+
}
|
|
30
44
|
}
|
|
31
45
|
}
|
|
32
46
|
}
|
|
33
47
|
catch (err) {
|
|
34
|
-
|
|
35
|
-
|
|
48
|
+
out.failSpinner('Failed to fetch MFA status');
|
|
49
|
+
handleError(out, err, 'MFA management requires JWT authentication');
|
|
36
50
|
}
|
|
37
51
|
});
|
|
38
|
-
mfa.command('setup-totp')
|
|
39
|
-
.description('Set up TOTP authenticator app (Google Authenticator, Authy, etc.)')
|
|
40
|
-
.action(async () => {
|
|
41
|
-
const
|
|
52
|
+
addGlobalFlags(mfa.command('setup-totp')
|
|
53
|
+
.description('Set up TOTP authenticator app (Google Authenticator, Authy, etc.)'))
|
|
54
|
+
.action(async (_opts) => {
|
|
55
|
+
const flags = resolveFlags(_opts);
|
|
56
|
+
const out = createOutput(flags);
|
|
57
|
+
out.startSpinner('Generating TOTP secret...');
|
|
42
58
|
try {
|
|
43
59
|
const client = await getClientAsync();
|
|
44
60
|
const setup = await client.mfa.setupTotp();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
out.stopSpinner();
|
|
62
|
+
if (flags.output === 'json') {
|
|
63
|
+
out.record({ secret: setup.secret, otpauthUri: setup.otpauthUri });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
out.raw(chalk.bold('TOTP Setup') + '\n');
|
|
67
|
+
out.raw('\n');
|
|
68
|
+
out.raw(`Secret: ${chalk.cyan(setup.secret)}\n`);
|
|
69
|
+
out.raw('\n');
|
|
70
|
+
out.raw('Add this URI to your authenticator app:\n');
|
|
71
|
+
out.raw(chalk.dim(setup.otpauthUri) + '\n');
|
|
72
|
+
out.raw('\n');
|
|
73
|
+
out.raw(chalk.yellow('Note: QR codes cannot be displayed in the terminal.') + '\n');
|
|
74
|
+
out.raw(chalk.yellow(' Copy the URI above to any authenticator app that supports otpauth:// URIs.') + '\n');
|
|
75
|
+
out.raw('\n');
|
|
76
|
+
}
|
|
77
|
+
// Prompt for verification code — skip in quiet mode
|
|
78
|
+
if (flags.quiet)
|
|
79
|
+
return;
|
|
57
80
|
const code = await promptMfaCode();
|
|
58
81
|
if (!code) {
|
|
59
|
-
|
|
82
|
+
out.status(chalk.yellow('Setup cancelled.'));
|
|
60
83
|
return;
|
|
61
84
|
}
|
|
62
|
-
|
|
85
|
+
out.startSpinner('Verifying code and enabling TOTP...');
|
|
63
86
|
const result = await client.mfa.verifyTotp(code);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
out.stopSpinner();
|
|
88
|
+
out.success('TOTP enabled successfully!');
|
|
89
|
+
if (flags.output === 'json') {
|
|
90
|
+
out.list(result.backupCodes.map((code) => ({ code })), { emptyMessage: 'No backup codes returned.' });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
out.raw('\n');
|
|
94
|
+
out.raw(chalk.bold.yellow('IMPORTANT: Save these backup codes securely!') + '\n');
|
|
95
|
+
out.raw(chalk.dim('You can use them to access your account if you lose your authenticator device.') + '\n');
|
|
96
|
+
out.raw('\n');
|
|
97
|
+
// Display backup codes in a grid (2 columns)
|
|
98
|
+
const codes = result.backupCodes;
|
|
99
|
+
for (let i = 0; i < codes.length; i += 2) {
|
|
100
|
+
const left = codes[i] || '';
|
|
101
|
+
const right = codes[i + 1] || '';
|
|
102
|
+
out.raw(` ${chalk.cyan(left.padEnd(20))} ${chalk.cyan(right)}\n`);
|
|
103
|
+
}
|
|
104
|
+
out.raw('\n');
|
|
75
105
|
}
|
|
76
|
-
console.log('');
|
|
77
106
|
}
|
|
78
107
|
catch (err) {
|
|
79
|
-
|
|
80
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
108
|
+
handleError(out, err, 'TOTP setup failed');
|
|
81
109
|
}
|
|
82
110
|
});
|
|
83
|
-
mfa.command('disable-totp')
|
|
84
|
-
.description('Disable TOTP authentication (requires password)')
|
|
85
|
-
.action(async () => {
|
|
111
|
+
addGlobalFlags(mfa.command('disable-totp')
|
|
112
|
+
.description('Disable TOTP authentication (requires password)'))
|
|
113
|
+
.action(async (_opts) => {
|
|
114
|
+
const flags = resolveFlags(_opts);
|
|
115
|
+
const out = createOutput(flags);
|
|
116
|
+
// Skip interactive prompt in quiet mode
|
|
117
|
+
if (flags.quiet) {
|
|
118
|
+
out.error('Password prompt required — cannot run in quiet mode without --password-stdin.');
|
|
119
|
+
process.exitCode = 1;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
86
122
|
const password = await promptPassword();
|
|
87
123
|
if (!password) {
|
|
88
|
-
|
|
124
|
+
out.status(chalk.yellow('Operation cancelled.'));
|
|
89
125
|
return;
|
|
90
126
|
}
|
|
91
|
-
|
|
127
|
+
out.startSpinner('Disabling TOTP...');
|
|
92
128
|
try {
|
|
93
129
|
const client = await getClientAsync();
|
|
94
130
|
const result = await client.mfa.disableTotp(password);
|
|
95
|
-
|
|
131
|
+
out.stopSpinner();
|
|
132
|
+
out.success(result.message, { message: result.message });
|
|
96
133
|
}
|
|
97
134
|
catch (err) {
|
|
98
|
-
|
|
99
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
135
|
+
handleError(out, err, 'Failed to disable TOTP');
|
|
100
136
|
}
|
|
101
137
|
});
|
|
102
|
-
mfa.command('backup-codes')
|
|
138
|
+
addGlobalFlags(mfa.command('backup-codes')
|
|
103
139
|
.description('Show remaining backup code count or regenerate codes')
|
|
104
|
-
.option('--regenerate', 'Generate new backup codes (requires password, invalidates old codes)')
|
|
105
|
-
.action(async (
|
|
106
|
-
|
|
140
|
+
.option('--regenerate', 'Generate new backup codes (requires password, invalidates old codes)'))
|
|
141
|
+
.action(async (_opts) => {
|
|
142
|
+
const flags = resolveFlags(_opts);
|
|
143
|
+
const out = createOutput(flags);
|
|
144
|
+
if (_opts.regenerate) {
|
|
107
145
|
// Regenerate backup codes
|
|
146
|
+
if (flags.quiet) {
|
|
147
|
+
out.error('Password prompt required — cannot run in quiet mode without --password-stdin.');
|
|
148
|
+
process.exitCode = 1;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
108
151
|
const password = await promptPassword();
|
|
109
152
|
if (!password) {
|
|
110
|
-
|
|
153
|
+
out.status(chalk.yellow('Operation cancelled.'));
|
|
111
154
|
return;
|
|
112
155
|
}
|
|
113
|
-
|
|
156
|
+
out.startSpinner('Regenerating backup codes...');
|
|
114
157
|
try {
|
|
115
158
|
const client = await getClientAsync();
|
|
116
159
|
const result = await client.mfa.regenerateBackupCodes(password);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
160
|
+
out.stopSpinner();
|
|
161
|
+
out.success('Backup codes regenerated!');
|
|
162
|
+
if (flags.output === 'json') {
|
|
163
|
+
out.list(result.backupCodes.map((code) => ({ code })), { emptyMessage: 'No backup codes returned.' });
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
out.raw('\n');
|
|
167
|
+
out.raw(chalk.bold.yellow('IMPORTANT: Save these new backup codes securely!') + '\n');
|
|
168
|
+
out.raw(chalk.dim('All previous backup codes have been invalidated.') + '\n');
|
|
169
|
+
out.raw('\n');
|
|
170
|
+
// Display backup codes in a grid (2 columns)
|
|
171
|
+
const codes = result.backupCodes;
|
|
172
|
+
for (let i = 0; i < codes.length; i += 2) {
|
|
173
|
+
const left = codes[i] || '';
|
|
174
|
+
const right = codes[i + 1] || '';
|
|
175
|
+
out.raw(` ${chalk.cyan(left.padEnd(20))} ${chalk.cyan(right)}\n`);
|
|
176
|
+
}
|
|
177
|
+
out.raw('\n');
|
|
128
178
|
}
|
|
129
|
-
console.log('');
|
|
130
179
|
}
|
|
131
180
|
catch (err) {
|
|
132
|
-
|
|
133
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
134
|
-
process.exitCode = 1;
|
|
181
|
+
handleError(out, err, 'Failed to regenerate backup codes');
|
|
135
182
|
}
|
|
136
183
|
}
|
|
137
184
|
else {
|
|
138
185
|
// Show backup code count
|
|
139
|
-
|
|
186
|
+
out.startSpinner('Fetching backup code count...');
|
|
140
187
|
try {
|
|
141
188
|
const client = await getClientAsync();
|
|
142
189
|
const status = await client.mfa.getStatus();
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
190
|
+
out.stopSpinner();
|
|
191
|
+
if (flags.output === 'json') {
|
|
192
|
+
out.record({ backupCodesRemaining: status.backupCodesRemaining });
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
out.raw(chalk.bold('Backup Codes') + '\n');
|
|
196
|
+
out.raw(` Remaining: ${status.backupCodesRemaining > 0 ? chalk.cyan(status.backupCodesRemaining) : chalk.yellow('0')}\n`);
|
|
197
|
+
if (status.backupCodesRemaining === 0) {
|
|
198
|
+
out.raw('\n');
|
|
199
|
+
out.raw(chalk.yellow('You have no backup codes remaining.') + '\n');
|
|
200
|
+
out.raw(chalk.yellow('Run `lsvault mfa backup-codes --regenerate` to generate new codes.') + '\n');
|
|
201
|
+
}
|
|
150
202
|
}
|
|
151
203
|
}
|
|
152
204
|
catch (err) {
|
|
153
|
-
|
|
154
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
155
|
-
process.exitCode = 1;
|
|
205
|
+
handleError(out, err, 'Failed to fetch backup code count');
|
|
156
206
|
}
|
|
157
207
|
}
|
|
158
208
|
});
|
package/dist/commands/plugins.js
CHANGED
|
@@ -44,7 +44,7 @@ export function registerPluginCommands(program) {
|
|
|
44
44
|
addGlobalFlags(plugins.command('install')
|
|
45
45
|
.description('Install a plugin from the marketplace')
|
|
46
46
|
.requiredOption('--plugin-id <pluginId>', 'Plugin marketplace identifier (e.g. org/plugin-name)')
|
|
47
|
-
.requiredOption('--version <version>', 'Version to install'))
|
|
47
|
+
.requiredOption('--plugin-version <version>', 'Version to install'))
|
|
48
48
|
.action(async (_opts) => {
|
|
49
49
|
const flags = resolveFlags(_opts);
|
|
50
50
|
const out = createOutput(flags);
|
|
@@ -53,7 +53,7 @@ export function registerPluginCommands(program) {
|
|
|
53
53
|
const client = await getClientAsync();
|
|
54
54
|
const installed = await client.plugins.install({
|
|
55
55
|
pluginId: _opts.pluginId,
|
|
56
|
-
version: _opts.
|
|
56
|
+
version: _opts.pluginVersion,
|
|
57
57
|
});
|
|
58
58
|
out.stopSpinner();
|
|
59
59
|
if (flags.output === 'json') {
|
|
@@ -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
|
+
import { confirmAction } from '../utils/confirm.js';
|
|
5
6
|
export function registerPublishVaultCommands(program) {
|
|
6
7
|
const pv = program.command('publish-vault').description('Manage whole-vault publishing (public sites)');
|
|
7
8
|
addGlobalFlags(pv.command('list')
|
|
@@ -37,7 +38,7 @@ export function registerPublishVaultCommands(program) {
|
|
|
37
38
|
.option('--description <desc>', 'Site description')
|
|
38
39
|
.option('--show-sidebar', 'Show sidebar navigation')
|
|
39
40
|
.option('--enable-search', 'Enable search on the site')
|
|
40
|
-
.option('--theme <theme>', 'Site theme')
|
|
41
|
+
.option('--theme <theme>', 'Site theme (e.g. default, minimal, blog, docs)')
|
|
41
42
|
.option('--domain <domainId>', 'Custom domain ID'))
|
|
42
43
|
.action(async (vaultId, _opts) => {
|
|
43
44
|
const flags = resolveFlags(_opts);
|
|
@@ -68,7 +69,7 @@ export function registerPublishVaultCommands(program) {
|
|
|
68
69
|
.option('--description <desc>', 'Site description')
|
|
69
70
|
.option('--show-sidebar', 'Show sidebar')
|
|
70
71
|
.option('--enable-search', 'Enable search')
|
|
71
|
-
.option('--theme <theme>', 'Site theme')
|
|
72
|
+
.option('--theme <theme>', 'Site theme (e.g. default, minimal, blog, docs)')
|
|
72
73
|
.option('--domain <domainId>', 'Custom domain ID'))
|
|
73
74
|
.action(async (vaultId, _opts) => {
|
|
74
75
|
const flags = resolveFlags(_opts);
|
|
@@ -100,12 +101,18 @@ export function registerPublishVaultCommands(program) {
|
|
|
100
101
|
});
|
|
101
102
|
addGlobalFlags(pv.command('unpublish')
|
|
102
103
|
.description('Unpublish a vault site')
|
|
103
|
-
.argument('<vaultId>', 'Vault ID')
|
|
104
|
+
.argument('<vaultId>', 'Vault ID')
|
|
105
|
+
.option('-y, --yes', 'Skip confirmation prompt'))
|
|
104
106
|
.action(async (vaultId, _opts) => {
|
|
105
107
|
const flags = resolveFlags(_opts);
|
|
106
108
|
const out = createOutput(flags);
|
|
107
|
-
out.startSpinner('Unpublishing vault...');
|
|
108
109
|
try {
|
|
110
|
+
const confirmed = await confirmAction(`Unpublish vault site for vault ${vaultId}?`, { yes: _opts.yes });
|
|
111
|
+
if (!confirmed) {
|
|
112
|
+
out.status('Unpublish cancelled.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
out.startSpinner('Unpublishing vault...');
|
|
109
116
|
const client = await getClientAsync();
|
|
110
117
|
await client.publishVault.unpublish(vaultId);
|
|
111
118
|
out.success('Vault unpublished', { vaultId });
|
package/dist/commands/publish.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getClientAsync } from '../client.js';
|
|
|
3
3
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
4
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
5
|
import { resolveVaultId } from '../utils/resolve-vault.js';
|
|
6
|
+
import { loadConfigAsync } from '../config.js';
|
|
6
7
|
export function registerPublishCommands(program) {
|
|
7
8
|
const publish = program.command('publish').description('Publish documents to public profile pages');
|
|
8
9
|
addGlobalFlags(publish.command('list')
|
|
@@ -63,6 +64,7 @@ export function registerPublishCommands(program) {
|
|
|
63
64
|
out.startSpinner('Publishing document...');
|
|
64
65
|
try {
|
|
65
66
|
vaultId = await resolveVaultId(vaultId);
|
|
67
|
+
out.debug(`API: POST publish ${vaultId}/${docPath}`);
|
|
66
68
|
const client = await getClientAsync();
|
|
67
69
|
const params = {
|
|
68
70
|
slug: String(_opts.slug),
|
|
@@ -74,9 +76,11 @@ export function registerPublishCommands(program) {
|
|
|
74
76
|
if (_opts.ogImage)
|
|
75
77
|
params.ogImage = String(_opts.ogImage);
|
|
76
78
|
const pub = await client.publish.create(vaultId, docPath, params);
|
|
79
|
+
const config = await loadConfigAsync();
|
|
80
|
+
const baseUrl = config.apiUrl.replace(/\/api\/v\d+\/?$/, '');
|
|
77
81
|
out.success('Document published successfully!', {
|
|
78
82
|
slug: pub.slug,
|
|
79
|
-
url:
|
|
83
|
+
url: `${baseUrl}/${pub.publishedBy}/${pub.slug}`,
|
|
80
84
|
isPublished: pub.isPublished,
|
|
81
85
|
seoTitle: pub.seoTitle || null,
|
|
82
86
|
seoDescription: pub.seoDescription || null,
|
package/dist/commands/saml.js
CHANGED
|
@@ -3,7 +3,9 @@ import { getClientAsync } from '../client.js';
|
|
|
3
3
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
4
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
5
|
export function registerSamlCommands(program) {
|
|
6
|
-
const saml = program.command('saml')
|
|
6
|
+
const saml = program.command('saml')
|
|
7
|
+
.description('SAML SSO configuration management (requires admin role)')
|
|
8
|
+
.addHelpText('after', '\nNOTE: SAML commands require JWT authentication with admin role.\nRun "lsvault auth login" to authenticate first.');
|
|
7
9
|
// ── list-configs ─────────────────────────────────────────────────────────
|
|
8
10
|
addGlobalFlags(saml.command('list-configs')
|
|
9
11
|
.description('List all SSO configurations'))
|
|
@@ -154,12 +156,14 @@ export function registerSamlCommands(program) {
|
|
|
154
156
|
addGlobalFlags(saml.command('delete-config')
|
|
155
157
|
.description('Delete an SSO configuration')
|
|
156
158
|
.argument('<id>', 'SSO config ID')
|
|
157
|
-
.option('--force', 'Skip confirmation prompt')
|
|
159
|
+
.option('--force', 'Skip confirmation prompt')
|
|
160
|
+
.option('-y, --yes', 'Alias for --force'))
|
|
158
161
|
.action(async (id, _opts) => {
|
|
159
162
|
const flags = resolveFlags(_opts);
|
|
160
163
|
const out = createOutput(flags);
|
|
161
|
-
if (!_opts.force) {
|
|
162
|
-
out.
|
|
164
|
+
if (!_opts.force && !_opts.yes) {
|
|
165
|
+
out.warn(`Pass --force to delete SSO config ${id}.`);
|
|
166
|
+
process.exitCode = 1;
|
|
163
167
|
return;
|
|
164
168
|
}
|
|
165
169
|
out.startSpinner('Deleting SSO config...');
|
|
@@ -203,6 +207,11 @@ export function registerSamlCommands(program) {
|
|
|
203
207
|
.action(async (slug, _opts) => {
|
|
204
208
|
const flags = resolveFlags(_opts);
|
|
205
209
|
const out = createOutput(flags);
|
|
210
|
+
if (!slug || !slug.trim()) {
|
|
211
|
+
out.error('Slug cannot be empty.');
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
206
215
|
try {
|
|
207
216
|
const client = await getClientAsync();
|
|
208
217
|
const url = client.saml.getLoginUrl(slug);
|
package/dist/commands/search.js
CHANGED
|
@@ -16,7 +16,11 @@ EXAMPLES
|
|
|
16
16
|
lsvault search "meeting notes"
|
|
17
17
|
lsvault search "project plan" --vault abc123
|
|
18
18
|
lsvault search "typescript" --tags dev,code --limit 5
|
|
19
|
-
lsvault search "machine learning" --mode semantic
|
|
19
|
+
lsvault search "machine learning" --mode semantic
|
|
20
|
+
|
|
21
|
+
NOTE
|
|
22
|
+
Semantic search (--mode semantic) requires the embedding worker to have
|
|
23
|
+
processed documents. If results are empty, ensure the worker is running.`))
|
|
20
24
|
.action(async (query, _opts) => {
|
|
21
25
|
const flags = resolveFlags(_opts);
|
|
22
26
|
const out = createOutput(flags);
|
package/dist/commands/shares.js
CHANGED
|
@@ -5,6 +5,7 @@ import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
|
5
5
|
import { createOutput, handleError } from '../utils/output.js';
|
|
6
6
|
import { promptPassword, readPasswordFromStdin } from '../utils/prompt.js';
|
|
7
7
|
import { resolveVaultId } from '../utils/resolve-vault.js';
|
|
8
|
+
import { confirmAction } from '../utils/confirm.js';
|
|
8
9
|
export function registerShareCommands(program) {
|
|
9
10
|
const shares = program.command('shares').description('Create, list, and revoke document share links');
|
|
10
11
|
addGlobalFlags(shares.command('list')
|
|
@@ -134,12 +135,18 @@ export function registerShareCommands(program) {
|
|
|
134
135
|
addGlobalFlags(shares.command('revoke')
|
|
135
136
|
.description('Revoke a share link')
|
|
136
137
|
.argument('<vaultId>', 'Vault ID or slug')
|
|
137
|
-
.argument('<shareId>', 'Share link ID')
|
|
138
|
+
.argument('<shareId>', 'Share link ID')
|
|
139
|
+
.option('-y, --yes', 'Skip confirmation prompt'))
|
|
138
140
|
.action(async (vaultId, shareId, _opts) => {
|
|
139
141
|
const flags = resolveFlags(_opts);
|
|
140
142
|
const out = createOutput(flags);
|
|
141
|
-
out.startSpinner('Revoking share link...');
|
|
142
143
|
try {
|
|
144
|
+
const confirmed = await confirmAction(`Revoke share link ${shareId}?`, { yes: _opts.yes });
|
|
145
|
+
if (!confirmed) {
|
|
146
|
+
out.status('Revoke cancelled.');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
out.startSpinner('Revoking share link...');
|
|
143
150
|
vaultId = await resolveVaultId(vaultId);
|
|
144
151
|
const client = await getClientAsync();
|
|
145
152
|
await client.shares.revoke(vaultId, shareId);
|
package/dist/commands/sync.js
CHANGED
|
@@ -4,6 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { getClientAsync } from '../client.js';
|
|
5
5
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
6
6
|
import { createOutput, handleError } from '../utils/output.js';
|
|
7
|
+
import { confirmAction } from '../utils/confirm.js';
|
|
7
8
|
import { formatUptime } from '../utils/format.js';
|
|
8
9
|
import { loadSyncConfigs, createSyncConfig, deleteSyncConfig, getSyncConfig, } from '../sync/config.js';
|
|
9
10
|
import { deleteSyncState, loadSyncState, saveSyncState, hashFileContent, buildRemoteFileState } from '../sync/state.js';
|
|
@@ -124,12 +125,18 @@ Sync modes:
|
|
|
124
125
|
// sync delete <syncId>
|
|
125
126
|
addGlobalFlags(sync.command('delete')
|
|
126
127
|
.description('Delete a sync configuration')
|
|
127
|
-
.argument('<syncId>', 'Sync configuration ID')
|
|
128
|
+
.argument('<syncId>', 'Sync configuration ID')
|
|
129
|
+
.option('-y, --yes', 'Skip confirmation prompt'))
|
|
128
130
|
.action(async (syncId, _opts) => {
|
|
129
131
|
const flags = resolveFlags(_opts);
|
|
130
132
|
const out = createOutput(flags);
|
|
131
|
-
out.startSpinner('Deleting sync configuration...');
|
|
132
133
|
try {
|
|
134
|
+
const confirmed = await confirmAction(`Delete sync configuration ${syncId}?`, { yes: _opts.yes });
|
|
135
|
+
if (!confirmed) {
|
|
136
|
+
out.status('Delete cancelled.');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
out.startSpinner('Deleting sync configuration...');
|
|
133
140
|
const deleted = deleteSyncConfig(syncId);
|
|
134
141
|
if (!deleted) {
|
|
135
142
|
out.failSpinner('Sync configuration not found');
|
|
@@ -274,8 +281,6 @@ Sync modes:
|
|
|
274
281
|
}
|
|
275
282
|
out.stopSpinner();
|
|
276
283
|
if (flags.dryRun) {
|
|
277
|
-
out.status(chalk.yellow('Dry run — no changes will be made:'));
|
|
278
|
-
out.status(formatDiff(diff));
|
|
279
284
|
if (flags.output === 'json') {
|
|
280
285
|
out.record({
|
|
281
286
|
dryRun: true,
|
|
@@ -285,6 +290,10 @@ Sync modes:
|
|
|
285
290
|
totalBytes: diff.totalBytes,
|
|
286
291
|
});
|
|
287
292
|
}
|
|
293
|
+
else {
|
|
294
|
+
out.status(chalk.yellow('Dry run — no changes will be made:'));
|
|
295
|
+
out.status(formatDiff(diff));
|
|
296
|
+
}
|
|
288
297
|
return;
|
|
289
298
|
}
|
|
290
299
|
if (flags.verbose) {
|
package/dist/commands/teams.js
CHANGED
|
@@ -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
|
+
import { confirmAction } from '../utils/confirm.js';
|
|
5
6
|
export function registerTeamCommands(program) {
|
|
6
7
|
const teams = program.command('teams').description('Manage teams, members, invitations, and shared vaults');
|
|
7
8
|
// ── Team CRUD ──────────────────────────────────────────────────────
|
|
@@ -12,8 +13,10 @@ export function registerTeamCommands(program) {
|
|
|
12
13
|
const out = createOutput(flags);
|
|
13
14
|
out.startSpinner('Fetching teams...');
|
|
14
15
|
try {
|
|
16
|
+
out.debug('API: GET teams');
|
|
15
17
|
const client = await getClientAsync();
|
|
16
18
|
const teamList = await client.teams.list();
|
|
19
|
+
out.debug(`Response: ${teamList.length} teams`);
|
|
17
20
|
out.stopSpinner();
|
|
18
21
|
out.list(teamList.map(t => ({ name: t.name, id: t.id, description: t.description || 'No description' })), {
|
|
19
22
|
emptyMessage: 'No teams found.',
|
|
@@ -152,7 +155,7 @@ EXAMPLES
|
|
|
152
155
|
.description('Update a member role')
|
|
153
156
|
.argument('<teamId>', 'Team ID')
|
|
154
157
|
.argument('<userId>', 'User ID')
|
|
155
|
-
.requiredOption('-r, --role <role>', 'New role
|
|
158
|
+
.requiredOption('-r, --role <role>', 'New role: admin, editor, or viewer'))
|
|
156
159
|
.action(async (teamId, userId, _opts) => {
|
|
157
160
|
const flags = resolveFlags(_opts);
|
|
158
161
|
const out = createOutput(flags);
|
|
@@ -195,10 +198,16 @@ EXAMPLES
|
|
|
195
198
|
});
|
|
196
199
|
addGlobalFlags(teams.command('leave')
|
|
197
200
|
.description('Leave a team')
|
|
198
|
-
.argument('<teamId>', 'Team ID')
|
|
201
|
+
.argument('<teamId>', 'Team ID')
|
|
202
|
+
.option('-y, --yes', 'Skip confirmation prompt'))
|
|
199
203
|
.action(async (teamId, _opts) => {
|
|
200
204
|
const flags = resolveFlags(_opts);
|
|
201
205
|
const out = createOutput(flags);
|
|
206
|
+
const confirmed = await confirmAction(`Are you sure you want to leave team ${teamId}?`, { yes: !!_opts.yes });
|
|
207
|
+
if (!confirmed) {
|
|
208
|
+
out.status('Cancelled.');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
202
211
|
out.startSpinner('Leaving team...');
|
|
203
212
|
try {
|
|
204
213
|
const client = await getClientAsync();
|
|
@@ -245,11 +254,11 @@ EXAMPLES
|
|
|
245
254
|
.description('Invite a user to the team')
|
|
246
255
|
.argument('<teamId>', 'Team ID')
|
|
247
256
|
.argument('<email>', 'Email address')
|
|
248
|
-
.requiredOption('-r, --role <role>', 'Role
|
|
257
|
+
.requiredOption('-r, --role <role>', 'Role to assign: admin, editor, or viewer (default: editor)'))
|
|
249
258
|
.action(async (teamId, email, _opts) => {
|
|
250
259
|
const flags = resolveFlags(_opts);
|
|
251
260
|
const out = createOutput(flags);
|
|
252
|
-
const role = String(_opts.role);
|
|
261
|
+
const role = (String(_opts.role) || 'editor');
|
|
253
262
|
out.startSpinner('Sending invitation...');
|
|
254
263
|
try {
|
|
255
264
|
const client = await getClientAsync();
|
|
@@ -267,10 +276,16 @@ EXAMPLES
|
|
|
267
276
|
addGlobalFlags(invitations.command('revoke')
|
|
268
277
|
.description('Revoke a pending invitation')
|
|
269
278
|
.argument('<teamId>', 'Team ID')
|
|
270
|
-
.argument('<invitationId>', 'Invitation ID')
|
|
279
|
+
.argument('<invitationId>', 'Invitation ID')
|
|
280
|
+
.option('-y, --yes', 'Skip confirmation prompt'))
|
|
271
281
|
.action(async (teamId, invitationId, _opts) => {
|
|
272
282
|
const flags = resolveFlags(_opts);
|
|
273
283
|
const out = createOutput(flags);
|
|
284
|
+
const confirmed = await confirmAction(`Revoke invitation ${invitationId}?`, { yes: !!_opts.yes });
|
|
285
|
+
if (!confirmed) {
|
|
286
|
+
out.status('Cancelled.');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
274
289
|
out.startSpinner('Revoking invitation...');
|
|
275
290
|
try {
|
|
276
291
|
const client = await getClientAsync();
|
package/dist/commands/user.js
CHANGED
|
@@ -5,6 +5,7 @@ import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
|
5
5
|
import { createOutput, handleError } from '../utils/output.js';
|
|
6
6
|
import { formatBytes } from '../utils/format.js';
|
|
7
7
|
import { promptPassword, readPasswordFromStdin } from '../utils/prompt.js';
|
|
8
|
+
import { confirmAction } from '../utils/confirm.js';
|
|
8
9
|
export function registerUserCommands(program) {
|
|
9
10
|
const user = program.command('user').description('View account details and storage usage');
|
|
10
11
|
addGlobalFlags(user.command('storage')
|
|
@@ -176,10 +177,16 @@ export function registerUserCommands(program) {
|
|
|
176
177
|
.description('Request account deletion')
|
|
177
178
|
.option('--password-stdin', 'Read password from stdin for CI usage')
|
|
178
179
|
.option('--reason <reason>', 'Reason for deletion')
|
|
179
|
-
.option('--export-data', 'Request data export before deletion')
|
|
180
|
+
.option('--export-data', 'Request data export before deletion')
|
|
181
|
+
.option('-y, --yes', 'Skip confirmation prompt'))
|
|
180
182
|
.action(async (_opts) => {
|
|
181
183
|
const flags = resolveFlags(_opts);
|
|
182
184
|
const out = createOutput(flags);
|
|
185
|
+
const confirmed = await confirmAction('Are you sure you want to delete your account? This cannot be undone.', { yes: _opts.yes });
|
|
186
|
+
if (!confirmed) {
|
|
187
|
+
out.status('Account deletion cancelled.');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
183
190
|
let password;
|
|
184
191
|
if (_opts.passwordStdin) {
|
|
185
192
|
password = await readPasswordFromStdin();
|