@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/admin.js
CHANGED
|
@@ -4,7 +4,9 @@ import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
|
4
4
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
5
|
import { formatBytes, formatUptime } from '../utils/format.js';
|
|
6
6
|
export function registerAdminCommands(program) {
|
|
7
|
-
const admin = program.command('admin')
|
|
7
|
+
const admin = program.command('admin')
|
|
8
|
+
.description('System administration (requires admin role)')
|
|
9
|
+
.addHelpText('after', '\nNOTE: Admin commands require JWT authentication with admin role.\nRun "lsvault auth login" to authenticate first.');
|
|
8
10
|
// ── System Stats ────────────────────────────────────────────────────
|
|
9
11
|
const stats = admin.command('stats').description('View system-wide statistics and metrics');
|
|
10
12
|
const statsAction = async (_opts) => {
|
package/dist/commands/ai.js
CHANGED
|
@@ -80,7 +80,11 @@ export function registerAiCommands(program) {
|
|
|
80
80
|
addGlobalFlags(ai.command('chat')
|
|
81
81
|
.description('Send a message in an AI chat session')
|
|
82
82
|
.argument('<sessionId>', 'Session ID')
|
|
83
|
-
.argument('<message>', 'Message to send')
|
|
83
|
+
.argument('<message>', 'Message to send')
|
|
84
|
+
.addHelpText('after', `
|
|
85
|
+
EXAMPLES
|
|
86
|
+
lsvault ai chat <session-id> "What are the key points in my notes?"
|
|
87
|
+
lsvault ai chat <session-id> "Summarize recent changes" -o json`))
|
|
84
88
|
.action(async (sessionId, message, _opts) => {
|
|
85
89
|
const flags = resolveFlags(_opts);
|
|
86
90
|
const out = createOutput(flags);
|
|
@@ -67,7 +67,7 @@ export function registerAnalyticsCommands(program) {
|
|
|
67
67
|
.addHelpText('after', `
|
|
68
68
|
NOTE
|
|
69
69
|
The publishedDocId is a UUID, NOT a document path. Get it from:
|
|
70
|
-
lsvault
|
|
70
|
+
lsvault analytics published`))
|
|
71
71
|
.action(async (vaultId, publishedDocId, _opts) => {
|
|
72
72
|
const flags = resolveFlags(_opts);
|
|
73
73
|
const out = createOutput(flags);
|
package/dist/commands/audit.js
CHANGED
package/dist/commands/auth.js
CHANGED
|
@@ -5,6 +5,8 @@ import { loadConfigAsync, getCredentialManager } from '../config.js';
|
|
|
5
5
|
import { getClientAsync } from '../client.js';
|
|
6
6
|
import { migrateCredentials, hasPlaintextCredentials, checkAndPromptMigration } from '../lib/migration.js';
|
|
7
7
|
import { promptPassword, promptMfaCode } from '../utils/prompt.js';
|
|
8
|
+
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
9
|
+
import { createOutput, handleError } from '../utils/output.js';
|
|
8
10
|
export function registerAuthCommands(program) {
|
|
9
11
|
const auth = program.command('auth').description('Authentication and credential management');
|
|
10
12
|
auth.command('login')
|
|
@@ -186,41 +188,47 @@ EXAMPLES
|
|
|
186
188
|
spinner.info('Migration skipped.');
|
|
187
189
|
}
|
|
188
190
|
});
|
|
189
|
-
auth.command('whoami')
|
|
190
|
-
.description('Show the currently authenticated user, plan, and API URL')
|
|
191
|
-
.action(async () => {
|
|
191
|
+
addGlobalFlags(auth.command('whoami')
|
|
192
|
+
.description('Show the currently authenticated user, plan, and API URL'))
|
|
193
|
+
.action(async (_opts) => {
|
|
194
|
+
const flags = resolveFlags(_opts);
|
|
195
|
+
const out = createOutput(flags);
|
|
192
196
|
const config = await loadConfigAsync();
|
|
193
|
-
console.log(`API URL: ${config.apiUrl}`);
|
|
194
|
-
console.log(`API Key: ${config.apiKey ? config.apiKey.slice(0, 12) + '...' : chalk.yellow('not set')}`);
|
|
195
|
-
if (config.accessToken) {
|
|
196
|
-
console.log(`Auth: ${chalk.green('JWT (email/password)')}`);
|
|
197
|
-
}
|
|
198
197
|
// Warn about plaintext credentials
|
|
199
198
|
await checkAndPromptMigration(getCredentialManager());
|
|
200
|
-
if (config.apiKey
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
199
|
+
if (!config.apiKey && !config.accessToken) {
|
|
200
|
+
out.record({
|
|
201
|
+
apiUrl: config.apiUrl,
|
|
202
|
+
apiKey: null,
|
|
203
|
+
auth: 'none',
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
out.startSpinner('Fetching user info...');
|
|
208
|
+
try {
|
|
209
|
+
const client = await getClientAsync();
|
|
210
|
+
const user = await client.user.me();
|
|
211
|
+
out.stopSpinner();
|
|
212
|
+
let plan = user.subscriptionTier;
|
|
213
|
+
if (!plan) {
|
|
214
|
+
try {
|
|
215
|
+
const sub = await client.subscription.get();
|
|
216
|
+
plan = sub.subscription.tier;
|
|
216
217
|
}
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
catch (err) {
|
|
220
|
-
spinner.fail('Could not fetch user info');
|
|
221
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
222
|
-
process.exitCode = 1;
|
|
218
|
+
catch { /* API key may not have scope */ }
|
|
223
219
|
}
|
|
220
|
+
out.record({
|
|
221
|
+
apiUrl: config.apiUrl,
|
|
222
|
+
apiKey: config.apiKey ? config.apiKey.slice(0, 12) + '...' : null,
|
|
223
|
+
auth: config.accessToken ? 'JWT (email/password)' : 'API key',
|
|
224
|
+
email: user.email,
|
|
225
|
+
displayName: user.displayName || null,
|
|
226
|
+
role: user.role,
|
|
227
|
+
plan: plan || 'unknown',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
handleError(out, err, 'Could not fetch user info');
|
|
224
232
|
}
|
|
225
233
|
});
|
|
226
234
|
}
|
package/dist/commands/booking.js
CHANGED
|
@@ -170,7 +170,7 @@ export function registerBookingCommands(program) {
|
|
|
170
170
|
const client = await getClientAsync();
|
|
171
171
|
await client.booking.deleteSlot(vaultId, slotId);
|
|
172
172
|
out.stopSpinner();
|
|
173
|
-
out.
|
|
173
|
+
out.success(`Slot ${slotId} deleted.`, { id: slotId, deleted: true });
|
|
174
174
|
}
|
|
175
175
|
catch (err) {
|
|
176
176
|
handleError(out, err, 'Delete slot failed');
|
|
@@ -405,7 +405,8 @@ export function registerBookingCommands(program) {
|
|
|
405
405
|
addGlobalFlags(booking.command('reschedule')
|
|
406
406
|
.description('Reschedule a booking by guest reschedule token')
|
|
407
407
|
.argument('<token>', 'Reschedule token (from guest email link)')
|
|
408
|
-
.argument('<newStartAt>', 'New start time in ISO 8601 format (e.g. 2026-03-15T10:00:00Z)')
|
|
408
|
+
.argument('<newStartAt>', 'New start time in ISO 8601 format (e.g. 2026-03-15T10:00:00Z)')
|
|
409
|
+
.addHelpText('after', '\n The <token> is the guest reschedule token from the booking confirmation email,\n not the booking ID.'))
|
|
409
410
|
.action(async (token, newStartAt, _opts) => {
|
|
410
411
|
const flags = resolveFlags(_opts);
|
|
411
412
|
const out = createOutput(flags);
|
|
@@ -31,6 +31,7 @@ export function registerCalendarCommands(program) {
|
|
|
31
31
|
out.startSpinner('Loading calendar...');
|
|
32
32
|
try {
|
|
33
33
|
vaultId = await resolveVaultId(vaultId);
|
|
34
|
+
out.debug(`API: GET calendar events for vault ${vaultId}`);
|
|
34
35
|
const client = await getClientAsync();
|
|
35
36
|
const response = await client.calendar.getActivity(vaultId, {
|
|
36
37
|
start: _opts.start ?? getDefaultStart(),
|
|
@@ -134,9 +135,7 @@ export function registerCalendarCommands(program) {
|
|
|
134
135
|
recurrence: _opts.recurrence || null,
|
|
135
136
|
});
|
|
136
137
|
out.stopSpinner();
|
|
137
|
-
out.
|
|
138
|
-
? chalk.green(`Due date cleared for ${path}`)
|
|
139
|
-
: chalk.green(`Due date set to ${dateStr} for ${path}`));
|
|
138
|
+
out.success(dateStr === 'clear' ? `Due date cleared for ${path}` : `Due date set to ${dateStr} for ${path}`, { path, dueDate: dateStr === 'clear' ? null : dateStr });
|
|
140
139
|
}
|
|
141
140
|
catch (err) {
|
|
142
141
|
handleError(out, err, 'Set due date failed');
|
|
@@ -290,7 +289,7 @@ export function registerCalendarCommands(program) {
|
|
|
290
289
|
const client = await getClientAsync();
|
|
291
290
|
await client.calendar.deleteEvent(vaultId, eventId);
|
|
292
291
|
out.stopSpinner();
|
|
293
|
-
out.
|
|
292
|
+
out.success(`Event ${eventId} deleted.`, { id: eventId, deleted: true });
|
|
294
293
|
}
|
|
295
294
|
catch (err) {
|
|
296
295
|
handleError(out, err, 'Delete event failed');
|
|
@@ -809,7 +808,7 @@ NOTE
|
|
|
809
808
|
const client = await getClientAsync();
|
|
810
809
|
await client.calendar.removeParticipant(vaultId, eventId, participantId);
|
|
811
810
|
out.stopSpinner();
|
|
812
|
-
out.
|
|
811
|
+
out.success(`Participant ${participantId} removed.`, { id: participantId, eventId, removed: true });
|
|
813
812
|
}
|
|
814
813
|
catch (err) {
|
|
815
814
|
handleError(out, err, 'Remove participant failed');
|
|
@@ -1,45 +1,217 @@
|
|
|
1
1
|
const SAFE_COMMAND_NAME = /^[a-z][a-z0-9-]*$/;
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Recursively walk the Commander tree and produce a structured CommandNode tree.
|
|
4
|
+
* Only names matching SAFE_COMMAND_NAME are included (shell injection guard).
|
|
5
|
+
*/
|
|
6
|
+
function collectCommandTree(cmd) {
|
|
7
|
+
const nodes = [];
|
|
4
8
|
for (const sub of cmd.commands) {
|
|
5
9
|
const name = sub.name();
|
|
6
|
-
// Skip any name that doesn't match the safe pattern to prevent shell injection
|
|
7
10
|
if (!SAFE_COMMAND_NAME.test(name))
|
|
8
11
|
continue;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
// Collect long-form option flags from this command's declared options.
|
|
13
|
+
const options = sub.options
|
|
14
|
+
.map((o) => o.long)
|
|
15
|
+
.filter((flag) => typeof flag === 'string');
|
|
16
|
+
nodes.push({
|
|
17
|
+
name,
|
|
18
|
+
description: sub.description() ?? '',
|
|
19
|
+
subcommands: collectCommandTree(sub),
|
|
20
|
+
options,
|
|
21
|
+
});
|
|
12
22
|
}
|
|
13
|
-
return
|
|
23
|
+
return nodes;
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Bash
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* Emit the bash case arm for a top-level command.
|
|
30
|
+
* Handles COMP_CWORD == 2 (show subcommand names) and COMP_CWORD >= 3 (show
|
|
31
|
+
* a flat list of options for the matched sub/sub-subcommand).
|
|
32
|
+
*/
|
|
33
|
+
function bashCaseArm(node, indent) {
|
|
34
|
+
const lines = [];
|
|
35
|
+
lines.push(`${indent}${node.name})`);
|
|
36
|
+
if (node.subcommands.length > 0) {
|
|
37
|
+
const subNames = node.subcommands.map(s => s.name).join(' ');
|
|
38
|
+
lines.push(`${indent} if [[ $COMP_CWORD -eq 2 ]]; then`);
|
|
39
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "${subNames}" -- "$cur"))`);
|
|
40
|
+
lines.push(`${indent} else`);
|
|
41
|
+
// Third-level: match sub-subcommand name in COMP_WORDS[2] and show its options
|
|
42
|
+
lines.push(`${indent} case "$cmd2" in`);
|
|
43
|
+
for (const sub of node.subcommands) {
|
|
44
|
+
const allOpts = sub.options.join(' ');
|
|
45
|
+
// Also include sub-subcommand names if this node has nested commands
|
|
46
|
+
const deepNames = sub.subcommands.map(s => s.name).join(' ');
|
|
47
|
+
const completions = [deepNames, allOpts].filter(Boolean).join(' ');
|
|
48
|
+
lines.push(`${indent} ${sub.name})`);
|
|
49
|
+
if (completions) {
|
|
50
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "${completions}" -- "$cur"))`);
|
|
51
|
+
}
|
|
52
|
+
lines.push(`${indent} ;;`);
|
|
53
|
+
}
|
|
54
|
+
lines.push(`${indent} esac`);
|
|
55
|
+
lines.push(`${indent} fi`);
|
|
56
|
+
}
|
|
57
|
+
else if (node.options.length > 0) {
|
|
58
|
+
// Leaf command with flags only
|
|
59
|
+
const opts = node.options.join(' ');
|
|
60
|
+
lines.push(`${indent} COMPREPLY=($(compgen -W "${opts}" -- "$cur"))`);
|
|
61
|
+
}
|
|
62
|
+
lines.push(`${indent} ;;`);
|
|
63
|
+
return lines.join('\n');
|
|
14
64
|
}
|
|
15
65
|
export function generateBashCompletion(program) {
|
|
16
|
-
const
|
|
66
|
+
const tree = collectCommandTree(program);
|
|
67
|
+
const topLevelNames = tree.map(n => n.name).join(' ');
|
|
68
|
+
const caseArms = tree.map(node => bashCaseArm(node, ' ')).join('\n');
|
|
17
69
|
return `# Bash completion for lsvault
|
|
18
70
|
# Add to ~/.bashrc: eval "$(lsvault completion bash)"
|
|
19
71
|
_lsvault_completions() {
|
|
20
72
|
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
21
|
-
local
|
|
22
|
-
|
|
73
|
+
local cmd1="\${COMP_WORDS[1]:-}"
|
|
74
|
+
local cmd2="\${COMP_WORDS[2]:-}"
|
|
75
|
+
|
|
76
|
+
# Top-level completions
|
|
77
|
+
if [[ $COMP_CWORD -eq 1 ]]; then
|
|
78
|
+
COMPREPLY=($(compgen -W "${topLevelNames}" -- "$cur"))
|
|
79
|
+
return
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Second-level and deeper
|
|
83
|
+
case "$cmd1" in
|
|
84
|
+
${caseArms}
|
|
85
|
+
esac
|
|
23
86
|
}
|
|
24
87
|
complete -F _lsvault_completions lsvault
|
|
25
88
|
`;
|
|
26
89
|
}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Zsh
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
function zshDescribeArray(nodes, varName, indent) {
|
|
94
|
+
const lines = [];
|
|
95
|
+
lines.push(`${indent}local -a ${varName}`);
|
|
96
|
+
lines.push(`${indent}${varName}=(`);
|
|
97
|
+
for (const n of nodes) {
|
|
98
|
+
// Escape single quotes in description
|
|
99
|
+
const desc = n.description.replace(/'/g, "'\\''");
|
|
100
|
+
lines.push(`${indent} '${n.name}:${desc}'`);
|
|
101
|
+
}
|
|
102
|
+
lines.push(`${indent})`);
|
|
103
|
+
return lines.join('\n');
|
|
104
|
+
}
|
|
105
|
+
function zshCaseArm(node, indent) {
|
|
106
|
+
const lines = [];
|
|
107
|
+
lines.push(`${indent}${node.name})`);
|
|
108
|
+
if (node.subcommands.length > 0) {
|
|
109
|
+
lines.push(zshDescribeArray(node.subcommands, 'subcmds', indent + ' '));
|
|
110
|
+
lines.push(`${indent} if (( CURRENT == 3 )); then`);
|
|
111
|
+
lines.push(`${indent} _describe 'subcommand' subcmds`);
|
|
112
|
+
lines.push(`${indent} else`);
|
|
113
|
+
// Third-level: show options for matched subcommand
|
|
114
|
+
lines.push(`${indent} case "$words[3]" in`);
|
|
115
|
+
for (const sub of node.subcommands) {
|
|
116
|
+
if (sub.options.length > 0 || sub.subcommands.length > 0) {
|
|
117
|
+
lines.push(`${indent} ${sub.name})`);
|
|
118
|
+
if (sub.subcommands.length > 0) {
|
|
119
|
+
lines.push(zshDescribeArray(sub.subcommands, 'deepcmds', indent + ' '));
|
|
120
|
+
lines.push(`${indent} if (( CURRENT == 4 )); then`);
|
|
121
|
+
lines.push(`${indent} _describe 'subcommand' deepcmds`);
|
|
122
|
+
if (sub.options.length > 0) {
|
|
123
|
+
const optSpecs = sub.options.map(o => `'${o}'`).join(' ');
|
|
124
|
+
lines.push(`${indent} else`);
|
|
125
|
+
lines.push(`${indent} _arguments ${optSpecs}`);
|
|
126
|
+
}
|
|
127
|
+
lines.push(`${indent} fi`);
|
|
128
|
+
}
|
|
129
|
+
else if (sub.options.length > 0) {
|
|
130
|
+
const optSpecs = sub.options.map(o => `'${o}'`).join(' ');
|
|
131
|
+
lines.push(`${indent} _arguments ${optSpecs}`);
|
|
132
|
+
}
|
|
133
|
+
lines.push(`${indent} ;;`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
lines.push(`${indent} esac`);
|
|
137
|
+
lines.push(`${indent} fi`);
|
|
138
|
+
}
|
|
139
|
+
else if (node.options.length > 0) {
|
|
140
|
+
const optSpecs = node.options.map(o => `'${o}'`).join(' ');
|
|
141
|
+
lines.push(`${indent} _arguments ${optSpecs}`);
|
|
142
|
+
}
|
|
143
|
+
lines.push(`${indent} ;;`);
|
|
144
|
+
return lines.join('\n');
|
|
145
|
+
}
|
|
27
146
|
export function generateZshCompletion(program) {
|
|
28
|
-
const
|
|
147
|
+
const tree = collectCommandTree(program);
|
|
148
|
+
const topLevelArray = zshDescribeArray(tree, 'toplevel', ' ');
|
|
149
|
+
const caseArms = tree.map(node => zshCaseArm(node, ' ')).join('\n');
|
|
29
150
|
return `# Zsh completion for lsvault
|
|
30
151
|
# Add to ~/.zshrc: eval "$(lsvault completion zsh)"
|
|
31
152
|
_lsvault() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
153
|
+
${topLevelArray}
|
|
154
|
+
|
|
155
|
+
if (( CURRENT == 2 )); then
|
|
156
|
+
_describe 'command' toplevel
|
|
157
|
+
return
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
case "$words[2]" in
|
|
161
|
+
${caseArms}
|
|
162
|
+
esac
|
|
35
163
|
}
|
|
36
164
|
compdef _lsvault lsvault
|
|
37
165
|
`;
|
|
38
166
|
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Fish
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
/**
|
|
171
|
+
* Escape a string for use inside a fish completion -d '...' description.
|
|
172
|
+
* Fish uses single quotes and doesn't support backslash escapes within them,
|
|
173
|
+
* so we just strip single quotes entirely.
|
|
174
|
+
*/
|
|
175
|
+
function fishDesc(s) {
|
|
176
|
+
return s.replace(/'/g, '');
|
|
177
|
+
}
|
|
39
178
|
export function generateFishCompletion(program) {
|
|
40
|
-
const
|
|
41
|
-
|
|
179
|
+
const tree = collectCommandTree(program);
|
|
180
|
+
const lines = [];
|
|
181
|
+
// Helper: the names of all top-level commands (used in __fish_seen_subcommand_from guards)
|
|
182
|
+
const topLevelNames = tree.map(n => n.name).join(' ');
|
|
183
|
+
for (const node of tree) {
|
|
184
|
+
// Top-level command — only show when no subcommand has been entered yet
|
|
185
|
+
lines.push(`complete -c lsvault -n '__fish_use_subcommand' -a '${node.name}' -d '${fishDesc(node.description)}'`);
|
|
186
|
+
if (node.subcommands.length > 0) {
|
|
187
|
+
// Subcommand names — show when this top-level command is active and no sub-subcommand yet
|
|
188
|
+
for (const sub of node.subcommands) {
|
|
189
|
+
lines.push(`complete -c lsvault -n '__fish_seen_subcommand_from ${node.name}; and not __fish_seen_subcommand_from ${sub.subcommands.map(s => s.name).concat(node.subcommands.map(s => s.name)).join(' ')}' -a '${sub.name}' -d '${fishDesc(sub.description)}'`);
|
|
190
|
+
// Options for this subcommand — show when top-level AND subcommand are both seen
|
|
191
|
+
for (const opt of sub.options) {
|
|
192
|
+
lines.push(`complete -c lsvault -n '__fish_seen_subcommand_from ${node.name}; and __fish_seen_subcommand_from ${sub.name}' -a '${opt}'`);
|
|
193
|
+
}
|
|
194
|
+
// Third-level sub-subcommands
|
|
195
|
+
for (const deep of sub.subcommands) {
|
|
196
|
+
lines.push(`complete -c lsvault -n '__fish_seen_subcommand_from ${node.name}; and __fish_seen_subcommand_from ${sub.name}' -a '${deep.name}' -d '${fishDesc(deep.description)}'`);
|
|
197
|
+
for (const opt of deep.options) {
|
|
198
|
+
lines.push(`complete -c lsvault -n '__fish_seen_subcommand_from ${node.name}; and __fish_seen_subcommand_from ${deep.name}' -a '${opt}'`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Leaf top-level command — offer its own options when active
|
|
205
|
+
for (const opt of node.options) {
|
|
206
|
+
lines.push(`complete -c lsvault -n '__fish_seen_subcommand_from ${topLevelNames}' -f -a '${opt}'`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return lines.join('\n') + '\n';
|
|
42
211
|
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Registration
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
43
215
|
export function registerCompletionCommands(program) {
|
|
44
216
|
const completion = program.command('completion').description('Generate shell completion scripts');
|
|
45
217
|
completion.command('bash')
|
package/dist/commands/config.js
CHANGED
|
@@ -46,6 +46,7 @@ EXAMPLES
|
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
48
48
|
process.stdout.write(chalk.yellow(`Key "${key}" not set in profile "${profile}"`) + '\n');
|
|
49
|
+
process.exitCode = 1;
|
|
49
50
|
}
|
|
50
51
|
});
|
|
51
52
|
config
|
|
@@ -83,6 +84,13 @@ EXAMPLES
|
|
|
83
84
|
lsvault config use prod
|
|
84
85
|
lsvault config use dev`)
|
|
85
86
|
.action((name) => {
|
|
87
|
+
const profiles = listProfiles();
|
|
88
|
+
if (!profiles.includes(name)) {
|
|
89
|
+
process.stderr.write(chalk.red(`Profile '${name}' does not exist.`) + '\n');
|
|
90
|
+
process.stderr.write(chalk.dim('Available profiles: ' + (profiles.length ? profiles.join(', ') : 'none')) + '\n');
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
86
94
|
setActiveProfile(name);
|
|
87
95
|
process.stdout.write(chalk.green(`Active profile set to ${chalk.bold(name)}`) + '\n');
|
|
88
96
|
});
|
|
@@ -14,6 +14,7 @@ export function registerConnectorCommands(program) {
|
|
|
14
14
|
const out = createOutput(flags);
|
|
15
15
|
out.startSpinner('Fetching connectors...');
|
|
16
16
|
try {
|
|
17
|
+
out.debug('API: GET connectors');
|
|
17
18
|
if (_opts.vault)
|
|
18
19
|
_opts.vault = await resolveVaultId(String(_opts.vault));
|
|
19
20
|
const client = await getClientAsync();
|
|
@@ -205,12 +206,17 @@ VALID PROVIDERS
|
|
|
205
206
|
const out = createOutput(flags);
|
|
206
207
|
out.startSpinner('Triggering sync...');
|
|
207
208
|
try {
|
|
209
|
+
out.debug(`API: POST connectors/${connectorId}/sync`);
|
|
208
210
|
const client = await getClientAsync();
|
|
209
211
|
const result = await client.connectors.sync(connectorId);
|
|
210
212
|
out.success(result.message, { message: result.message });
|
|
211
213
|
}
|
|
212
214
|
catch (err) {
|
|
213
215
|
handleError(out, err, 'Failed to trigger sync');
|
|
216
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
217
|
+
if (/oauth|token|not authorized|unauthorized/i.test(msg)) {
|
|
218
|
+
out.status(chalk.dim('Hint: Ensure the connector has valid OAuth credentials. Run "lsvault connectors create" to set up.'));
|
|
219
|
+
}
|
|
214
220
|
}
|
|
215
221
|
});
|
|
216
222
|
addGlobalFlags(connectors.command('logs')
|
|
@@ -106,8 +106,9 @@ export function registerCustomDomainCommands(program) {
|
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
108
|
addGlobalFlags(domains.command('verify')
|
|
109
|
-
.description('
|
|
110
|
-
.argument('<domainId>', 'Domain ID')
|
|
109
|
+
.description('Trigger DNS verification check for a custom domain')
|
|
110
|
+
.argument('<domainId>', 'Domain ID')
|
|
111
|
+
.addHelpText('after', '\n Submits a verification request to the server, which checks your DNS TXT record.\n Use "check" to read the current verification status without triggering a new check.'))
|
|
111
112
|
.action(async (domainId, _opts) => {
|
|
112
113
|
const flags = resolveFlags(_opts);
|
|
113
114
|
const out = createOutput(flags);
|
|
@@ -122,8 +123,9 @@ export function registerCustomDomainCommands(program) {
|
|
|
122
123
|
}
|
|
123
124
|
});
|
|
124
125
|
addGlobalFlags(domains.command('check')
|
|
125
|
-
.description('
|
|
126
|
-
.argument('<domainId>', 'Domain ID')
|
|
126
|
+
.description('Show current DNS verification status for a custom domain')
|
|
127
|
+
.argument('<domainId>', 'Domain ID')
|
|
128
|
+
.addHelpText('after', '\n Returns the current verification status without triggering a new check.\n Use "verify" to submit a fresh DNS verification request to the server.'))
|
|
127
129
|
.action(async (domainId, _opts) => {
|
|
128
130
|
const flags = resolveFlags(_opts);
|
|
129
131
|
const out = createOutput(flags);
|
package/dist/commands/docs.js
CHANGED
|
@@ -165,9 +165,11 @@ EXAMPLES
|
|
|
165
165
|
else {
|
|
166
166
|
doc = await client.documents.put(vaultId, docPath, content);
|
|
167
167
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
const docPath2 = doc.path ?? docPath;
|
|
169
|
+
const size = doc.sizeBytes ?? Buffer.byteLength(content, 'utf8');
|
|
170
|
+
out.success(`Document saved: ${chalk.cyan(docPath2)} (${size} bytes)`, {
|
|
171
|
+
path: docPath2,
|
|
172
|
+
sizeBytes: size,
|
|
171
173
|
encrypted: doc.encrypted ?? false,
|
|
172
174
|
});
|
|
173
175
|
}
|
package/dist/commands/links.js
CHANGED
|
@@ -22,16 +22,16 @@ export function registerLinkCommands(program) {
|
|
|
22
22
|
out.list(linkList.map(link => ({
|
|
23
23
|
targetPath: link.targetPath,
|
|
24
24
|
linkText: link.linkText,
|
|
25
|
-
|
|
25
|
+
isResolved: link.isResolved,
|
|
26
26
|
})), {
|
|
27
27
|
emptyMessage: 'No forward links found.',
|
|
28
28
|
columns: [
|
|
29
29
|
{ key: 'targetPath', header: 'Target' },
|
|
30
30
|
{ key: 'linkText', header: 'Link Text' },
|
|
31
|
-
{ key: '
|
|
31
|
+
{ key: 'isResolved', header: 'Resolved' },
|
|
32
32
|
],
|
|
33
33
|
textFn: (link) => {
|
|
34
|
-
const resolved = link.
|
|
34
|
+
const resolved = link.isResolved ? chalk.green('✓') : chalk.red('✗');
|
|
35
35
|
return ` ${resolved} [[${String(link.linkText)}]] → ${String(link.targetPath)}`;
|
|
36
36
|
},
|
|
37
37
|
});
|
|
@@ -55,18 +55,23 @@ export function registerLinkCommands(program) {
|
|
|
55
55
|
const backlinks = await client.documents.getBacklinks(vaultId, docPath);
|
|
56
56
|
out.stopSpinner();
|
|
57
57
|
out.list(backlinks.map(bl => ({
|
|
58
|
-
|
|
58
|
+
sourcePath: bl.sourceDocument.path,
|
|
59
|
+
sourceTitle: bl.sourceDocument.title || null,
|
|
59
60
|
linkText: bl.linkText,
|
|
60
61
|
context: bl.contextSnippet || '',
|
|
61
62
|
})), {
|
|
62
63
|
emptyMessage: 'No backlinks found.',
|
|
63
64
|
columns: [
|
|
64
|
-
{ key: '
|
|
65
|
+
{ key: 'sourcePath', header: 'Source Path' },
|
|
66
|
+
{ key: 'sourceTitle', header: 'Source Title' },
|
|
65
67
|
{ key: 'linkText', header: 'Link Text' },
|
|
66
68
|
{ key: 'context', header: 'Context' },
|
|
67
69
|
],
|
|
68
70
|
textFn: (bl) => {
|
|
69
|
-
const
|
|
71
|
+
const displayName = bl.sourceTitle ? String(bl.sourceTitle) : String(bl.sourcePath);
|
|
72
|
+
const lines = [chalk.cyan(` ${displayName}`)];
|
|
73
|
+
if (bl.sourceTitle)
|
|
74
|
+
lines.push(` Path: ${String(bl.sourcePath)}`);
|
|
70
75
|
lines.push(` Link: [[${String(bl.linkText)}]]`);
|
|
71
76
|
if (bl.context)
|
|
72
77
|
lines.push(` Context: ...${String(bl.context)}...`);
|