@lifestreamdynamics/vault-cli 1.3.8 → 1.3.10
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/ai.js +29 -1
- package/dist/commands/audit.js +25 -4
- package/dist/commands/auth.js +38 -30
- package/dist/commands/booking.js +1 -1
- package/dist/commands/calendar.js +4 -5
- package/dist/commands/config.js +7 -0
- package/dist/commands/connectors.js +6 -0
- package/dist/commands/links.js +30 -2
- package/dist/commands/publish.js +10 -3
- package/dist/commands/saml.js +10 -3
- package/dist/commands/teams.js +20 -5
- package/dist/commands/vaults.js +7 -1
- package/dist/commands/versions.js +27 -1
- package/dist/utils/output.js +2 -4
- package/package.json +1 -1
package/dist/commands/ai.js
CHANGED
|
@@ -5,6 +5,30 @@ import { createOutput, handleError } from '../utils/output.js';
|
|
|
5
5
|
export function registerAiCommands(program) {
|
|
6
6
|
const ai = program.command('ai').description('AI chat and document summarization');
|
|
7
7
|
const sessions = ai.command('sessions').description('AI chat session management');
|
|
8
|
+
addGlobalFlags(sessions.command('create')
|
|
9
|
+
.description('Create a new AI chat session')
|
|
10
|
+
.option('--title <title>', 'Session title')
|
|
11
|
+
.option('--vault <vaultId>', 'Vault ID to scope the session'))
|
|
12
|
+
.action(async (_opts) => {
|
|
13
|
+
const flags = resolveFlags(_opts);
|
|
14
|
+
const out = createOutput(flags);
|
|
15
|
+
out.startSpinner('Creating AI session...');
|
|
16
|
+
try {
|
|
17
|
+
const client = await getClientAsync();
|
|
18
|
+
const session = await client.ai.createSession({
|
|
19
|
+
title: _opts.title ? String(_opts.title) : undefined,
|
|
20
|
+
vaultId: _opts.vault ? String(_opts.vault) : undefined,
|
|
21
|
+
});
|
|
22
|
+
out.success(`Session created: ${session.id}`, {
|
|
23
|
+
id: session.id,
|
|
24
|
+
title: session.title ?? 'Untitled',
|
|
25
|
+
vaultId: session.vaultId ?? null,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
handleError(out, err, 'Failed to create AI session');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
8
32
|
addGlobalFlags(sessions.command('list')
|
|
9
33
|
.description('List AI chat sessions'))
|
|
10
34
|
.action(async (_opts) => {
|
|
@@ -80,7 +104,11 @@ export function registerAiCommands(program) {
|
|
|
80
104
|
addGlobalFlags(ai.command('chat')
|
|
81
105
|
.description('Send a message in an AI chat session')
|
|
82
106
|
.argument('<sessionId>', 'Session ID')
|
|
83
|
-
.argument('<message>', 'Message to send')
|
|
107
|
+
.argument('<message>', 'Message to send')
|
|
108
|
+
.addHelpText('after', `
|
|
109
|
+
EXAMPLES
|
|
110
|
+
lsvault ai chat <session-id> "What are the key points in my notes?"
|
|
111
|
+
lsvault ai chat <session-id> "Summarize recent changes" -o json`))
|
|
84
112
|
.action(async (sessionId, message, _opts) => {
|
|
85
113
|
const flags = resolveFlags(_opts);
|
|
86
114
|
const out = createOutput(flags);
|
package/dist/commands/audit.js
CHANGED
|
@@ -43,6 +43,7 @@ EXAMPLES
|
|
|
43
43
|
const logPath = String(_opts.logPath || DEFAULT_LOG_PATH);
|
|
44
44
|
if (!fs.existsSync(logPath)) {
|
|
45
45
|
out.warn(`Audit log file not found at ${logPath}`);
|
|
46
|
+
process.exitCode = 1;
|
|
46
47
|
if (flags.output === 'json') {
|
|
47
48
|
process.stdout.write('[]\n');
|
|
48
49
|
}
|
|
@@ -90,8 +91,8 @@ EXAMPLES
|
|
|
90
91
|
}
|
|
91
92
|
});
|
|
92
93
|
addGlobalFlags(audit.command('export')
|
|
93
|
-
.description('Export audit log entries to a CSV file or stdout')
|
|
94
|
-
.option('--format <format>', 'Export format (csv)', 'csv')
|
|
94
|
+
.description('Export audit log entries to a CSV or JSON file or stdout')
|
|
95
|
+
.option('--format <format>', 'Export format (csv or json)', 'csv')
|
|
95
96
|
.option('--file <file>', 'Output file path')
|
|
96
97
|
.option('--status <code>', 'Filter by HTTP status code', parseInt)
|
|
97
98
|
.option('--since <date>', 'Show entries since date (ISO 8601)')
|
|
@@ -101,8 +102,8 @@ EXAMPLES
|
|
|
101
102
|
const flags = resolveFlags(_opts);
|
|
102
103
|
const out = createOutput(flags);
|
|
103
104
|
try {
|
|
104
|
-
if (_opts.format !== 'csv') {
|
|
105
|
-
out.error(`Unsupported format: ${String(_opts.format)}.
|
|
105
|
+
if (_opts.format !== 'csv' && _opts.format !== 'json') {
|
|
106
|
+
out.error(`Unsupported format: ${String(_opts.format)}. Supported: csv, json`);
|
|
106
107
|
process.exitCode = 2;
|
|
107
108
|
return;
|
|
108
109
|
}
|
|
@@ -140,6 +141,26 @@ EXAMPLES
|
|
|
140
141
|
out.status('No audit log entries to export.');
|
|
141
142
|
return;
|
|
142
143
|
}
|
|
144
|
+
if (_opts.format === 'json') {
|
|
145
|
+
const jsonOutput = JSON.stringify(entries, null, 2);
|
|
146
|
+
if (_opts.file) {
|
|
147
|
+
const outputPath = String(_opts.file);
|
|
148
|
+
const outputDir = path.dirname(outputPath);
|
|
149
|
+
if (!fs.existsSync(outputDir)) {
|
|
150
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
151
|
+
}
|
|
152
|
+
fs.writeFileSync(outputPath, jsonOutput, 'utf-8');
|
|
153
|
+
out.success(`Exported ${entries.length} entries to ${outputPath}`, {
|
|
154
|
+
entries: entries.length,
|
|
155
|
+
path: outputPath,
|
|
156
|
+
format: 'json',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
out.raw(jsonOutput + '\n');
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
143
164
|
const csv = logger.exportCsv(entries);
|
|
144
165
|
if (_opts.file) {
|
|
145
166
|
const outputPath = String(_opts.file);
|
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');
|
|
@@ -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');
|
package/dist/commands/config.js
CHANGED
|
@@ -84,6 +84,13 @@ EXAMPLES
|
|
|
84
84
|
lsvault config use prod
|
|
85
85
|
lsvault config use dev`)
|
|
86
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
|
+
}
|
|
87
94
|
setActiveProfile(name);
|
|
88
95
|
process.stdout.write(chalk.green(`Active profile set to ${chalk.bold(name)}`) + '\n');
|
|
89
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')
|
package/dist/commands/links.js
CHANGED
|
@@ -100,9 +100,37 @@ export function registerLinkCommands(program) {
|
|
|
100
100
|
process.stdout.write(JSON.stringify({ nodes: graph.nodes, edges: graph.edges }) + '\n');
|
|
101
101
|
}
|
|
102
102
|
else {
|
|
103
|
-
process.stdout.write(chalk.bold(`Nodes: ${graph.nodes.length} Edges: ${graph.edges.length}\n`));
|
|
103
|
+
process.stdout.write(chalk.bold(`Nodes: ${graph.nodes.length} Edges: ${graph.edges.length}\n\n`));
|
|
104
|
+
// Most connected nodes (top 5)
|
|
105
|
+
const connectionCounts = new Map();
|
|
104
106
|
for (const node of graph.nodes) {
|
|
105
|
-
|
|
107
|
+
connectionCounts.set(node.id, 0);
|
|
108
|
+
}
|
|
109
|
+
for (const edge of graph.edges) {
|
|
110
|
+
connectionCounts.set(edge.source, (connectionCounts.get(edge.source) ?? 0) + 1);
|
|
111
|
+
connectionCounts.set(edge.target, (connectionCounts.get(edge.target) ?? 0) + 1);
|
|
112
|
+
}
|
|
113
|
+
const sorted = [...connectionCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
114
|
+
const topConnected = sorted.slice(0, 5).filter(([, count]) => count > 0);
|
|
115
|
+
if (topConnected.length > 0) {
|
|
116
|
+
process.stdout.write(chalk.bold('Most connected:\n'));
|
|
117
|
+
for (const [nodeId, count] of topConnected) {
|
|
118
|
+
const node = graph.nodes.find((n) => n.id === nodeId);
|
|
119
|
+
process.stdout.write(` ${chalk.cyan(String(node?.path ?? nodeId))} (${count} links)\n`);
|
|
120
|
+
}
|
|
121
|
+
process.stdout.write('\n');
|
|
122
|
+
}
|
|
123
|
+
// Orphan nodes (no connections)
|
|
124
|
+
const orphans = sorted.filter(([, count]) => count === 0);
|
|
125
|
+
if (orphans.length > 0) {
|
|
126
|
+
process.stdout.write(chalk.bold(`Orphan nodes (${orphans.length}):\n`));
|
|
127
|
+
for (const [nodeId] of orphans.slice(0, 10)) {
|
|
128
|
+
const node = graph.nodes.find((n) => n.id === nodeId);
|
|
129
|
+
process.stdout.write(` ${chalk.dim(String(node?.path ?? nodeId))}\n`);
|
|
130
|
+
}
|
|
131
|
+
if (orphans.length > 10) {
|
|
132
|
+
process.stdout.write(chalk.dim(` ... and ${orphans.length - 10} more\n`));
|
|
133
|
+
}
|
|
106
134
|
}
|
|
107
135
|
}
|
|
108
136
|
}
|
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,
|
|
@@ -160,13 +164,16 @@ export function registerPublishCommands(program) {
|
|
|
160
164
|
const result = await client.publish.getSubdomain(vaultId);
|
|
161
165
|
out.stopSpinner();
|
|
162
166
|
if (flags.output === 'json') {
|
|
163
|
-
out.
|
|
167
|
+
out.raw(JSON.stringify(result, null, 2) + '\n');
|
|
164
168
|
}
|
|
165
169
|
else if (result.subdomain == null) {
|
|
166
170
|
out.status('No subdomain configured.');
|
|
167
171
|
}
|
|
168
172
|
else {
|
|
169
|
-
out.record({
|
|
173
|
+
out.record({
|
|
174
|
+
subdomain: result.subdomain,
|
|
175
|
+
url: `https://${result.subdomain}.lifestreamdynamics.com`,
|
|
176
|
+
});
|
|
170
177
|
}
|
|
171
178
|
}
|
|
172
179
|
catch (err) {
|
package/dist/commands/saml.js
CHANGED
|
@@ -156,12 +156,14 @@ export function registerSamlCommands(program) {
|
|
|
156
156
|
addGlobalFlags(saml.command('delete-config')
|
|
157
157
|
.description('Delete an SSO configuration')
|
|
158
158
|
.argument('<id>', 'SSO config ID')
|
|
159
|
-
.option('--force', 'Skip confirmation prompt')
|
|
159
|
+
.option('--force', 'Skip confirmation prompt')
|
|
160
|
+
.option('-y, --yes', 'Alias for --force'))
|
|
160
161
|
.action(async (id, _opts) => {
|
|
161
162
|
const flags = resolveFlags(_opts);
|
|
162
163
|
const out = createOutput(flags);
|
|
163
|
-
if (!_opts.force) {
|
|
164
|
-
out.
|
|
164
|
+
if (!_opts.force && !_opts.yes) {
|
|
165
|
+
out.warn(`Pass --force to delete SSO config ${id}.`);
|
|
166
|
+
process.exitCode = 1;
|
|
165
167
|
return;
|
|
166
168
|
}
|
|
167
169
|
out.startSpinner('Deleting SSO config...');
|
|
@@ -205,6 +207,11 @@ export function registerSamlCommands(program) {
|
|
|
205
207
|
.action(async (slug, _opts) => {
|
|
206
208
|
const flags = resolveFlags(_opts);
|
|
207
209
|
const out = createOutput(flags);
|
|
210
|
+
if (!slug || !slug.trim()) {
|
|
211
|
+
out.error('Slug cannot be empty.');
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
208
215
|
try {
|
|
209
216
|
const client = await getClientAsync();
|
|
210
217
|
const url = client.saml.getLoginUrl(slug);
|
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/vaults.js
CHANGED
|
@@ -16,11 +16,13 @@ export function registerVaultCommands(program) {
|
|
|
16
16
|
const out = createOutput(flags);
|
|
17
17
|
out.startSpinner('Fetching vaults...');
|
|
18
18
|
try {
|
|
19
|
+
out.debug('API: GET vaults');
|
|
19
20
|
const client = await getClientAsync();
|
|
20
21
|
const vaultList = await client.vaults.list({
|
|
21
22
|
includeArchived: _opts.includeArchived === true,
|
|
22
23
|
});
|
|
23
24
|
out.stopSpinner();
|
|
25
|
+
out.debug(`Response: ${vaultList.length} vaults`);
|
|
24
26
|
out.list(vaultList.map(v => ({ name: v.name, slug: v.slug, encrypted: v.encryptionEnabled ? 'yes' : 'no', description: v.description ?? null, id: v.id })), {
|
|
25
27
|
emptyMessage: 'No vaults found.',
|
|
26
28
|
columns: [
|
|
@@ -167,10 +169,12 @@ EXAMPLES
|
|
|
167
169
|
// vault tree
|
|
168
170
|
addGlobalFlags(vaults.command('tree')
|
|
169
171
|
.description('Show vault file tree')
|
|
170
|
-
.argument('<vaultId>', 'Vault ID or slug')
|
|
172
|
+
.argument('<vaultId>', 'Vault ID or slug')
|
|
173
|
+
.option('--depth <n>', 'Maximum display depth (0 = root only)', parseInt))
|
|
171
174
|
.action(async (vaultId, _opts) => {
|
|
172
175
|
const flags = resolveFlags(_opts);
|
|
173
176
|
const out = createOutput(flags);
|
|
177
|
+
const maxDepth = _opts.depth;
|
|
174
178
|
out.startSpinner('Fetching vault tree...');
|
|
175
179
|
try {
|
|
176
180
|
vaultId = await resolveVaultId(vaultId);
|
|
@@ -182,6 +186,8 @@ EXAMPLES
|
|
|
182
186
|
}
|
|
183
187
|
else {
|
|
184
188
|
function printNode(node, depth) {
|
|
189
|
+
if (maxDepth !== undefined && depth > maxDepth)
|
|
190
|
+
return;
|
|
185
191
|
const indent = ' '.repeat(depth);
|
|
186
192
|
const icon = node.type === 'directory' ? chalk.yellow('📁') : chalk.cyan('📄');
|
|
187
193
|
process.stdout.write(`${indent}${icon} ${node.name}\n`);
|
|
@@ -146,7 +146,8 @@ EXAMPLES
|
|
|
146
146
|
.argument('<version>', 'Version number to restore')
|
|
147
147
|
.addHelpText('after', `
|
|
148
148
|
EXAMPLES
|
|
149
|
-
lsvault versions restore abc123 notes/todo.md 2
|
|
149
|
+
lsvault versions restore abc123 notes/todo.md 2
|
|
150
|
+
lsvault versions restore abc123 notes/todo.md 2 --dry-run`))
|
|
150
151
|
.action(async (vaultId, docPath, versionStr, _opts) => {
|
|
151
152
|
const flags = resolveFlags(_opts);
|
|
152
153
|
const out = createOutput(flags);
|
|
@@ -156,6 +157,31 @@ EXAMPLES
|
|
|
156
157
|
process.exitCode = 1;
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
160
|
+
if (flags.dryRun) {
|
|
161
|
+
out.startSpinner(`Fetching version ${versionNum} preview...`);
|
|
162
|
+
try {
|
|
163
|
+
vaultId = await resolveVaultId(vaultId);
|
|
164
|
+
const client = await getClientAsync();
|
|
165
|
+
const version = await client.documents.getVersion(vaultId, docPath, versionNum);
|
|
166
|
+
out.stopSpinner();
|
|
167
|
+
if (flags.output === 'json') {
|
|
168
|
+
out.raw(JSON.stringify({ dryRun: true, version: { version: version.versionNum, createdAt: version.createdAt, size: version.content?.length ?? 0 } }, null, 2) + '\n');
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
process.stdout.write(chalk.bold('Dry run — no changes made\n\n'));
|
|
172
|
+
process.stdout.write(`Version: ${version.versionNum}\n`);
|
|
173
|
+
process.stdout.write(`Created: ${version.createdAt}\n`);
|
|
174
|
+
if (version.content) {
|
|
175
|
+
const preview = version.content.slice(0, 200);
|
|
176
|
+
process.stdout.write(`Content preview:\n${chalk.dim(preview)}${version.content.length > 200 ? '...' : ''}\n`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
handleError(out, err, 'Failed to preview version');
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
159
185
|
out.startSpinner(`Restoring to version ${versionNum}...`);
|
|
160
186
|
try {
|
|
161
187
|
vaultId = await resolveVaultId(vaultId);
|
package/dist/utils/output.js
CHANGED
|
@@ -118,9 +118,9 @@ export class Output {
|
|
|
118
118
|
* - table: prints an ASCII table
|
|
119
119
|
*/
|
|
120
120
|
list(data, options) {
|
|
121
|
+
if (this.flags.quiet)
|
|
122
|
+
return;
|
|
121
123
|
if (data.length === 0) {
|
|
122
|
-
if (this.flags.quiet)
|
|
123
|
-
return;
|
|
124
124
|
if (this.flags.output === 'json') {
|
|
125
125
|
process.stdout.write('[]\n');
|
|
126
126
|
return;
|
|
@@ -133,8 +133,6 @@ export class Output {
|
|
|
133
133
|
}
|
|
134
134
|
return;
|
|
135
135
|
}
|
|
136
|
-
if (this.flags.quiet)
|
|
137
|
-
return;
|
|
138
136
|
switch (this.flags.output) {
|
|
139
137
|
case 'json':
|
|
140
138
|
process.stdout.write(JSON.stringify(data) + '\n');
|