@viewportai/daemon 0.5.3 → 0.6.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/dist/cli/commands.d.ts +1 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +1 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/context-access-command.d.ts +0 -6
- package/dist/cli/context-access-command.d.ts.map +1 -1
- package/dist/cli/context-access-command.js +1 -71
- package/dist/cli/context-access-command.js.map +1 -1
- package/dist/cli/context-command.d.ts.map +1 -1
- package/dist/cli/context-command.js +526 -38
- package/dist/cli/context-command.js.map +1 -1
- package/dist/cli/context-vault-metadata-command.d.ts.map +1 -1
- package/dist/cli/context-vault-metadata-command.js +6 -1
- package/dist/cli/context-vault-metadata-command.js.map +1 -1
- package/dist/cli/lifecycle-commands.d.ts.map +1 -1
- package/dist/cli/lifecycle-commands.js +2 -8
- package/dist/cli/lifecycle-commands.js.map +1 -1
- package/dist/cli/unlock-command.d.ts +2 -0
- package/dist/cli/unlock-command.d.ts.map +1 -0
- package/dist/cli/unlock-command.js +35 -0
- package/dist/cli/unlock-command.js.map +1 -0
- package/dist/context/local-edge-store.d.ts +11 -0
- package/dist/context/local-edge-store.d.ts.map +1 -1
- package/dist/context/local-edge-store.js +25 -0
- package/dist/context/local-edge-store.js.map +1 -1
- package/dist/context/local-edge-sync.d.ts +2 -15
- package/dist/context/local-edge-sync.d.ts.map +1 -1
- package/dist/context/local-edge-sync.js +244 -85
- package/dist/context/local-edge-sync.js.map +1 -1
- package/dist/context/local-edge-types.d.ts +12 -0
- package/dist/context/local-edge-types.d.ts.map +1 -1
- package/dist/hooks/trusted-edge-plan-artifacts.d.ts +30 -27
- package/dist/hooks/trusted-edge-plan-artifacts.d.ts.map +1 -1
- package/dist/hooks/trusted-edge-plan-artifacts.js +71 -89
- package/dist/hooks/trusted-edge-plan-artifacts.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/security/epoch-enrollment.d.ts +48 -0
- package/dist/security/epoch-enrollment.d.ts.map +1 -0
- package/dist/security/epoch-enrollment.js +290 -0
- package/dist/security/epoch-enrollment.js.map +1 -0
- package/dist/security/epoch-protocol.d.ts +181 -0
- package/dist/security/epoch-protocol.d.ts.map +1 -0
- package/dist/security/epoch-protocol.js +285 -0
- package/dist/security/epoch-protocol.js.map +1 -0
- package/dist/security/epoch-public-pins.d.ts +19 -0
- package/dist/security/epoch-public-pins.d.ts.map +1 -0
- package/dist/security/epoch-public-pins.js +129 -0
- package/dist/security/epoch-public-pins.js.map +1 -0
- package/dist/security/epoch-recovery.d.ts +56 -0
- package/dist/security/epoch-recovery.d.ts.map +1 -0
- package/dist/security/epoch-recovery.js +314 -0
- package/dist/security/epoch-recovery.js.map +1 -0
- package/dist/security/epoch-store.d.ts +111 -0
- package/dist/security/epoch-store.d.ts.map +1 -0
- package/dist/security/epoch-store.js +224 -0
- package/dist/security/epoch-store.js.map +1 -0
- package/dist/security/epoch-sync.d.ts +47 -0
- package/dist/security/epoch-sync.d.ts.map +1 -0
- package/dist/security/epoch-sync.js +371 -0
- package/dist/security/epoch-sync.js.map +1 -0
- package/dist/security/team-epoch-grants.d.ts +28 -0
- package/dist/security/team-epoch-grants.d.ts.map +1 -0
- package/dist/security/team-epoch-grants.js +256 -0
- package/dist/security/team-epoch-grants.js.map +1 -0
- package/dist/server/http-context-routes.d.ts +2 -1
- package/dist/server/http-context-routes.d.ts.map +1 -1
- package/dist/server/http-context-routes.js +46 -15
- package/dist/server/http-context-routes.js.map +1 -1
- package/dist/server/http-server.js +1 -1
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/rate-limiter.d.ts.map +1 -1
- package/dist/server/rate-limiter.js +2 -1
- package/dist/server/rate-limiter.js.map +1 -1
- package/dist/server/trusted-edge-command-capability.d.ts +1 -1
- package/dist/server/trusted-edge-command-capability.d.ts.map +1 -1
- package/dist/server/trusted-edge-command-capability.js +11 -0
- package/dist/server/trusted-edge-command-capability.js.map +1 -1
- package/dist/server/ws-command-handlers.d.ts.map +1 -1
- package/dist/server/ws-command-handlers.js +124 -28
- package/dist/server/ws-command-handlers.js.map +1 -1
- package/dist/server/ws-protocol.d.ts +268 -44
- package/dist/server/ws-protocol.d.ts.map +1 -1
- package/dist/server/ws-protocol.js +81 -19
- package/dist/server/ws-protocol.js.map +1 -1
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +0 -17
- package/dist/startup.js.map +1 -1
- package/docs/protocol-matrix.json +40 -8
- package/node_modules/@viewportai/context-engine/src/repo/materializer.js +20 -5
- package/node_modules/@viewportai/context-engine/src/repo/membership.js +15 -0
- package/node_modules/@viewportai/context-engine/src/repo/sync.js +4 -4
- package/node_modules/@viewportai/context-engine/src/repo/vault.js +8 -3
- package/package.json +1 -1
|
@@ -1,19 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { getArgs, getFlag, hasFlag } from './args.js';
|
|
2
4
|
import { isJsonMode, printJson } from './command-shared.js';
|
|
3
|
-
import { initContextResource, isResolverPinMismatch, readContextStatus, resolveContextBundle, } from '../context/local-edge-store.js';
|
|
5
|
+
import { initContextResource, isResolverPinMismatch, joinContextResource, readContextStatus, resolveContextBundle, } from '../context/local-edge-store.js';
|
|
4
6
|
import { proposeContextEntry } from '../context/local-edge-candidates.js';
|
|
5
7
|
import { readCandidateDecisionApplications } from '../context/local-edge-decision-applications.js';
|
|
6
|
-
import { processPendingContextGrants, processPendingContextRevocations,
|
|
8
|
+
import { processPendingContextGrants, processPendingContextRevocations, pullContextEvents, pushContextEvents, } from '../context/local-edge-sync.js';
|
|
7
9
|
import { resolveContextKeyStore } from '../context/local-edge-key-store.js';
|
|
8
10
|
import { resolveContextSyncTarget, resolveWorkspaceSyncTarget } from './context-sync-target.js';
|
|
9
11
|
import { parseLimit, parseMaxItems, parseSince } from './context-command-parsers.js';
|
|
12
|
+
import { transportFetch } from './network.js';
|
|
10
13
|
import { contextAdd } from './context-add-command.js';
|
|
11
14
|
import { contextGet, contextProviderPropose, contextSearch } from './context-provider-command.js';
|
|
12
15
|
import { contextVaultCreate, contextVaultsList } from './context-vault-metadata-command.js';
|
|
13
16
|
import { contextVaultUse } from './context-vault-use-command.js';
|
|
14
17
|
import { contextCandidatePreview } from './context-candidate-preview-command.js';
|
|
15
18
|
import { contextRulesInstall } from './context-rules-command.js';
|
|
16
|
-
import {
|
|
19
|
+
import { acceptDeviceEpochEnrollment, approveDeviceEpochEnrollment, listDeviceEpochEnrollments, requestDeviceEpochEnrollment, } from '../security/epoch-enrollment.js';
|
|
20
|
+
import { ensureTeamCryptoEpoch, ensureUserCryptoEpoch, processPendingCryptoRotationRequests, rotateTeamCryptoEpoch, rotateUserCryptoEpoch, } from '../security/epoch-sync.js';
|
|
21
|
+
import { createUserEpochRecoveryBackup, generateUserEpochRecoveryKey, restoreUserEpochFromRecoveryBackup, } from '../security/epoch-recovery.js';
|
|
22
|
+
import { acceptTeamEpochMemberGrants, grantTeamEpochToUserEpoch, } from '../security/team-epoch-grants.js';
|
|
23
|
+
import { contextJoin, contextUserInit } from './context-access-command.js';
|
|
24
|
+
import { configDir } from '../core/config.js';
|
|
17
25
|
export async function context() {
|
|
18
26
|
const subcommand = getArgs()[1];
|
|
19
27
|
if (!subcommand) {
|
|
@@ -72,8 +80,56 @@ export async function context() {
|
|
|
72
80
|
await contextSyncPull();
|
|
73
81
|
return;
|
|
74
82
|
}
|
|
75
|
-
if (subcommand === '
|
|
76
|
-
await
|
|
83
|
+
if (subcommand === 'sync-all') {
|
|
84
|
+
await contextSyncAll();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (subcommand === 'dev-reset-crypto') {
|
|
88
|
+
await contextDevResetCrypto();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (subcommand === 'epoch-publish') {
|
|
92
|
+
await contextEpochPublish();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (subcommand === 'epoch-rotate') {
|
|
96
|
+
await contextEpochRotate();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (subcommand === 'recovery-backup') {
|
|
100
|
+
await contextRecoveryBackup();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (subcommand === 'recovery-restore') {
|
|
104
|
+
await contextRecoveryRestore();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (subcommand === 'rotations-process') {
|
|
108
|
+
await contextRotationsProcess();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (subcommand === 'device-enroll-request') {
|
|
112
|
+
await contextDeviceEnrollRequest();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (subcommand === 'device-enroll-approve') {
|
|
116
|
+
await contextDeviceEnrollApprove();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (subcommand === 'device-enroll-accept') {
|
|
120
|
+
await contextDeviceEnrollAccept();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (subcommand === 'device-enrollments' || subcommand === 'device-enroll-status') {
|
|
124
|
+
await contextDeviceEnrollments();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (subcommand === 'team-grant-create') {
|
|
128
|
+
await contextTeamGrantCreate();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (subcommand === 'team-grants-accept') {
|
|
132
|
+
await contextTeamGrantsAccept();
|
|
77
133
|
return;
|
|
78
134
|
}
|
|
79
135
|
if (subcommand === 'grants-process') {
|
|
@@ -104,34 +160,10 @@ export async function context() {
|
|
|
104
160
|
await contextJoin();
|
|
105
161
|
return;
|
|
106
162
|
}
|
|
107
|
-
if (subcommand === 'identity-export') {
|
|
108
|
-
await contextIdentityExport();
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (subcommand === 'identity-import') {
|
|
112
|
-
await contextIdentityImport();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (subcommand === 'device-request') {
|
|
116
|
-
await contextDeviceRequest();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (subcommand === 'device-approve') {
|
|
120
|
-
await contextDeviceApprove();
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (subcommand === 'device-accept') {
|
|
124
|
-
await contextDeviceAccept();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (subcommand === 'grant') {
|
|
128
|
-
await contextGrant();
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
163
|
throw new Error(contextUsage());
|
|
132
164
|
}
|
|
133
165
|
function contextUsage() {
|
|
134
|
-
return 'Usage: vpd context <create|vaults|use|init|status|add|search|get|propose|resolve|sync-push|sync-pull|
|
|
166
|
+
return 'Usage: vpd context <create|vaults|use|init|status|add|search|get|propose|resolve|sync-push|sync-pull|sync-all|dev-reset-crypto --i-understand|epoch-publish [--team <team-id>]|epoch-rotate [--team <team-id>] [--reason <reason>]|recovery-backup [--recovery-key <key>]|recovery-restore --recovery-key <key>|rotations-process|device-enroll-request|device-enroll-approve|device-enroll-accept|device-enrollments|team-grant-create|team-grants-accept|grants-process|revokes-process|decisions|candidate-preview|rules install|user-init|join> ...';
|
|
135
167
|
}
|
|
136
168
|
function showContextHelp() {
|
|
137
169
|
console.log(contextUsage());
|
|
@@ -263,25 +295,481 @@ async function contextSyncPull() {
|
|
|
263
295
|
console.log(`Context events pulled: ${result.imported}/${result.pulled}`);
|
|
264
296
|
console.log(`Repo: ${result.repoId}`);
|
|
265
297
|
}
|
|
266
|
-
async function
|
|
267
|
-
const target = await resolveWorkspaceSyncTarget('
|
|
268
|
-
const
|
|
298
|
+
async function contextSyncAll() {
|
|
299
|
+
const target = await resolveWorkspaceSyncTarget('sync-all');
|
|
300
|
+
const home = getFlag('home');
|
|
301
|
+
const userName = requiredFlag('user', 'vpd context sync-all --user <name> --device <name>');
|
|
302
|
+
const deviceName = requiredFlag('device', 'vpd context sync-all --user <name> --device <name>');
|
|
303
|
+
const credentials = readCredentials({ required: false });
|
|
304
|
+
const keyStore = parseKeyStore(getFlag('key-store'));
|
|
305
|
+
const syncTarget = {
|
|
269
306
|
workspaceId: target.workspaceId,
|
|
270
307
|
serverUrl: target.serverUrl,
|
|
271
308
|
credential: target.credential,
|
|
272
|
-
identityName: requiredFlag('name', 'vpd context identity-publish --name <identity>'),
|
|
273
309
|
tlsVerify: target.tlsVerify,
|
|
274
310
|
caCertPath: target.caCertPath,
|
|
275
311
|
tlsPins: target.tlsPins,
|
|
312
|
+
};
|
|
313
|
+
const rotations = await processPendingCryptoRotationRequests({
|
|
314
|
+
target: syncTarget,
|
|
315
|
+
home,
|
|
316
|
+
});
|
|
317
|
+
const acceptedTeamGrants = await acceptTeamEpochMemberGrants({
|
|
318
|
+
target: syncTarget,
|
|
319
|
+
home,
|
|
320
|
+
});
|
|
321
|
+
const vaults = await fetchVisibleContextVaults(target);
|
|
322
|
+
const results = [];
|
|
323
|
+
for (const vault of vaults) {
|
|
324
|
+
const contextResourceId = vault.vault_id;
|
|
325
|
+
if (!contextResourceId || vault.access?.can_view === false)
|
|
326
|
+
continue;
|
|
327
|
+
const status = await readContextStatus({ contextResourceId, home });
|
|
328
|
+
if (status.contexts.length === 0) {
|
|
329
|
+
await joinContextResource({
|
|
330
|
+
contextResourceId,
|
|
331
|
+
userName,
|
|
332
|
+
deviceName,
|
|
333
|
+
credentials,
|
|
334
|
+
keyStore,
|
|
335
|
+
home,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
const pulled = await pullContextEvents({
|
|
339
|
+
contextResourceId,
|
|
340
|
+
workspaceId: target.workspaceId,
|
|
341
|
+
serverUrl: target.serverUrl,
|
|
342
|
+
credential: target.credential,
|
|
343
|
+
tlsVerify: target.tlsVerify,
|
|
344
|
+
caCertPath: target.caCertPath,
|
|
345
|
+
tlsPins: target.tlsPins,
|
|
346
|
+
actorName: deviceName,
|
|
347
|
+
credentials,
|
|
348
|
+
limit: parseLimit(getFlag('limit')),
|
|
349
|
+
home,
|
|
350
|
+
});
|
|
351
|
+
const revoked = await processPendingContextRevocations({
|
|
352
|
+
contextResourceId,
|
|
353
|
+
workspaceId: target.workspaceId,
|
|
354
|
+
serverUrl: target.serverUrl,
|
|
355
|
+
credential: target.credential,
|
|
356
|
+
tlsVerify: target.tlsVerify,
|
|
357
|
+
caCertPath: target.caCertPath,
|
|
358
|
+
tlsPins: target.tlsPins,
|
|
359
|
+
actorName: deviceName,
|
|
360
|
+
credentials,
|
|
361
|
+
home,
|
|
362
|
+
});
|
|
363
|
+
const granted = await processPendingContextGrants({
|
|
364
|
+
contextResourceId,
|
|
365
|
+
workspaceId: target.workspaceId,
|
|
366
|
+
serverUrl: target.serverUrl,
|
|
367
|
+
credential: target.credential,
|
|
368
|
+
tlsVerify: target.tlsVerify,
|
|
369
|
+
caCertPath: target.caCertPath,
|
|
370
|
+
tlsPins: target.tlsPins,
|
|
371
|
+
actorName: deviceName,
|
|
372
|
+
credentials,
|
|
373
|
+
home,
|
|
374
|
+
});
|
|
375
|
+
results.push({ contextResourceId, ...pulled, revoked, granted });
|
|
376
|
+
}
|
|
377
|
+
const summary = {
|
|
378
|
+
vaults: results.length,
|
|
379
|
+
pulled: results.reduce((total, item) => total + item.pulled, 0),
|
|
380
|
+
imported: results.reduce((total, item) => total + item.imported, 0),
|
|
381
|
+
materializedGrants: results.reduce((total, item) => total + item.materializedGrants, 0),
|
|
382
|
+
revocationsProcessed: results.reduce((total, item) => total + item.revoked.revoked, 0),
|
|
383
|
+
grantsEmitted: results.reduce((total, item) => total + item.granted.emitted, 0),
|
|
384
|
+
rotationsProcessed: rotations.processed,
|
|
385
|
+
teamEpochGrantsAccepted: acceptedTeamGrants.accepted,
|
|
386
|
+
};
|
|
387
|
+
if (isJsonMode()) {
|
|
388
|
+
printJson({
|
|
389
|
+
command: 'context sync-all',
|
|
390
|
+
ok: true,
|
|
391
|
+
workspaceId: target.workspaceId,
|
|
392
|
+
...summary,
|
|
393
|
+
rotations,
|
|
394
|
+
acceptedTeamGrants,
|
|
395
|
+
results,
|
|
396
|
+
});
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
console.log(`Context vaults synced: ${summary.vaults}`);
|
|
400
|
+
console.log(`Context events pulled: ${summary.imported}/${summary.pulled}`);
|
|
401
|
+
if (summary.materializedGrants > 0) {
|
|
402
|
+
console.log(`Context grants materialized: ${summary.materializedGrants}`);
|
|
403
|
+
}
|
|
404
|
+
if (summary.rotationsProcessed > 0) {
|
|
405
|
+
console.log(`Crypto rotations processed: ${summary.rotationsProcessed}`);
|
|
406
|
+
}
|
|
407
|
+
if (summary.teamEpochGrantsAccepted > 0) {
|
|
408
|
+
console.log(`Team epoch grants accepted: ${summary.teamEpochGrantsAccepted}`);
|
|
409
|
+
}
|
|
410
|
+
if (summary.revocationsProcessed > 0) {
|
|
411
|
+
console.log(`Context revocations processed: ${summary.revocationsProcessed}`);
|
|
412
|
+
}
|
|
413
|
+
if (summary.grantsEmitted > 0) {
|
|
414
|
+
console.log(`Context grants emitted: ${summary.grantsEmitted}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
async function contextDevResetCrypto() {
|
|
418
|
+
if (!hasFlag('i-understand')) {
|
|
419
|
+
throw new Error('vpd context dev-reset-crypto removes local encrypted context, epoch, and plan key material. Re-run with --i-understand to continue.');
|
|
420
|
+
}
|
|
421
|
+
const home = getFlag('home') ?? configDir();
|
|
422
|
+
const targets = [
|
|
423
|
+
path.join(home, 'crypto', 'epochs.json'),
|
|
424
|
+
path.join(home, 'context', 'canonical-resources'),
|
|
425
|
+
path.join(home, 'context', 'candidate-decision-applications'),
|
|
426
|
+
path.join(home, 'repos'),
|
|
427
|
+
path.join(home, 'identities'),
|
|
428
|
+
path.join(home, 'plans', 'trusted-edge-keys.json'),
|
|
429
|
+
];
|
|
430
|
+
const removed = [];
|
|
431
|
+
for (const target of targets) {
|
|
432
|
+
try {
|
|
433
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
434
|
+
removed.push(path.relative(home, target) || target);
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
throw new Error(`Failed to remove local crypto state at ${target}: ${error.message}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (isJsonMode()) {
|
|
441
|
+
printJson({ command: 'context dev-reset-crypto', ok: true, home, removed });
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
console.log(`Local encrypted collaboration state reset under ${home}`);
|
|
445
|
+
for (const item of removed) {
|
|
446
|
+
console.log(`Removed: ${item}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function fetchVisibleContextVaults(target) {
|
|
450
|
+
const query = new URLSearchParams({ credential: target.credential });
|
|
451
|
+
const response = await transportFetch(`${target.serverUrl.replace(/\/+$/, '')}/api/runtime/workspaces/${encodeURIComponent(target.workspaceId)}/context-vaults?${query.toString()}`, {
|
|
452
|
+
method: 'GET',
|
|
453
|
+
headers: { accept: 'application/json' },
|
|
454
|
+
timeoutMs: 5_000,
|
|
455
|
+
tlsVerify: target.tlsVerify,
|
|
456
|
+
caCertPath: target.caCertPath,
|
|
457
|
+
tlsPins: target.tlsPins,
|
|
458
|
+
});
|
|
459
|
+
const payload = (await response.json());
|
|
460
|
+
if (!response.ok) {
|
|
461
|
+
throw new Error(`Failed to list context vaults for sync-all: HTTP ${response.status}`);
|
|
462
|
+
}
|
|
463
|
+
if (!Array.isArray(payload.data))
|
|
464
|
+
return [];
|
|
465
|
+
return payload.data
|
|
466
|
+
.filter((item) => !!item && typeof item === 'object' && !Array.isArray(item))
|
|
467
|
+
.map((item) => ({
|
|
468
|
+
vault_id: String(item.vault_id ?? ''),
|
|
469
|
+
access: item.access && typeof item.access === 'object' && !Array.isArray(item.access)
|
|
470
|
+
? item.access
|
|
471
|
+
: null,
|
|
472
|
+
}))
|
|
473
|
+
.filter((item) => item.vault_id.length > 0);
|
|
474
|
+
}
|
|
475
|
+
async function contextEpochPublish() {
|
|
476
|
+
const target = await resolveWorkspaceSyncTarget('epoch-publish');
|
|
477
|
+
const syncTarget = {
|
|
478
|
+
workspaceId: target.workspaceId,
|
|
479
|
+
serverUrl: target.serverUrl,
|
|
480
|
+
credential: target.credential,
|
|
481
|
+
tlsVerify: target.tlsVerify,
|
|
482
|
+
caCertPath: target.caCertPath,
|
|
483
|
+
tlsPins: target.tlsPins,
|
|
484
|
+
};
|
|
485
|
+
const teamId = getFlag('team');
|
|
486
|
+
const epoch = teamId
|
|
487
|
+
? await ensureTeamCryptoEpoch({
|
|
488
|
+
target: syncTarget,
|
|
489
|
+
teamId,
|
|
490
|
+
home: getFlag('home'),
|
|
491
|
+
})
|
|
492
|
+
: await ensureUserCryptoEpoch({
|
|
493
|
+
target: syncTarget,
|
|
494
|
+
home: getFlag('home'),
|
|
495
|
+
});
|
|
496
|
+
if (isJsonMode()) {
|
|
497
|
+
printJson({
|
|
498
|
+
command: 'context epoch-publish',
|
|
499
|
+
ok: true,
|
|
500
|
+
scope: teamId ? 'team' : 'user',
|
|
501
|
+
epoch,
|
|
502
|
+
});
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
console.log(`${teamId ? 'Team' : 'User'} crypto epoch ready: ${epoch.fingerprint}`);
|
|
506
|
+
console.log(`Epoch: ${epoch.epoch}`);
|
|
507
|
+
}
|
|
508
|
+
async function contextEpochRotate() {
|
|
509
|
+
const target = await resolveWorkspaceSyncTarget('epoch-rotate');
|
|
510
|
+
const syncTarget = {
|
|
511
|
+
workspaceId: target.workspaceId,
|
|
512
|
+
serverUrl: target.serverUrl,
|
|
513
|
+
credential: target.credential,
|
|
514
|
+
tlsVerify: target.tlsVerify,
|
|
515
|
+
caCertPath: target.caCertPath,
|
|
516
|
+
tlsPins: target.tlsPins,
|
|
517
|
+
};
|
|
518
|
+
const reason = epochRotationReason(getFlag('reason') ?? 'manual_rotation');
|
|
519
|
+
const teamId = getFlag('team');
|
|
520
|
+
const epoch = teamId
|
|
521
|
+
? await rotateTeamCryptoEpoch({
|
|
522
|
+
target: syncTarget,
|
|
523
|
+
teamId,
|
|
524
|
+
reason,
|
|
525
|
+
home: getFlag('home'),
|
|
526
|
+
})
|
|
527
|
+
: await rotateUserCryptoEpoch({
|
|
528
|
+
target: syncTarget,
|
|
529
|
+
reason,
|
|
530
|
+
home: getFlag('home'),
|
|
531
|
+
});
|
|
532
|
+
if (isJsonMode()) {
|
|
533
|
+
printJson({
|
|
534
|
+
command: 'context epoch-rotate',
|
|
535
|
+
ok: true,
|
|
536
|
+
scope: teamId ? 'team' : 'user',
|
|
537
|
+
reason,
|
|
538
|
+
epoch,
|
|
539
|
+
});
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
console.log(`${teamId ? 'Team' : 'User'} crypto epoch rotated: ${epoch.fingerprint}`);
|
|
543
|
+
console.log(`Epoch: ${epoch.epoch}`);
|
|
544
|
+
console.log(`Reason: ${reason}`);
|
|
545
|
+
}
|
|
546
|
+
async function contextRecoveryBackup() {
|
|
547
|
+
const target = await resolveWorkspaceSyncTarget('recovery-backup');
|
|
548
|
+
const generatedRecoveryKey = getFlag('recovery-key') ? null : generateUserEpochRecoveryKey();
|
|
549
|
+
const recoveryKey = getFlag('recovery-key') ?? generatedRecoveryKey;
|
|
550
|
+
if (!recoveryKey) {
|
|
551
|
+
throw new Error('vpd context recovery-backup requires --recovery-key <key>');
|
|
552
|
+
}
|
|
553
|
+
const backup = await createUserEpochRecoveryBackup({
|
|
554
|
+
target: {
|
|
555
|
+
workspaceId: target.workspaceId,
|
|
556
|
+
serverUrl: target.serverUrl,
|
|
557
|
+
credential: target.credential,
|
|
558
|
+
tlsVerify: target.tlsVerify,
|
|
559
|
+
caCertPath: target.caCertPath,
|
|
560
|
+
tlsPins: target.tlsPins,
|
|
561
|
+
},
|
|
562
|
+
recoveryKey,
|
|
563
|
+
home: getFlag('home'),
|
|
564
|
+
});
|
|
565
|
+
if (isJsonMode()) {
|
|
566
|
+
printJson({
|
|
567
|
+
command: 'context recovery-backup',
|
|
568
|
+
ok: true,
|
|
569
|
+
backup,
|
|
570
|
+
generatedRecoveryKey,
|
|
571
|
+
});
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
console.log(`Recovery backup stored: ${backup.id}`);
|
|
575
|
+
console.log(`User epoch: ${backup.user_crypto_epoch_id}`);
|
|
576
|
+
if (generatedRecoveryKey) {
|
|
577
|
+
console.log('Recovery key generated. Store it somewhere private; Viewport cannot recover it.');
|
|
578
|
+
console.log(generatedRecoveryKey);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function contextRecoveryRestore() {
|
|
582
|
+
const target = await resolveWorkspaceSyncTarget('recovery-restore');
|
|
583
|
+
const recoveryKey = requiredFlag('recovery-key', 'vpd context recovery-restore --recovery-key <key>');
|
|
584
|
+
const result = await restoreUserEpochFromRecoveryBackup({
|
|
585
|
+
target: {
|
|
586
|
+
workspaceId: target.workspaceId,
|
|
587
|
+
serverUrl: target.serverUrl,
|
|
588
|
+
credential: target.credential,
|
|
589
|
+
tlsVerify: target.tlsVerify,
|
|
590
|
+
caCertPath: target.caCertPath,
|
|
591
|
+
tlsPins: target.tlsPins,
|
|
592
|
+
},
|
|
593
|
+
recoveryKey,
|
|
594
|
+
home: getFlag('home'),
|
|
595
|
+
});
|
|
596
|
+
if (isJsonMode()) {
|
|
597
|
+
printJson({ command: 'context recovery-restore', ok: true, ...result });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
console.log(`Recovery backup restored: ${result.backup.id}`);
|
|
601
|
+
console.log(`Recovered epoch: ${result.restoredEpoch.fingerprint}`);
|
|
602
|
+
console.log(`Rotated epoch: ${result.rotatedEpoch.fingerprint}`);
|
|
603
|
+
console.log(`Fresh recovery backup stored: ${result.rotatedBackup.id}`);
|
|
604
|
+
}
|
|
605
|
+
async function contextRotationsProcess() {
|
|
606
|
+
const target = await resolveWorkspaceSyncTarget('rotations-process');
|
|
607
|
+
const result = await processPendingCryptoRotationRequests({
|
|
608
|
+
target: {
|
|
609
|
+
workspaceId: target.workspaceId,
|
|
610
|
+
serverUrl: target.serverUrl,
|
|
611
|
+
credential: target.credential,
|
|
612
|
+
tlsVerify: target.tlsVerify,
|
|
613
|
+
caCertPath: target.caCertPath,
|
|
614
|
+
tlsPins: target.tlsPins,
|
|
615
|
+
},
|
|
616
|
+
home: getFlag('home'),
|
|
617
|
+
});
|
|
618
|
+
if (isJsonMode()) {
|
|
619
|
+
printJson({ command: 'context rotations-process', ok: true, ...result });
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
console.log(`Crypto rotation requests processed: ${result.processed}`);
|
|
623
|
+
if (result.userRotations > 0) {
|
|
624
|
+
console.log(`User epoch rotations: ${result.userRotations}`);
|
|
625
|
+
}
|
|
626
|
+
if (result.teamRotations > 0) {
|
|
627
|
+
console.log(`Team epoch rotations: ${result.teamRotations}`);
|
|
628
|
+
}
|
|
629
|
+
if (result.teamMemberGrants > 0) {
|
|
630
|
+
console.log(`Team epoch member grants created: ${result.teamMemberGrants}`);
|
|
631
|
+
}
|
|
632
|
+
if (result.skipped > 0) {
|
|
633
|
+
console.log(`Skipped rotation requests: ${result.skipped}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function epochRotationReason(value) {
|
|
637
|
+
if (value === 'device_revoked' ||
|
|
638
|
+
value === 'member_added' ||
|
|
639
|
+
value === 'member_revoked' ||
|
|
640
|
+
value === 'manual_rotation' ||
|
|
641
|
+
value === 'recovery') {
|
|
642
|
+
return value;
|
|
643
|
+
}
|
|
644
|
+
throw new Error('Epoch rotation reason must be device_revoked, member_added, member_revoked, manual_rotation, or recovery.');
|
|
645
|
+
}
|
|
646
|
+
async function contextDeviceEnrollRequest() {
|
|
647
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-request');
|
|
648
|
+
const deviceId = requiredFlag('device', 'vpd context device-enroll-request --device <id>');
|
|
649
|
+
const enrollment = await requestDeviceEpochEnrollment({
|
|
650
|
+
target: {
|
|
651
|
+
workspaceId: target.workspaceId,
|
|
652
|
+
serverUrl: target.serverUrl,
|
|
653
|
+
credential: target.credential,
|
|
654
|
+
tlsVerify: target.tlsVerify,
|
|
655
|
+
caCertPath: target.caCertPath,
|
|
656
|
+
tlsPins: target.tlsPins,
|
|
657
|
+
},
|
|
658
|
+
deviceId,
|
|
659
|
+
deviceLabel: getFlag('label') ?? deviceId,
|
|
660
|
+
home: getFlag('home'),
|
|
661
|
+
});
|
|
662
|
+
if (isJsonMode()) {
|
|
663
|
+
printJson({ command: 'context device-enroll-request', ok: true, enrollment });
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
console.log(`Device enrollment requested: ${enrollment.enrollmentId}`);
|
|
667
|
+
console.log(`Fingerprint: ${enrollment.fingerprint}`);
|
|
668
|
+
}
|
|
669
|
+
async function contextDeviceEnrollApprove() {
|
|
670
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-approve');
|
|
671
|
+
const enrollment = await approveDeviceEpochEnrollment({
|
|
672
|
+
target: {
|
|
673
|
+
workspaceId: target.workspaceId,
|
|
674
|
+
serverUrl: target.serverUrl,
|
|
675
|
+
credential: target.credential,
|
|
676
|
+
tlsVerify: target.tlsVerify,
|
|
677
|
+
caCertPath: target.caCertPath,
|
|
678
|
+
tlsPins: target.tlsPins,
|
|
679
|
+
},
|
|
680
|
+
enrollmentId: requiredFlag('enrollment', 'vpd context device-enroll-approve --enrollment <id>'),
|
|
681
|
+
home: getFlag('home'),
|
|
682
|
+
});
|
|
683
|
+
if (isJsonMode()) {
|
|
684
|
+
printJson({ command: 'context device-enroll-approve', ok: true, enrollment });
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
console.log(`Device enrollment approved: ${enrollment.id}`);
|
|
688
|
+
console.log(`Status: ${enrollment.status}`);
|
|
689
|
+
}
|
|
690
|
+
async function contextDeviceEnrollAccept() {
|
|
691
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-accept');
|
|
692
|
+
const epoch = await acceptDeviceEpochEnrollment({
|
|
693
|
+
target: {
|
|
694
|
+
workspaceId: target.workspaceId,
|
|
695
|
+
serverUrl: target.serverUrl,
|
|
696
|
+
credential: target.credential,
|
|
697
|
+
tlsVerify: target.tlsVerify,
|
|
698
|
+
caCertPath: target.caCertPath,
|
|
699
|
+
tlsPins: target.tlsPins,
|
|
700
|
+
},
|
|
701
|
+
enrollmentId: requiredFlag('enrollment', 'vpd context device-enroll-accept --enrollment <id>'),
|
|
702
|
+
home: getFlag('home'),
|
|
703
|
+
});
|
|
704
|
+
if (isJsonMode()) {
|
|
705
|
+
printJson({ command: 'context device-enroll-accept', ok: true, epoch });
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
console.log(`Device enrollment accepted. User crypto epoch ready: ${epoch.fingerprint}`);
|
|
709
|
+
}
|
|
710
|
+
async function contextDeviceEnrollments() {
|
|
711
|
+
const target = await resolveWorkspaceSyncTarget('device-enrollments');
|
|
712
|
+
const enrollments = await listDeviceEpochEnrollments({
|
|
713
|
+
target: {
|
|
714
|
+
workspaceId: target.workspaceId,
|
|
715
|
+
serverUrl: target.serverUrl,
|
|
716
|
+
credential: target.credential,
|
|
717
|
+
tlsVerify: target.tlsVerify,
|
|
718
|
+
caCertPath: target.caCertPath,
|
|
719
|
+
tlsPins: target.tlsPins,
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
if (isJsonMode()) {
|
|
723
|
+
printJson({ command: 'context device-enrollments', ok: true, enrollments });
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (enrollments.length === 0) {
|
|
727
|
+
console.log('No device enrollments found.');
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
for (const enrollment of enrollments) {
|
|
731
|
+
console.log(`${enrollment.id} ${enrollment.status} ${enrollment.device_label} ${enrollment.fingerprint}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async function contextTeamGrantCreate() {
|
|
735
|
+
const target = await resolveWorkspaceSyncTarget('team-grant-create');
|
|
736
|
+
const grant = await grantTeamEpochToUserEpoch({
|
|
737
|
+
target: {
|
|
738
|
+
workspaceId: target.workspaceId,
|
|
739
|
+
serverUrl: target.serverUrl,
|
|
740
|
+
credential: target.credential,
|
|
741
|
+
tlsVerify: target.tlsVerify,
|
|
742
|
+
caCertPath: target.caCertPath,
|
|
743
|
+
tlsPins: target.tlsPins,
|
|
744
|
+
},
|
|
745
|
+
teamCryptoEpochId: requiredFlag('team-epoch', 'vpd context team-grant-create --team-epoch <id> --recipient-epoch <id>'),
|
|
746
|
+
recipientUserCryptoEpochId: requiredFlag('recipient-epoch', 'vpd context team-grant-create --team-epoch <id> --recipient-epoch <id>'),
|
|
747
|
+
home: getFlag('home'),
|
|
748
|
+
});
|
|
749
|
+
if (isJsonMode()) {
|
|
750
|
+
printJson({ command: 'context team-grant-create', ok: true, grant });
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
console.log(`Team epoch member grant created: ${grant.id}`);
|
|
754
|
+
}
|
|
755
|
+
async function contextTeamGrantsAccept() {
|
|
756
|
+
const target = await resolveWorkspaceSyncTarget('team-grants-accept');
|
|
757
|
+
const result = await acceptTeamEpochMemberGrants({
|
|
758
|
+
target: {
|
|
759
|
+
workspaceId: target.workspaceId,
|
|
760
|
+
serverUrl: target.serverUrl,
|
|
761
|
+
credential: target.credential,
|
|
762
|
+
tlsVerify: target.tlsVerify,
|
|
763
|
+
caCertPath: target.caCertPath,
|
|
764
|
+
tlsPins: target.tlsPins,
|
|
765
|
+
},
|
|
276
766
|
home: getFlag('home'),
|
|
277
767
|
});
|
|
278
768
|
if (isJsonMode()) {
|
|
279
|
-
printJson({ command: 'context
|
|
769
|
+
printJson({ command: 'context team-grants-accept', ok: true, ...result });
|
|
280
770
|
return;
|
|
281
771
|
}
|
|
282
|
-
console.log(`
|
|
283
|
-
if (result.fingerprint)
|
|
284
|
-
console.log(`Fingerprint: ${result.fingerprint}`);
|
|
772
|
+
console.log(`Team epoch grants accepted: ${result.accepted}`);
|
|
285
773
|
}
|
|
286
774
|
async function contextGrantsProcess() {
|
|
287
775
|
const target = await resolveContextSyncTarget('grants-process');
|