@lifestreamdynamics/vault-cli 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -30
- package/dist/client.d.ts +4 -0
- package/dist/client.js +12 -11
- package/dist/commands/admin.js +5 -5
- package/dist/commands/ai.d.ts +2 -0
- package/dist/commands/ai.js +124 -0
- package/dist/commands/analytics.d.ts +2 -0
- package/dist/commands/analytics.js +84 -0
- package/dist/commands/auth.js +10 -105
- package/dist/commands/booking.d.ts +2 -0
- package/dist/commands/booking.js +739 -0
- package/dist/commands/calendar.js +778 -6
- package/dist/commands/completion.d.ts +5 -0
- package/dist/commands/completion.js +60 -0
- package/dist/commands/config.js +17 -16
- package/dist/commands/connectors.js +12 -1
- package/dist/commands/custom-domains.d.ts +2 -0
- package/dist/commands/custom-domains.js +154 -0
- package/dist/commands/docs.js +152 -5
- package/dist/commands/hooks.js +6 -1
- package/dist/commands/links.js +9 -2
- package/dist/commands/mfa.js +1 -70
- package/dist/commands/plugins.d.ts +2 -0
- package/dist/commands/plugins.js +172 -0
- package/dist/commands/publish-vault.d.ts +2 -0
- package/dist/commands/publish-vault.js +117 -0
- package/dist/commands/publish.js +63 -2
- package/dist/commands/saml.d.ts +2 -0
- package/dist/commands/saml.js +220 -0
- package/dist/commands/scim.d.ts +2 -0
- package/dist/commands/scim.js +238 -0
- package/dist/commands/shares.js +25 -3
- package/dist/commands/subscription.js +9 -2
- package/dist/commands/sync.js +3 -0
- package/dist/commands/teams.js +233 -4
- package/dist/commands/user.js +444 -0
- package/dist/commands/vaults.js +240 -8
- package/dist/commands/webhooks.js +6 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +7 -3
- package/dist/index.js +28 -1
- package/dist/lib/credential-manager.js +32 -7
- package/dist/lib/migration.js +2 -2
- package/dist/lib/profiles.js +4 -4
- package/dist/sync/config.js +2 -2
- package/dist/sync/daemon-worker.js +13 -6
- package/dist/sync/daemon.js +2 -1
- package/dist/sync/remote-poller.js +7 -3
- package/dist/sync/state.js +2 -2
- package/dist/utils/confirm.d.ts +11 -0
- package/dist/utils/confirm.js +23 -0
- package/dist/utils/format.js +1 -1
- package/dist/utils/output.js +4 -1
- package/dist/utils/prompt.d.ts +29 -0
- package/dist/utils/prompt.js +146 -0
- package/package.json +2 -2
package/dist/commands/user.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
3
|
import { getClientAsync } from '../client.js';
|
|
3
4
|
import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
|
|
4
5
|
import { createOutput, handleError } from '../utils/output.js';
|
|
5
6
|
import { formatBytes } from '../utils/format.js';
|
|
7
|
+
import { promptPassword, readPasswordFromStdin } from '../utils/prompt.js';
|
|
6
8
|
export function registerUserCommands(program) {
|
|
7
9
|
const user = program.command('user').description('View account details and storage usage');
|
|
8
10
|
addGlobalFlags(user.command('storage')
|
|
@@ -45,4 +47,446 @@ export function registerUserCommands(program) {
|
|
|
45
47
|
handleError(out, err, 'Failed to fetch storage usage');
|
|
46
48
|
}
|
|
47
49
|
});
|
|
50
|
+
// user me
|
|
51
|
+
addGlobalFlags(user.command('me')
|
|
52
|
+
.description('Show your user profile'))
|
|
53
|
+
.action(async (_opts) => {
|
|
54
|
+
const flags = resolveFlags(_opts);
|
|
55
|
+
const out = createOutput(flags);
|
|
56
|
+
out.startSpinner('Fetching profile...');
|
|
57
|
+
try {
|
|
58
|
+
const client = await getClientAsync();
|
|
59
|
+
const me = await client.user.me();
|
|
60
|
+
out.stopSpinner();
|
|
61
|
+
out.record({ id: me.id, email: me.email, displayName: me.displayName, role: me.role, createdAt: me.createdAt });
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
handleError(out, err, 'Failed to fetch profile');
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// user password
|
|
68
|
+
addGlobalFlags(user.command('password')
|
|
69
|
+
.description('Change your password')
|
|
70
|
+
.option('--password-stdin', 'Read current and new passwords from stdin (one per line) for CI usage'))
|
|
71
|
+
.action(async (_opts) => {
|
|
72
|
+
const flags = resolveFlags(_opts);
|
|
73
|
+
const out = createOutput(flags);
|
|
74
|
+
let currentPassword;
|
|
75
|
+
let newPassword;
|
|
76
|
+
if (_opts.passwordStdin) {
|
|
77
|
+
currentPassword = await readPasswordFromStdin();
|
|
78
|
+
newPassword = await readPasswordFromStdin();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
currentPassword = await promptPassword('Current password: ');
|
|
82
|
+
if (!currentPassword) {
|
|
83
|
+
out.error('Current password is required.');
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
newPassword = await promptPassword('New password: ');
|
|
88
|
+
}
|
|
89
|
+
if (!currentPassword) {
|
|
90
|
+
out.error('Current password is required.');
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!newPassword) {
|
|
95
|
+
out.error('New password is required.');
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
out.startSpinner('Changing password...');
|
|
100
|
+
try {
|
|
101
|
+
const client = await getClientAsync();
|
|
102
|
+
await client.user.changePassword({ currentPassword, newPassword });
|
|
103
|
+
out.success('Password changed successfully', { changed: true });
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
handleError(out, err, 'Failed to change password');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// user email
|
|
110
|
+
addGlobalFlags(user.command('email')
|
|
111
|
+
.description('Request email address change')
|
|
112
|
+
.requiredOption('--new <email>', 'New email address')
|
|
113
|
+
.option('--password-stdin', 'Read password from stdin for CI usage'))
|
|
114
|
+
.action(async (_opts) => {
|
|
115
|
+
const flags = resolveFlags(_opts);
|
|
116
|
+
const out = createOutput(flags);
|
|
117
|
+
let password;
|
|
118
|
+
if (_opts.passwordStdin) {
|
|
119
|
+
password = await readPasswordFromStdin();
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
password = await promptPassword('Password: ');
|
|
123
|
+
}
|
|
124
|
+
if (!password) {
|
|
125
|
+
out.error('Password is required to change your email address.');
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
out.startSpinner('Requesting email change...');
|
|
130
|
+
try {
|
|
131
|
+
const client = await getClientAsync();
|
|
132
|
+
const result = await client.user.requestEmailChange({ newEmail: _opts.new, password });
|
|
133
|
+
out.success(result.message, { message: result.message });
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
handleError(out, err, 'Failed to request email change');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
// user email-verify
|
|
140
|
+
addGlobalFlags(user.command('email-verify')
|
|
141
|
+
.description('Confirm email change with verification token')
|
|
142
|
+
.requiredOption('--token <token>', 'Verification token from email'))
|
|
143
|
+
.action(async (_opts) => {
|
|
144
|
+
const flags = resolveFlags(_opts);
|
|
145
|
+
const out = createOutput(flags);
|
|
146
|
+
out.startSpinner('Verifying email change...');
|
|
147
|
+
try {
|
|
148
|
+
const client = await getClientAsync();
|
|
149
|
+
const result = await client.user.confirmEmailChange(_opts.token);
|
|
150
|
+
out.success(result.message, { message: result.message });
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
handleError(out, err, 'Failed to verify email change');
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
// user profile
|
|
157
|
+
addGlobalFlags(user.command('profile')
|
|
158
|
+
.description('Update your profile')
|
|
159
|
+
.option('--name <name>', 'Display name')
|
|
160
|
+
.option('--slug <slug>', 'Profile URL slug'))
|
|
161
|
+
.action(async (_opts) => {
|
|
162
|
+
const flags = resolveFlags(_opts);
|
|
163
|
+
const out = createOutput(flags);
|
|
164
|
+
out.startSpinner('Updating profile...');
|
|
165
|
+
try {
|
|
166
|
+
const client = await getClientAsync();
|
|
167
|
+
const result = await client.user.updateProfile({ displayName: _opts.name, profileSlug: _opts.slug });
|
|
168
|
+
out.success(result.message, { message: result.message });
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
handleError(out, err, 'Failed to update profile');
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
// user delete
|
|
175
|
+
addGlobalFlags(user.command('delete')
|
|
176
|
+
.description('Request account deletion')
|
|
177
|
+
.option('--password-stdin', 'Read password from stdin for CI usage')
|
|
178
|
+
.option('--reason <reason>', 'Reason for deletion')
|
|
179
|
+
.option('--export-data', 'Request data export before deletion'))
|
|
180
|
+
.action(async (_opts) => {
|
|
181
|
+
const flags = resolveFlags(_opts);
|
|
182
|
+
const out = createOutput(flags);
|
|
183
|
+
let password;
|
|
184
|
+
if (_opts.passwordStdin) {
|
|
185
|
+
password = await readPasswordFromStdin();
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
password = await promptPassword('Password: ');
|
|
189
|
+
}
|
|
190
|
+
if (!password) {
|
|
191
|
+
out.error('Password is required to delete your account.');
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
out.startSpinner('Requesting account deletion...');
|
|
196
|
+
try {
|
|
197
|
+
const client = await getClientAsync();
|
|
198
|
+
const result = await client.user.requestAccountDeletion({
|
|
199
|
+
password,
|
|
200
|
+
reason: _opts.reason,
|
|
201
|
+
exportData: _opts.exportData === true,
|
|
202
|
+
});
|
|
203
|
+
out.success(result.message, { message: result.message, scheduledAt: result.scheduledAt });
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
handleError(out, err, 'Failed to request account deletion');
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
// user delete-cancel
|
|
210
|
+
addGlobalFlags(user.command('delete-cancel')
|
|
211
|
+
.description('Cancel a pending account deletion'))
|
|
212
|
+
.action(async (_opts) => {
|
|
213
|
+
const flags = resolveFlags(_opts);
|
|
214
|
+
const out = createOutput(flags);
|
|
215
|
+
out.startSpinner('Cancelling account deletion...');
|
|
216
|
+
try {
|
|
217
|
+
const client = await getClientAsync();
|
|
218
|
+
const result = await client.user.cancelAccountDeletion();
|
|
219
|
+
out.success(result.message, { message: result.message });
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
handleError(out, err, 'Failed to cancel account deletion');
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
// user sessions subgroup
|
|
226
|
+
const sessions = user.command('sessions').description('Session management');
|
|
227
|
+
addGlobalFlags(sessions.command('list')
|
|
228
|
+
.description('List active sessions'))
|
|
229
|
+
.action(async (_opts) => {
|
|
230
|
+
const flags = resolveFlags(_opts);
|
|
231
|
+
const out = createOutput(flags);
|
|
232
|
+
out.startSpinner('Fetching sessions...');
|
|
233
|
+
try {
|
|
234
|
+
const client = await getClientAsync();
|
|
235
|
+
const list = await client.user.getSessions();
|
|
236
|
+
out.stopSpinner();
|
|
237
|
+
out.list(list.map(s => ({ id: s.id, current: s.current ? 'yes' : 'no', ip: s.ipAddress || '', createdAt: s.createdAt, lastSeen: s.lastSeenAt })), {
|
|
238
|
+
emptyMessage: 'No sessions found.',
|
|
239
|
+
columns: [
|
|
240
|
+
{ key: 'id', header: 'ID' },
|
|
241
|
+
{ key: 'current', header: 'Current' },
|
|
242
|
+
{ key: 'ip', header: 'IP' },
|
|
243
|
+
{ key: 'createdAt', header: 'Created' },
|
|
244
|
+
{ key: 'lastSeen', header: 'Last Seen' },
|
|
245
|
+
],
|
|
246
|
+
textFn: (s) => `${String(s.id)} ${s.current === 'yes' ? chalk.green('[current]') : ''} — ${String(s.ip)}`,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
handleError(out, err, 'Failed to fetch sessions');
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
addGlobalFlags(sessions.command('revoke')
|
|
254
|
+
.description('Revoke a session')
|
|
255
|
+
.argument('<sessionId>', 'Session ID'))
|
|
256
|
+
.action(async (sessionId, _opts) => {
|
|
257
|
+
const flags = resolveFlags(_opts);
|
|
258
|
+
const out = createOutput(flags);
|
|
259
|
+
out.startSpinner('Revoking session...');
|
|
260
|
+
try {
|
|
261
|
+
const client = await getClientAsync();
|
|
262
|
+
const result = await client.user.revokeSession(sessionId);
|
|
263
|
+
out.success(result.message, { message: result.message });
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
handleError(out, err, 'Failed to revoke session');
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
addGlobalFlags(sessions.command('revoke-all')
|
|
270
|
+
.description('Revoke all sessions'))
|
|
271
|
+
.action(async (_opts) => {
|
|
272
|
+
const flags = resolveFlags(_opts);
|
|
273
|
+
const out = createOutput(flags);
|
|
274
|
+
out.startSpinner('Revoking all sessions...');
|
|
275
|
+
try {
|
|
276
|
+
const client = await getClientAsync();
|
|
277
|
+
const result = await client.user.revokeAllSessions();
|
|
278
|
+
out.success(result.message, { message: result.message });
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
handleError(out, err, 'Failed to revoke sessions');
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// user export subgroup
|
|
285
|
+
const userExport = user.command('export').description('Data export management');
|
|
286
|
+
addGlobalFlags(userExport.command('create')
|
|
287
|
+
.description('Request a data export')
|
|
288
|
+
.option('--format <format>', 'Export format (json)', 'json'))
|
|
289
|
+
.action(async (_opts) => {
|
|
290
|
+
const flags = resolveFlags(_opts);
|
|
291
|
+
const out = createOutput(flags);
|
|
292
|
+
out.startSpinner('Creating data export...');
|
|
293
|
+
try {
|
|
294
|
+
const client = await getClientAsync();
|
|
295
|
+
const exp = await client.user.requestDataExport(_opts.format);
|
|
296
|
+
out.success('Data export requested', { id: exp.id, status: exp.status, format: exp.format });
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
handleError(out, err, 'Failed to create data export');
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
addGlobalFlags(userExport.command('get')
|
|
303
|
+
.description('Get a data export status')
|
|
304
|
+
.argument('<exportId>', 'Export ID'))
|
|
305
|
+
.action(async (exportId, _opts) => {
|
|
306
|
+
const flags = resolveFlags(_opts);
|
|
307
|
+
const out = createOutput(flags);
|
|
308
|
+
out.startSpinner('Fetching export status...');
|
|
309
|
+
try {
|
|
310
|
+
const client = await getClientAsync();
|
|
311
|
+
const exp = await client.user.getDataExport(exportId);
|
|
312
|
+
out.stopSpinner();
|
|
313
|
+
out.record({ id: exp.id, status: exp.status, format: exp.format, createdAt: exp.createdAt, completedAt: exp.completedAt, downloadUrl: exp.downloadUrl });
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
handleError(out, err, 'Failed to fetch export status');
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
addGlobalFlags(userExport.command('list')
|
|
320
|
+
.description('List all data exports'))
|
|
321
|
+
.action(async (_opts) => {
|
|
322
|
+
const flags = resolveFlags(_opts);
|
|
323
|
+
const out = createOutput(flags);
|
|
324
|
+
out.startSpinner('Fetching data exports...');
|
|
325
|
+
try {
|
|
326
|
+
const client = await getClientAsync();
|
|
327
|
+
const exports = await client.user.listDataExports();
|
|
328
|
+
out.stopSpinner();
|
|
329
|
+
out.list(exports.map(e => ({
|
|
330
|
+
id: e.id,
|
|
331
|
+
status: e.status,
|
|
332
|
+
format: e.format,
|
|
333
|
+
createdAt: e.createdAt,
|
|
334
|
+
completedAt: e.completedAt ?? '',
|
|
335
|
+
})), {
|
|
336
|
+
emptyMessage: 'No data exports found.',
|
|
337
|
+
columns: [
|
|
338
|
+
{ key: 'id', header: 'ID' },
|
|
339
|
+
{ key: 'status', header: 'Status' },
|
|
340
|
+
{ key: 'format', header: 'Format' },
|
|
341
|
+
{ key: 'createdAt', header: 'Created' },
|
|
342
|
+
{ key: 'completedAt', header: 'Completed' },
|
|
343
|
+
],
|
|
344
|
+
textFn: (e) => `${chalk.cyan(String(e.id))} [${String(e.status)}] ${String(e.format)} — created ${String(e.createdAt)}`,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
handleError(out, err, 'Failed to fetch data exports');
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
addGlobalFlags(userExport.command('download')
|
|
352
|
+
.description('Download a completed data export')
|
|
353
|
+
.argument('<exportId>', 'Export ID')
|
|
354
|
+
.option('-f, --file <filepath>', 'File path to write the export to'))
|
|
355
|
+
.action(async (exportId, _opts) => {
|
|
356
|
+
const flags = resolveFlags(_opts);
|
|
357
|
+
const out = createOutput(flags);
|
|
358
|
+
const outputPath = _opts.file;
|
|
359
|
+
if (!outputPath && process.stdout.isTTY) {
|
|
360
|
+
process.stderr.write(chalk.yellow('Warning: binary export data would corrupt your terminal. Use -f <filepath> to save to a file.\n'));
|
|
361
|
+
process.exitCode = 1;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
out.startSpinner('Downloading data export...');
|
|
365
|
+
try {
|
|
366
|
+
const client = await getClientAsync();
|
|
367
|
+
const blob = await client.user.downloadDataExport(exportId);
|
|
368
|
+
out.stopSpinner();
|
|
369
|
+
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
370
|
+
if (outputPath) {
|
|
371
|
+
await writeFile(outputPath, buffer);
|
|
372
|
+
out.success(`Export saved to ${chalk.cyan(outputPath)}`, { exportId, path: outputPath });
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
process.stdout.write(buffer);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
handleError(out, err, 'Failed to download data export');
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
// user consents subgroup
|
|
383
|
+
const consents = user.command('consents').description('Consent management');
|
|
384
|
+
addGlobalFlags(consents.command('list')
|
|
385
|
+
.description('List consent records'))
|
|
386
|
+
.action(async (_opts) => {
|
|
387
|
+
const flags = resolveFlags(_opts);
|
|
388
|
+
const out = createOutput(flags);
|
|
389
|
+
out.startSpinner('Fetching consents...');
|
|
390
|
+
try {
|
|
391
|
+
const client = await getClientAsync();
|
|
392
|
+
const list = await client.user.getConsents();
|
|
393
|
+
out.stopSpinner();
|
|
394
|
+
out.list(list.map(c => ({ type: c.consentType, version: c.version, granted: c.granted ? 'yes' : 'no', recordedAt: c.recordedAt })), {
|
|
395
|
+
emptyMessage: 'No consents found.',
|
|
396
|
+
columns: [
|
|
397
|
+
{ key: 'type', header: 'Type' },
|
|
398
|
+
{ key: 'version', header: 'Version' },
|
|
399
|
+
{ key: 'granted', header: 'Granted' },
|
|
400
|
+
{ key: 'recordedAt', header: 'Recorded' },
|
|
401
|
+
],
|
|
402
|
+
textFn: (c) => `${String(c.type)} v${String(c.version)}: ${c.granted === 'yes' ? chalk.green('granted') : chalk.red('denied')}`,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
handleError(out, err, 'Failed to fetch consents');
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
addGlobalFlags(consents.command('set')
|
|
410
|
+
.description('Record a consent decision')
|
|
411
|
+
.requiredOption('--type <t>', 'Consent type')
|
|
412
|
+
.requiredOption('--version <v>', 'Policy version')
|
|
413
|
+
.option('--granted', 'Grant consent')
|
|
414
|
+
.option('--no-granted', 'Deny consent'))
|
|
415
|
+
.action(async (_opts) => {
|
|
416
|
+
const flags = resolveFlags(_opts);
|
|
417
|
+
const out = createOutput(flags);
|
|
418
|
+
out.startSpinner('Recording consent...');
|
|
419
|
+
try {
|
|
420
|
+
const client = await getClientAsync();
|
|
421
|
+
const result = await client.user.recordConsent({
|
|
422
|
+
consentType: _opts.type,
|
|
423
|
+
version: _opts.version,
|
|
424
|
+
granted: _opts.granted !== false,
|
|
425
|
+
});
|
|
426
|
+
out.success(result.message, { message: result.message });
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
handleError(out, err, 'Failed to record consent');
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
// user invitations subgroup
|
|
433
|
+
const invitations = user.command('invitations').description('Team invitation management');
|
|
434
|
+
addGlobalFlags(invitations.command('list')
|
|
435
|
+
.description('List pending team invitations'))
|
|
436
|
+
.action(async (_opts) => {
|
|
437
|
+
const flags = resolveFlags(_opts);
|
|
438
|
+
const out = createOutput(flags);
|
|
439
|
+
out.startSpinner('Fetching invitations...');
|
|
440
|
+
try {
|
|
441
|
+
const client = await getClientAsync();
|
|
442
|
+
const list = await client.user.listTeamInvitations();
|
|
443
|
+
out.stopSpinner();
|
|
444
|
+
out.list(list.map(i => ({ id: i.id, team: i.teamName, role: i.role, invitedBy: i.invitedBy, expiresAt: i.expiresAt })), {
|
|
445
|
+
emptyMessage: 'No pending invitations.',
|
|
446
|
+
columns: [
|
|
447
|
+
{ key: 'id', header: 'ID' },
|
|
448
|
+
{ key: 'team', header: 'Team' },
|
|
449
|
+
{ key: 'role', header: 'Role' },
|
|
450
|
+
{ key: 'invitedBy', header: 'Invited By' },
|
|
451
|
+
{ key: 'expiresAt', header: 'Expires' },
|
|
452
|
+
],
|
|
453
|
+
textFn: (i) => `${chalk.cyan(String(i.team))} [${String(i.role)}] — invited by ${String(i.invitedBy)}`,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
catch (err) {
|
|
457
|
+
handleError(out, err, 'Failed to fetch invitations');
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
addGlobalFlags(invitations.command('accept')
|
|
461
|
+
.description('Accept a team invitation')
|
|
462
|
+
.argument('<id>', 'Invitation ID'))
|
|
463
|
+
.action(async (id, _opts) => {
|
|
464
|
+
const flags = resolveFlags(_opts);
|
|
465
|
+
const out = createOutput(flags);
|
|
466
|
+
out.startSpinner('Accepting invitation...');
|
|
467
|
+
try {
|
|
468
|
+
const client = await getClientAsync();
|
|
469
|
+
const result = await client.user.acceptTeamInvitation(id);
|
|
470
|
+
out.success(result.message, { message: result.message });
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
handleError(out, err, 'Failed to accept invitation');
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
addGlobalFlags(invitations.command('decline')
|
|
477
|
+
.description('Decline a team invitation')
|
|
478
|
+
.argument('<id>', 'Invitation ID'))
|
|
479
|
+
.action(async (id, _opts) => {
|
|
480
|
+
const flags = resolveFlags(_opts);
|
|
481
|
+
const out = createOutput(flags);
|
|
482
|
+
out.startSpinner('Declining invitation...');
|
|
483
|
+
try {
|
|
484
|
+
const client = await getClientAsync();
|
|
485
|
+
const result = await client.user.declineTeamInvitation(id);
|
|
486
|
+
out.success(result.message, { message: result.message });
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
handleError(out, err, 'Failed to decline invitation');
|
|
490
|
+
}
|
|
491
|
+
});
|
|
48
492
|
}
|