@lifestreamdynamics/vault-cli 1.0.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/LICENSE +21 -0
- package/README.md +759 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.js +79 -0
- package/dist/commands/admin.d.ts +2 -0
- package/dist/commands/admin.js +263 -0
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +119 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +256 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +130 -0
- package/dist/commands/connectors.d.ts +2 -0
- package/dist/commands/connectors.js +224 -0
- package/dist/commands/docs.d.ts +2 -0
- package/dist/commands/docs.js +194 -0
- package/dist/commands/hooks.d.ts +2 -0
- package/dist/commands/hooks.js +159 -0
- package/dist/commands/keys.d.ts +2 -0
- package/dist/commands/keys.js +165 -0
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +138 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +61 -0
- package/dist/commands/shares.d.ts +2 -0
- package/dist/commands/shares.js +121 -0
- package/dist/commands/subscription.d.ts +2 -0
- package/dist/commands/subscription.js +166 -0
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +565 -0
- package/dist/commands/teams.d.ts +2 -0
- package/dist/commands/teams.js +322 -0
- package/dist/commands/user.d.ts +2 -0
- package/dist/commands/user.js +48 -0
- package/dist/commands/vaults.d.ts +2 -0
- package/dist/commands/vaults.js +157 -0
- package/dist/commands/versions.d.ts +2 -0
- package/dist/commands/versions.js +219 -0
- package/dist/commands/webhooks.d.ts +2 -0
- package/dist/commands/webhooks.js +181 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +88 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +63 -0
- package/dist/lib/credential-manager.d.ts +48 -0
- package/dist/lib/credential-manager.js +101 -0
- package/dist/lib/encrypted-config.d.ts +20 -0
- package/dist/lib/encrypted-config.js +102 -0
- package/dist/lib/keychain.d.ts +8 -0
- package/dist/lib/keychain.js +82 -0
- package/dist/lib/migration.d.ts +31 -0
- package/dist/lib/migration.js +92 -0
- package/dist/lib/profiles.d.ts +43 -0
- package/dist/lib/profiles.js +104 -0
- package/dist/sync/config.d.ts +32 -0
- package/dist/sync/config.js +100 -0
- package/dist/sync/conflict.d.ts +30 -0
- package/dist/sync/conflict.js +60 -0
- package/dist/sync/daemon-worker.d.ts +1 -0
- package/dist/sync/daemon-worker.js +128 -0
- package/dist/sync/daemon.d.ts +44 -0
- package/dist/sync/daemon.js +174 -0
- package/dist/sync/diff.d.ts +43 -0
- package/dist/sync/diff.js +166 -0
- package/dist/sync/engine.d.ts +41 -0
- package/dist/sync/engine.js +233 -0
- package/dist/sync/ignore.d.ts +16 -0
- package/dist/sync/ignore.js +72 -0
- package/dist/sync/remote-poller.d.ts +23 -0
- package/dist/sync/remote-poller.js +145 -0
- package/dist/sync/state.d.ts +32 -0
- package/dist/sync/state.js +98 -0
- package/dist/sync/types.d.ts +68 -0
- package/dist/sync/types.js +4 -0
- package/dist/sync/watcher.d.ts +23 -0
- package/dist/sync/watcher.js +207 -0
- package/dist/utils/flags.d.ts +18 -0
- package/dist/utils/flags.js +31 -0
- package/dist/utils/format.d.ts +2 -0
- package/dist/utils/format.js +22 -0
- package/dist/utils/output.d.ts +87 -0
- package/dist/utils/output.js +229 -0
- package/package.json +62 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getClient } from '../client.js';
|
|
3
|
+
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
|
+
import { createOutput, handleError } from '../utils/output.js';
|
|
5
|
+
export function registerTeamCommands(program) {
|
|
6
|
+
const teams = program.command('teams').description('Manage teams, members, invitations, and shared vaults');
|
|
7
|
+
// ── Team CRUD ──────────────────────────────────────────────────────
|
|
8
|
+
addGlobalFlags(teams.command('list')
|
|
9
|
+
.description('List all teams you belong to'))
|
|
10
|
+
.action(async (_opts) => {
|
|
11
|
+
const flags = resolveFlags(_opts);
|
|
12
|
+
const out = createOutput(flags);
|
|
13
|
+
out.startSpinner('Fetching teams...');
|
|
14
|
+
try {
|
|
15
|
+
const client = getClient();
|
|
16
|
+
const teamList = await client.teams.list();
|
|
17
|
+
out.stopSpinner();
|
|
18
|
+
out.list(teamList.map(t => ({ name: t.name, id: t.id, description: t.description || 'No description' })), {
|
|
19
|
+
emptyMessage: 'No teams found.',
|
|
20
|
+
columns: [
|
|
21
|
+
{ key: 'name', header: 'Name' },
|
|
22
|
+
{ key: 'id', header: 'ID' },
|
|
23
|
+
{ key: 'description', header: 'Description' },
|
|
24
|
+
],
|
|
25
|
+
textFn: (t) => `${chalk.cyan(String(t.name))} ${chalk.dim(`(${String(t.id)})`)} -- ${String(t.description)}`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
handleError(out, err, 'Failed to fetch teams');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
addGlobalFlags(teams.command('get')
|
|
33
|
+
.description('Show detailed information about a team')
|
|
34
|
+
.argument('<teamId>', 'Team ID'))
|
|
35
|
+
.action(async (teamId, _opts) => {
|
|
36
|
+
const flags = resolveFlags(_opts);
|
|
37
|
+
const out = createOutput(flags);
|
|
38
|
+
out.startSpinner('Fetching team...');
|
|
39
|
+
try {
|
|
40
|
+
const client = getClient();
|
|
41
|
+
const team = await client.teams.get(teamId);
|
|
42
|
+
out.stopSpinner();
|
|
43
|
+
out.record({
|
|
44
|
+
name: team.name,
|
|
45
|
+
id: team.id,
|
|
46
|
+
ownerId: team.ownerId,
|
|
47
|
+
description: team.description,
|
|
48
|
+
createdAt: team.createdAt,
|
|
49
|
+
updatedAt: team.updatedAt,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
handleError(out, err, 'Failed to fetch team');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
addGlobalFlags(teams.command('create')
|
|
57
|
+
.description('Create a new team workspace')
|
|
58
|
+
.argument('<name>', 'Team name')
|
|
59
|
+
.option('-d, --description <desc>', 'Team description')
|
|
60
|
+
.addHelpText('after', `
|
|
61
|
+
EXAMPLES
|
|
62
|
+
lsvault teams create "Engineering" --description "Dev team workspace"
|
|
63
|
+
lsvault teams create "Marketing"`))
|
|
64
|
+
.action(async (name, _opts) => {
|
|
65
|
+
const flags = resolveFlags(_opts);
|
|
66
|
+
const out = createOutput(flags);
|
|
67
|
+
out.startSpinner('Creating team...');
|
|
68
|
+
try {
|
|
69
|
+
const client = getClient();
|
|
70
|
+
const team = await client.teams.create({ name, description: _opts.description });
|
|
71
|
+
out.success(`Team created: ${chalk.cyan(team.name)} (${team.id})`, { id: team.id, name: team.name });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
handleError(out, err, 'Failed to create team');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
addGlobalFlags(teams.command('update')
|
|
78
|
+
.description('Update a team')
|
|
79
|
+
.argument('<teamId>', 'Team ID')
|
|
80
|
+
.option('-n, --name <name>', 'New team name')
|
|
81
|
+
.option('-d, --description <desc>', 'New description'))
|
|
82
|
+
.action(async (teamId, _opts) => {
|
|
83
|
+
const flags = resolveFlags(_opts);
|
|
84
|
+
const out = createOutput(flags);
|
|
85
|
+
out.startSpinner('Updating team...');
|
|
86
|
+
try {
|
|
87
|
+
const client = getClient();
|
|
88
|
+
const team = await client.teams.update(teamId, {
|
|
89
|
+
name: _opts.name,
|
|
90
|
+
description: _opts.description,
|
|
91
|
+
});
|
|
92
|
+
out.success(`Team updated: ${chalk.cyan(team.name)}`, { id: team.id, name: team.name });
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
handleError(out, err, 'Failed to update team');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
addGlobalFlags(teams.command('delete')
|
|
99
|
+
.description('Permanently delete a team and all its data')
|
|
100
|
+
.argument('<teamId>', 'Team ID'))
|
|
101
|
+
.action(async (teamId, _opts) => {
|
|
102
|
+
const flags = resolveFlags(_opts);
|
|
103
|
+
const out = createOutput(flags);
|
|
104
|
+
out.startSpinner('Deleting team...');
|
|
105
|
+
try {
|
|
106
|
+
const client = getClient();
|
|
107
|
+
await client.teams.delete(teamId);
|
|
108
|
+
out.success('Team deleted.', { id: teamId, deleted: true });
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
handleError(out, err, 'Failed to delete team');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// ── Members ────────────────────────────────────────────────────────
|
|
115
|
+
const members = teams.command('members').description('List, update roles, and remove team members');
|
|
116
|
+
addGlobalFlags(members.command('list')
|
|
117
|
+
.description('List team members')
|
|
118
|
+
.argument('<teamId>', 'Team ID'))
|
|
119
|
+
.action(async (teamId, _opts) => {
|
|
120
|
+
const flags = resolveFlags(_opts);
|
|
121
|
+
const out = createOutput(flags);
|
|
122
|
+
out.startSpinner('Fetching members...');
|
|
123
|
+
try {
|
|
124
|
+
const client = getClient();
|
|
125
|
+
const memberList = await client.teams.listMembers(teamId);
|
|
126
|
+
out.stopSpinner();
|
|
127
|
+
out.list(memberList.map(m => ({
|
|
128
|
+
name: m.user.name || m.user.email,
|
|
129
|
+
userId: m.userId,
|
|
130
|
+
role: m.role,
|
|
131
|
+
email: m.user.email,
|
|
132
|
+
})), {
|
|
133
|
+
emptyMessage: 'No members found.',
|
|
134
|
+
columns: [
|
|
135
|
+
{ key: 'name', header: 'Name' },
|
|
136
|
+
{ key: 'email', header: 'Email' },
|
|
137
|
+
{ key: 'role', header: 'Role' },
|
|
138
|
+
],
|
|
139
|
+
textFn: (m) => `${chalk.cyan(String(m.name))} ${chalk.dim(`(${String(m.userId)})`)} -- ${chalk.magenta(String(m.role))}`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
handleError(out, err, 'Failed to fetch members');
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
addGlobalFlags(members.command('update')
|
|
147
|
+
.description('Update a member role')
|
|
148
|
+
.argument('<teamId>', 'Team ID')
|
|
149
|
+
.argument('<userId>', 'User ID')
|
|
150
|
+
.requiredOption('-r, --role <role>', 'New role (admin or member)'))
|
|
151
|
+
.action(async (teamId, userId, _opts) => {
|
|
152
|
+
const flags = resolveFlags(_opts);
|
|
153
|
+
const out = createOutput(flags);
|
|
154
|
+
const role = String(_opts.role);
|
|
155
|
+
out.startSpinner('Updating member role...');
|
|
156
|
+
try {
|
|
157
|
+
const client = getClient();
|
|
158
|
+
const member = await client.teams.updateMemberRole(teamId, userId, role);
|
|
159
|
+
out.success(`Role updated to ${chalk.magenta(member.role)} for ${member.user.email}`, {
|
|
160
|
+
userId,
|
|
161
|
+
role: member.role,
|
|
162
|
+
email: member.user.email,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
handleError(out, err, 'Failed to update member role');
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
addGlobalFlags(members.command('remove')
|
|
170
|
+
.description('Remove a member from the team')
|
|
171
|
+
.argument('<teamId>', 'Team ID')
|
|
172
|
+
.argument('<userId>', 'User ID'))
|
|
173
|
+
.action(async (teamId, userId, _opts) => {
|
|
174
|
+
const flags = resolveFlags(_opts);
|
|
175
|
+
const out = createOutput(flags);
|
|
176
|
+
out.startSpinner('Removing member...');
|
|
177
|
+
try {
|
|
178
|
+
const client = getClient();
|
|
179
|
+
await client.teams.removeMember(teamId, userId);
|
|
180
|
+
out.success('Member removed.', { teamId, userId, removed: true });
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
handleError(out, err, 'Failed to remove member');
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
addGlobalFlags(teams.command('leave')
|
|
187
|
+
.description('Leave a team')
|
|
188
|
+
.argument('<teamId>', 'Team ID'))
|
|
189
|
+
.action(async (teamId, _opts) => {
|
|
190
|
+
const flags = resolveFlags(_opts);
|
|
191
|
+
const out = createOutput(flags);
|
|
192
|
+
out.startSpinner('Leaving team...');
|
|
193
|
+
try {
|
|
194
|
+
const client = getClient();
|
|
195
|
+
await client.teams.leave(teamId);
|
|
196
|
+
out.success('Left the team.', { teamId, left: true });
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
handleError(out, err, 'Failed to leave team');
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// ── Invitations ────────────────────────────────────────────────────
|
|
203
|
+
const invitations = teams.command('invitations').description('Send, list, and revoke team invitations');
|
|
204
|
+
addGlobalFlags(invitations.command('list')
|
|
205
|
+
.description('List pending invitations')
|
|
206
|
+
.argument('<teamId>', 'Team ID'))
|
|
207
|
+
.action(async (teamId, _opts) => {
|
|
208
|
+
const flags = resolveFlags(_opts);
|
|
209
|
+
const out = createOutput(flags);
|
|
210
|
+
out.startSpinner('Fetching invitations...');
|
|
211
|
+
try {
|
|
212
|
+
const client = getClient();
|
|
213
|
+
const invitationList = await client.teams.listInvitations(teamId);
|
|
214
|
+
out.stopSpinner();
|
|
215
|
+
out.list(invitationList.map(inv => ({
|
|
216
|
+
email: inv.email,
|
|
217
|
+
id: inv.id,
|
|
218
|
+
role: inv.role,
|
|
219
|
+
expiresAt: inv.expiresAt,
|
|
220
|
+
})), {
|
|
221
|
+
emptyMessage: 'No pending invitations.',
|
|
222
|
+
columns: [
|
|
223
|
+
{ key: 'email', header: 'Email' },
|
|
224
|
+
{ key: 'role', header: 'Role' },
|
|
225
|
+
{ key: 'expiresAt', header: 'Expires' },
|
|
226
|
+
],
|
|
227
|
+
textFn: (inv) => `${chalk.cyan(String(inv.email))} ${chalk.dim(`(${String(inv.id)})`)} -- ${chalk.magenta(String(inv.role))} -- expires ${String(inv.expiresAt)}`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
handleError(out, err, 'Failed to fetch invitations');
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
addGlobalFlags(invitations.command('create')
|
|
235
|
+
.description('Invite a user to the team')
|
|
236
|
+
.argument('<teamId>', 'Team ID')
|
|
237
|
+
.argument('<email>', 'Email address')
|
|
238
|
+
.requiredOption('-r, --role <role>', 'Role (admin or member)'))
|
|
239
|
+
.action(async (teamId, email, _opts) => {
|
|
240
|
+
const flags = resolveFlags(_opts);
|
|
241
|
+
const out = createOutput(flags);
|
|
242
|
+
const role = String(_opts.role);
|
|
243
|
+
out.startSpinner('Sending invitation...');
|
|
244
|
+
try {
|
|
245
|
+
const client = getClient();
|
|
246
|
+
const invitation = await client.teams.inviteMember(teamId, email, role);
|
|
247
|
+
out.success(`Invited ${chalk.cyan(invitation.email)} as ${chalk.magenta(invitation.role)}`, {
|
|
248
|
+
id: invitation.id,
|
|
249
|
+
email: invitation.email,
|
|
250
|
+
role: invitation.role,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
handleError(out, err, 'Failed to send invitation');
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
addGlobalFlags(invitations.command('revoke')
|
|
258
|
+
.description('Revoke a pending invitation')
|
|
259
|
+
.argument('<teamId>', 'Team ID')
|
|
260
|
+
.argument('<invitationId>', 'Invitation ID'))
|
|
261
|
+
.action(async (teamId, invitationId, _opts) => {
|
|
262
|
+
const flags = resolveFlags(_opts);
|
|
263
|
+
const out = createOutput(flags);
|
|
264
|
+
out.startSpinner('Revoking invitation...');
|
|
265
|
+
try {
|
|
266
|
+
const client = getClient();
|
|
267
|
+
await client.teams.revokeInvitation(teamId, invitationId);
|
|
268
|
+
out.success('Invitation revoked.', { id: invitationId, revoked: true });
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
handleError(out, err, 'Failed to revoke invitation');
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
// ── Team Vaults ────────────────────────────────────────────────────
|
|
275
|
+
const vaults = teams.command('vaults').description('Manage shared vaults within a team');
|
|
276
|
+
addGlobalFlags(vaults.command('list')
|
|
277
|
+
.description('List team vaults')
|
|
278
|
+
.argument('<teamId>', 'Team ID'))
|
|
279
|
+
.action(async (teamId, _opts) => {
|
|
280
|
+
const flags = resolveFlags(_opts);
|
|
281
|
+
const out = createOutput(flags);
|
|
282
|
+
out.startSpinner('Fetching team vaults...');
|
|
283
|
+
try {
|
|
284
|
+
const client = getClient();
|
|
285
|
+
const vaultList = await client.teams.listVaults(teamId);
|
|
286
|
+
out.stopSpinner();
|
|
287
|
+
out.list(vaultList.map(v => ({ name: String(v.name), slug: String(v.slug), description: String(v.description) || 'No description' })), {
|
|
288
|
+
emptyMessage: 'No team vaults found.',
|
|
289
|
+
columns: [
|
|
290
|
+
{ key: 'name', header: 'Name' },
|
|
291
|
+
{ key: 'slug', header: 'Slug' },
|
|
292
|
+
{ key: 'description', header: 'Description' },
|
|
293
|
+
],
|
|
294
|
+
textFn: (v) => `${chalk.cyan(String(v.name))} ${chalk.dim(`(${String(v.slug)})`)} -- ${String(v.description)}`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
handleError(out, err, 'Failed to fetch team vaults');
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
addGlobalFlags(vaults.command('create')
|
|
302
|
+
.description('Create a team vault')
|
|
303
|
+
.argument('<teamId>', 'Team ID')
|
|
304
|
+
.argument('<name>', 'Vault name')
|
|
305
|
+
.option('-d, --description <desc>', 'Description'))
|
|
306
|
+
.action(async (teamId, name, _opts) => {
|
|
307
|
+
const flags = resolveFlags(_opts);
|
|
308
|
+
const out = createOutput(flags);
|
|
309
|
+
out.startSpinner('Creating team vault...');
|
|
310
|
+
try {
|
|
311
|
+
const client = getClient();
|
|
312
|
+
const vault = await client.teams.createVault(teamId, { name, description: _opts.description });
|
|
313
|
+
out.success(`Team vault created: ${chalk.cyan(String(vault.name))}`, {
|
|
314
|
+
name: String(vault.name),
|
|
315
|
+
slug: String(vault.slug),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
handleError(out, err, 'Failed to create team vault');
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getClient } from '../client.js';
|
|
3
|
+
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
|
+
import { createOutput, handleError } from '../utils/output.js';
|
|
5
|
+
import { formatBytes } from '../utils/format.js';
|
|
6
|
+
export function registerUserCommands(program) {
|
|
7
|
+
const user = program.command('user').description('View account details and storage usage');
|
|
8
|
+
addGlobalFlags(user.command('storage')
|
|
9
|
+
.description('Show storage usage breakdown by vault and plan limits'))
|
|
10
|
+
.action(async (_opts) => {
|
|
11
|
+
const flags = resolveFlags(_opts);
|
|
12
|
+
const out = createOutput(flags);
|
|
13
|
+
out.startSpinner('Fetching storage usage...');
|
|
14
|
+
try {
|
|
15
|
+
const client = getClient();
|
|
16
|
+
const storage = await client.user.getStorage();
|
|
17
|
+
out.stopSpinner();
|
|
18
|
+
if (flags.output === 'json') {
|
|
19
|
+
out.record({
|
|
20
|
+
tier: storage.tier,
|
|
21
|
+
totalBytes: storage.totalBytes,
|
|
22
|
+
limitBytes: storage.limitBytes,
|
|
23
|
+
vaultCount: storage.vaultCount,
|
|
24
|
+
vaultLimit: storage.vaultLimit,
|
|
25
|
+
vaults: storage.vaults,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const pct = storage.limitBytes > 0
|
|
30
|
+
? ((storage.totalBytes / storage.limitBytes) * 100).toFixed(1)
|
|
31
|
+
: '0.0';
|
|
32
|
+
process.stdout.write(`Plan: ${chalk.green(storage.tier)}\n`);
|
|
33
|
+
process.stdout.write(`Storage: ${formatBytes(storage.totalBytes)} / ${formatBytes(storage.limitBytes)} (${pct}%)\n`);
|
|
34
|
+
process.stdout.write(`Vaults: ${storage.vaultCount} / ${storage.vaultLimit}\n`);
|
|
35
|
+
if (storage.vaults.length > 0) {
|
|
36
|
+
process.stdout.write('\n');
|
|
37
|
+
process.stdout.write(chalk.dim('Per-vault breakdown:') + '\n');
|
|
38
|
+
for (const v of storage.vaults) {
|
|
39
|
+
process.stdout.write(` ${chalk.cyan(v.name)}: ${formatBytes(v.bytes)} (${v.documentCount} docs)\n`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
handleError(out, err, 'Failed to fetch storage usage');
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getClient } from '../client.js';
|
|
3
|
+
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
|
+
import { createOutput, handleError } from '../utils/output.js';
|
|
5
|
+
import { generateVaultKey } from '@lifestreamdynamics/vault-sdk';
|
|
6
|
+
import { createCredentialManager } from '../lib/credential-manager.js';
|
|
7
|
+
export function registerVaultCommands(program) {
|
|
8
|
+
const vaults = program.command('vaults').description('Create, list, and inspect document vaults');
|
|
9
|
+
addGlobalFlags(vaults.command('list')
|
|
10
|
+
.description('List all vaults accessible to the current user'))
|
|
11
|
+
.action(async (_opts) => {
|
|
12
|
+
const flags = resolveFlags(_opts);
|
|
13
|
+
const out = createOutput(flags);
|
|
14
|
+
out.startSpinner('Fetching vaults...');
|
|
15
|
+
try {
|
|
16
|
+
const client = getClient();
|
|
17
|
+
const vaultList = await client.vaults.list();
|
|
18
|
+
out.stopSpinner();
|
|
19
|
+
out.list(vaultList.map(v => ({ name: v.name, slug: v.slug, encrypted: v.encryptionEnabled ? 'yes' : 'no', description: v.description || 'No description', id: v.id })), {
|
|
20
|
+
emptyMessage: 'No vaults found.',
|
|
21
|
+
columns: [
|
|
22
|
+
{ key: 'name', header: 'Name' },
|
|
23
|
+
{ key: 'slug', header: 'Slug' },
|
|
24
|
+
{ key: 'encrypted', header: 'Encrypted' },
|
|
25
|
+
{ key: 'description', header: 'Description' },
|
|
26
|
+
{ key: 'id', header: 'ID' },
|
|
27
|
+
],
|
|
28
|
+
textFn: (v) => {
|
|
29
|
+
const encIcon = v.encrypted === 'yes' ? chalk.green(' [encrypted]') : '';
|
|
30
|
+
return `${chalk.cyan(String(v.name))} ${chalk.dim(`(${String(v.slug)})`)}${encIcon} -- ${String(v.description)}`;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
handleError(out, err, 'Failed to fetch vaults');
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
addGlobalFlags(vaults.command('get')
|
|
39
|
+
.description('Show detailed information about a vault')
|
|
40
|
+
.argument('<vaultId>', 'Vault ID or slug'))
|
|
41
|
+
.action(async (vaultId, _opts) => {
|
|
42
|
+
const flags = resolveFlags(_opts);
|
|
43
|
+
const out = createOutput(flags);
|
|
44
|
+
out.startSpinner('Fetching vault...');
|
|
45
|
+
try {
|
|
46
|
+
const client = getClient();
|
|
47
|
+
const vault = await client.vaults.get(vaultId);
|
|
48
|
+
out.stopSpinner();
|
|
49
|
+
out.record({
|
|
50
|
+
name: vault.name,
|
|
51
|
+
slug: vault.slug,
|
|
52
|
+
id: vault.id,
|
|
53
|
+
description: vault.description,
|
|
54
|
+
encrypted: vault.encryptionEnabled ? 'yes' : 'no',
|
|
55
|
+
createdAt: vault.createdAt,
|
|
56
|
+
updatedAt: vault.updatedAt,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
handleError(out, err, 'Failed to fetch vault');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
addGlobalFlags(vaults.command('create')
|
|
64
|
+
.description('Create a new vault')
|
|
65
|
+
.argument('<name>', 'Vault name (used to generate the URL slug)')
|
|
66
|
+
.option('-d, --description <desc>', 'Vault description')
|
|
67
|
+
.option('--encrypted', 'Enable client-side encryption (AES-256-GCM)')
|
|
68
|
+
.addHelpText('after', `
|
|
69
|
+
EXAMPLES
|
|
70
|
+
lsvault vaults create "My Notes"
|
|
71
|
+
lsvault vaults create "Work Journal" --description "Daily work log"
|
|
72
|
+
lsvault vaults create "Secrets" --encrypted`))
|
|
73
|
+
.action(async (name, _opts) => {
|
|
74
|
+
const flags = resolveFlags(_opts);
|
|
75
|
+
const out = createOutput(flags);
|
|
76
|
+
out.startSpinner('Creating vault...');
|
|
77
|
+
try {
|
|
78
|
+
const client = getClient();
|
|
79
|
+
const isEncrypted = _opts.encrypted === true;
|
|
80
|
+
const vault = await client.vaults.create({
|
|
81
|
+
name,
|
|
82
|
+
description: _opts.description,
|
|
83
|
+
encryptionEnabled: isEncrypted,
|
|
84
|
+
});
|
|
85
|
+
if (isEncrypted) {
|
|
86
|
+
const key = generateVaultKey();
|
|
87
|
+
const credManager = createCredentialManager();
|
|
88
|
+
await credManager.saveVaultKey(vault.id, key);
|
|
89
|
+
out.success(`Encrypted vault created: ${chalk.cyan(vault.name)} (${vault.slug})`, {
|
|
90
|
+
id: vault.id,
|
|
91
|
+
name: vault.name,
|
|
92
|
+
slug: vault.slug,
|
|
93
|
+
encrypted: true,
|
|
94
|
+
vaultKey: key,
|
|
95
|
+
});
|
|
96
|
+
out.warn('IMPORTANT: Save this encryption key securely. If lost, your data cannot be recovered.');
|
|
97
|
+
out.status(`Vault Key: ${chalk.green(key)}`);
|
|
98
|
+
out.status(chalk.dim('The key has been saved to your credential store.'));
|
|
99
|
+
out.status(chalk.dim('You can export it later with: lsvault vaults export-key ' + vault.id));
|
|
100
|
+
out.warn('Encrypted vaults disable: full-text search, AI features, hooks, and webhooks.');
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
out.success(`Vault created: ${chalk.cyan(vault.name)} (${vault.slug})`, {
|
|
104
|
+
id: vault.id,
|
|
105
|
+
name: vault.name,
|
|
106
|
+
slug: vault.slug,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
handleError(out, err, 'Failed to create vault');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
addGlobalFlags(vaults.command('export-key')
|
|
115
|
+
.description('Export the encryption key for an encrypted vault')
|
|
116
|
+
.argument('<vaultId>', 'Vault ID'))
|
|
117
|
+
.action(async (vaultId, _opts) => {
|
|
118
|
+
const flags = resolveFlags(_opts);
|
|
119
|
+
const out = createOutput(flags);
|
|
120
|
+
try {
|
|
121
|
+
const credManager = createCredentialManager();
|
|
122
|
+
const key = await credManager.getVaultKey(vaultId);
|
|
123
|
+
if (!key) {
|
|
124
|
+
out.error('No encryption key found for vault ' + vaultId);
|
|
125
|
+
out.status(chalk.dim('Keys can be imported with: lsvault vaults import-key <vaultId> --key <key>'));
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
out.raw(key + '\n');
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
handleError(out, err, 'Failed to export vault key');
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
addGlobalFlags(vaults.command('import-key')
|
|
136
|
+
.description('Import an encryption key for an encrypted vault')
|
|
137
|
+
.argument('<vaultId>', 'Vault ID')
|
|
138
|
+
.requiredOption('--key <key>', 'Encryption key (64-character hex string)'))
|
|
139
|
+
.action(async (vaultId, _opts) => {
|
|
140
|
+
const flags = resolveFlags(_opts);
|
|
141
|
+
const out = createOutput(flags);
|
|
142
|
+
try {
|
|
143
|
+
const keyValue = _opts.key;
|
|
144
|
+
if (!/^[0-9a-f]{64}$/.test(keyValue)) {
|
|
145
|
+
out.error('Invalid key format. Expected a 64-character hex string (256 bits).');
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const credManager = createCredentialManager();
|
|
150
|
+
await credManager.saveVaultKey(vaultId, keyValue);
|
|
151
|
+
out.success('Vault encryption key saved successfully.', { vaultId });
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
handleError(out, err, 'Failed to import vault key');
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|