@viewportai/daemon 0.5.3 → 0.6.1
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 +575 -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/skills-command.js +3 -3
- 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-auto-sync.d.ts +17 -0
- package/dist/context/local-edge-auto-sync.d.ts.map +1 -0
- package/dist/context/local-edge-auto-sync.js +94 -0
- package/dist/context/local-edge-auto-sync.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 +306 -86
- 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/context-providers/viewport-vault-provider.d.ts.map +1 -1
- package/dist/context-providers/viewport-vault-provider.js +11 -0
- package/dist/context-providers/viewport-vault-provider.js.map +1 -1
- package/dist/core/session-context-prompt.d.ts.map +1 -1
- package/dist/core/session-context-prompt.js +8 -0
- package/dist/core/session-context-prompt.js.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/relay/bridge-daemon-key-registration.d.ts.map +1 -1
- package/dist/relay/bridge-daemon-key-registration.js +27 -7
- package/dist/relay/bridge-daemon-key-registration.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-grant-payloads.d.ts +44 -0
- package/dist/security/team-epoch-grant-payloads.d.ts.map +1 -0
- package/dist/security/team-epoch-grant-payloads.js +100 -0
- package/dist/security/team-epoch-grant-payloads.js.map +1 -0
- package/dist/security/team-epoch-grants.d.ts +31 -0
- package/dist/security/team-epoch-grants.d.ts.map +1 -0
- package/dist/security/team-epoch-grants.js +194 -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 +57 -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 +2 -1
- package/dist/server/trusted-edge-command-capability.d.ts.map +1 -1
- package/dist/server/trusted-edge-command-capability.js +15 -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 +200 -28
- package/dist/server/ws-command-handlers.js.map +1 -1
- package/dist/server/ws-protocol.d.ts +281 -44
- package/dist/server/ws-protocol.d.ts.map +1 -1
- package/dist/server/ws-protocol.js +89 -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/README.md +18 -0
- package/docs/configuration.md +3 -3
- package/docs/protocol-matrix.json +53 -8
- package/docs/security.md +11 -8
- package/node_modules/@viewportai/context-engine/src/repo/identities.js +7 -3
- 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, grantTeamEpochToWorkspaceUserEpochs, 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,530 @@ 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 = {
|
|
306
|
+
workspaceId: target.workspaceId,
|
|
307
|
+
serverUrl: target.serverUrl,
|
|
308
|
+
credential: target.credential,
|
|
309
|
+
tlsVerify: target.tlsVerify,
|
|
310
|
+
caCertPath: target.caCertPath,
|
|
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
|
+
accepted: acceptedTeamGrants.accepted,
|
|
396
|
+
teamEpochs: acceptedTeamGrants.teamEpochs.map(publicEpochForOutput),
|
|
397
|
+
},
|
|
398
|
+
results,
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
console.log(`Context vaults synced: ${summary.vaults}`);
|
|
403
|
+
console.log(`Context events pulled: ${summary.imported}/${summary.pulled}`);
|
|
404
|
+
if (summary.materializedGrants > 0) {
|
|
405
|
+
console.log(`Context grants materialized: ${summary.materializedGrants}`);
|
|
406
|
+
}
|
|
407
|
+
if (summary.rotationsProcessed > 0) {
|
|
408
|
+
console.log(`Crypto rotations processed: ${summary.rotationsProcessed}`);
|
|
409
|
+
}
|
|
410
|
+
if (summary.teamEpochGrantsAccepted > 0) {
|
|
411
|
+
console.log(`Team epoch grants accepted: ${summary.teamEpochGrantsAccepted}`);
|
|
412
|
+
}
|
|
413
|
+
if (summary.revocationsProcessed > 0) {
|
|
414
|
+
console.log(`Context revocations processed: ${summary.revocationsProcessed}`);
|
|
415
|
+
}
|
|
416
|
+
if (summary.grantsEmitted > 0) {
|
|
417
|
+
console.log(`Context grants emitted: ${summary.grantsEmitted}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async function contextDevResetCrypto() {
|
|
421
|
+
if (!hasFlag('i-understand')) {
|
|
422
|
+
throw new Error('vpd context dev-reset-crypto removes local encrypted context, epoch, and plan key material. Re-run with --i-understand to continue.');
|
|
423
|
+
}
|
|
424
|
+
const home = getFlag('home') ?? configDir();
|
|
425
|
+
const targets = [
|
|
426
|
+
path.join(home, 'crypto', 'epochs.json'),
|
|
427
|
+
path.join(home, 'context', 'canonical-resources'),
|
|
428
|
+
path.join(home, 'context', 'candidate-decision-applications'),
|
|
429
|
+
path.join(home, 'repos'),
|
|
430
|
+
path.join(home, 'identities'),
|
|
431
|
+
path.join(home, 'plans', 'trusted-edge-keys.json'),
|
|
432
|
+
];
|
|
433
|
+
const removed = [];
|
|
434
|
+
for (const target of targets) {
|
|
435
|
+
try {
|
|
436
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
437
|
+
removed.push(path.relative(home, target) || target);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
throw new Error(`Failed to remove local crypto state at ${target}: ${error.message}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (isJsonMode()) {
|
|
444
|
+
printJson({ command: 'context dev-reset-crypto', ok: true, home, removed });
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
console.log(`Local encrypted collaboration state reset under ${home}`);
|
|
448
|
+
for (const item of removed) {
|
|
449
|
+
console.log(`Removed: ${item}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async function fetchVisibleContextVaults(target) {
|
|
453
|
+
const query = new URLSearchParams({ credential: target.credential });
|
|
454
|
+
const response = await transportFetch(`${target.serverUrl.replace(/\/+$/, '')}/api/runtime/workspaces/${encodeURIComponent(target.workspaceId)}/context-vaults?${query.toString()}`, {
|
|
455
|
+
method: 'GET',
|
|
456
|
+
headers: {
|
|
457
|
+
accept: 'application/json',
|
|
458
|
+
'X-Viewport-Crypto-Protocol': 'viewport.trusted_edge_crypto/v2',
|
|
459
|
+
},
|
|
460
|
+
timeoutMs: 5_000,
|
|
461
|
+
tlsVerify: target.tlsVerify,
|
|
462
|
+
caCertPath: target.caCertPath,
|
|
463
|
+
tlsPins: target.tlsPins,
|
|
464
|
+
});
|
|
465
|
+
const payload = (await response.json());
|
|
466
|
+
if (!response.ok) {
|
|
467
|
+
throw new Error(`Failed to list context vaults for sync-all: HTTP ${response.status}`);
|
|
468
|
+
}
|
|
469
|
+
if (!Array.isArray(payload.data))
|
|
470
|
+
return [];
|
|
471
|
+
return payload.data
|
|
472
|
+
.filter((item) => !!item && typeof item === 'object' && !Array.isArray(item))
|
|
473
|
+
.map((item) => ({
|
|
474
|
+
vault_id: String(item.vault_id ?? ''),
|
|
475
|
+
access: item.access && typeof item.access === 'object' && !Array.isArray(item.access)
|
|
476
|
+
? item.access
|
|
477
|
+
: null,
|
|
478
|
+
}))
|
|
479
|
+
.filter((item) => item.vault_id.length > 0);
|
|
480
|
+
}
|
|
481
|
+
async function contextEpochPublish() {
|
|
482
|
+
const target = await resolveWorkspaceSyncTarget('epoch-publish');
|
|
483
|
+
const syncTarget = {
|
|
484
|
+
workspaceId: target.workspaceId,
|
|
485
|
+
serverUrl: target.serverUrl,
|
|
486
|
+
credential: target.credential,
|
|
487
|
+
tlsVerify: target.tlsVerify,
|
|
488
|
+
caCertPath: target.caCertPath,
|
|
489
|
+
tlsPins: target.tlsPins,
|
|
490
|
+
};
|
|
491
|
+
const teamId = getFlag('team');
|
|
492
|
+
let teamMemberGrants = null;
|
|
493
|
+
const epoch = teamId
|
|
494
|
+
? await ensureTeamCryptoEpoch({
|
|
495
|
+
target: syncTarget,
|
|
496
|
+
teamId,
|
|
497
|
+
home: getFlag('home'),
|
|
498
|
+
})
|
|
499
|
+
: await ensureUserCryptoEpoch({
|
|
500
|
+
target: syncTarget,
|
|
501
|
+
home: getFlag('home'),
|
|
502
|
+
});
|
|
503
|
+
if (teamId && 'platformEpochId' in epoch && epoch.platformEpochId) {
|
|
504
|
+
teamMemberGrants = await grantTeamEpochToWorkspaceUserEpochs({
|
|
505
|
+
target: syncTarget,
|
|
506
|
+
teamCryptoEpochId: epoch.platformEpochId,
|
|
507
|
+
home: getFlag('home'),
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (isJsonMode()) {
|
|
511
|
+
printJson({
|
|
512
|
+
command: 'context epoch-publish',
|
|
513
|
+
ok: true,
|
|
514
|
+
scope: teamId ? 'team' : 'user',
|
|
515
|
+
epoch: publicEpochForOutput(epoch),
|
|
516
|
+
...(teamMemberGrants
|
|
517
|
+
? {
|
|
518
|
+
teamMemberGrants: {
|
|
519
|
+
attempted: teamMemberGrants.attempted,
|
|
520
|
+
granted: teamMemberGrants.granted,
|
|
521
|
+
skipped: teamMemberGrants.skipped,
|
|
522
|
+
},
|
|
523
|
+
}
|
|
524
|
+
: {}),
|
|
525
|
+
});
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
console.log(`${teamId ? 'Team' : 'User'} crypto epoch ready: ${epoch.fingerprint}`);
|
|
529
|
+
console.log(`Epoch: ${epoch.epoch}`);
|
|
530
|
+
if (teamMemberGrants) {
|
|
531
|
+
console.log(`Team epoch member grants: ${teamMemberGrants.granted}/${teamMemberGrants.attempted}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function publicEpochForOutput(epoch) {
|
|
535
|
+
return {
|
|
536
|
+
workspaceId: epoch.workspaceId,
|
|
537
|
+
userId: 'userId' in epoch ? epoch.userId : undefined,
|
|
538
|
+
teamId: 'teamId' in epoch ? epoch.teamId : undefined,
|
|
539
|
+
platformTeamId: 'platformTeamId' in epoch ? (epoch.platformTeamId ?? null) : undefined,
|
|
540
|
+
platformEpochId: epoch.platformEpochId ?? null,
|
|
541
|
+
epoch: epoch.epoch,
|
|
542
|
+
schema: epoch.schema,
|
|
543
|
+
status: epoch.status,
|
|
544
|
+
encryptionPublicKeyJwk: epoch.encryptionPublicKeyJwk,
|
|
545
|
+
signingPublicKeyJwk: epoch.signingPublicKeyJwk,
|
|
546
|
+
fingerprint: epoch.fingerprint,
|
|
547
|
+
previousEpochFingerprint: epoch.previousEpochFingerprint ?? null,
|
|
548
|
+
createdAt: epoch.createdAt,
|
|
549
|
+
updatedAt: epoch.updatedAt,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
async function contextEpochRotate() {
|
|
553
|
+
const target = await resolveWorkspaceSyncTarget('epoch-rotate');
|
|
554
|
+
const syncTarget = {
|
|
269
555
|
workspaceId: target.workspaceId,
|
|
270
556
|
serverUrl: target.serverUrl,
|
|
271
557
|
credential: target.credential,
|
|
272
|
-
identityName: requiredFlag('name', 'vpd context identity-publish --name <identity>'),
|
|
273
558
|
tlsVerify: target.tlsVerify,
|
|
274
559
|
caCertPath: target.caCertPath,
|
|
275
560
|
tlsPins: target.tlsPins,
|
|
561
|
+
};
|
|
562
|
+
const reason = epochRotationReason(getFlag('reason') ?? 'manual_rotation');
|
|
563
|
+
const teamId = getFlag('team');
|
|
564
|
+
const epoch = teamId
|
|
565
|
+
? await rotateTeamCryptoEpoch({
|
|
566
|
+
target: syncTarget,
|
|
567
|
+
teamId,
|
|
568
|
+
reason,
|
|
569
|
+
home: getFlag('home'),
|
|
570
|
+
})
|
|
571
|
+
: await rotateUserCryptoEpoch({
|
|
572
|
+
target: syncTarget,
|
|
573
|
+
reason,
|
|
574
|
+
home: getFlag('home'),
|
|
575
|
+
});
|
|
576
|
+
if (isJsonMode()) {
|
|
577
|
+
printJson({
|
|
578
|
+
command: 'context epoch-rotate',
|
|
579
|
+
ok: true,
|
|
580
|
+
scope: teamId ? 'team' : 'user',
|
|
581
|
+
reason,
|
|
582
|
+
epoch,
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
console.log(`${teamId ? 'Team' : 'User'} crypto epoch rotated: ${epoch.fingerprint}`);
|
|
587
|
+
console.log(`Epoch: ${epoch.epoch}`);
|
|
588
|
+
console.log(`Reason: ${reason}`);
|
|
589
|
+
}
|
|
590
|
+
async function contextRecoveryBackup() {
|
|
591
|
+
const target = await resolveWorkspaceSyncTarget('recovery-backup');
|
|
592
|
+
const generatedRecoveryKey = getFlag('recovery-key') ? null : generateUserEpochRecoveryKey();
|
|
593
|
+
const recoveryKey = getFlag('recovery-key') ?? generatedRecoveryKey;
|
|
594
|
+
if (!recoveryKey) {
|
|
595
|
+
throw new Error('vpd context recovery-backup requires --recovery-key <key>');
|
|
596
|
+
}
|
|
597
|
+
const backup = await createUserEpochRecoveryBackup({
|
|
598
|
+
target: {
|
|
599
|
+
workspaceId: target.workspaceId,
|
|
600
|
+
serverUrl: target.serverUrl,
|
|
601
|
+
credential: target.credential,
|
|
602
|
+
tlsVerify: target.tlsVerify,
|
|
603
|
+
caCertPath: target.caCertPath,
|
|
604
|
+
tlsPins: target.tlsPins,
|
|
605
|
+
},
|
|
606
|
+
recoveryKey,
|
|
607
|
+
home: getFlag('home'),
|
|
608
|
+
});
|
|
609
|
+
if (isJsonMode()) {
|
|
610
|
+
printJson({
|
|
611
|
+
command: 'context recovery-backup',
|
|
612
|
+
ok: true,
|
|
613
|
+
backup,
|
|
614
|
+
generatedRecoveryKey,
|
|
615
|
+
});
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
console.log(`Recovery backup stored: ${backup.id}`);
|
|
619
|
+
console.log(`User epoch: ${backup.user_crypto_epoch_id}`);
|
|
620
|
+
if (generatedRecoveryKey) {
|
|
621
|
+
console.log('Recovery key generated. Store it somewhere private; Viewport cannot recover it.');
|
|
622
|
+
console.log(generatedRecoveryKey);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async function contextRecoveryRestore() {
|
|
626
|
+
const target = await resolveWorkspaceSyncTarget('recovery-restore');
|
|
627
|
+
const recoveryKey = requiredFlag('recovery-key', 'vpd context recovery-restore --recovery-key <key>');
|
|
628
|
+
const result = await restoreUserEpochFromRecoveryBackup({
|
|
629
|
+
target: {
|
|
630
|
+
workspaceId: target.workspaceId,
|
|
631
|
+
serverUrl: target.serverUrl,
|
|
632
|
+
credential: target.credential,
|
|
633
|
+
tlsVerify: target.tlsVerify,
|
|
634
|
+
caCertPath: target.caCertPath,
|
|
635
|
+
tlsPins: target.tlsPins,
|
|
636
|
+
},
|
|
637
|
+
recoveryKey,
|
|
638
|
+
home: getFlag('home'),
|
|
639
|
+
});
|
|
640
|
+
if (isJsonMode()) {
|
|
641
|
+
printJson({ command: 'context recovery-restore', ok: true, ...result });
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
console.log(`Recovery backup restored: ${result.backup.id}`);
|
|
645
|
+
console.log(`Recovered epoch: ${result.restoredEpoch.fingerprint}`);
|
|
646
|
+
console.log(`Rotated epoch: ${result.rotatedEpoch.fingerprint}`);
|
|
647
|
+
console.log(`Fresh recovery backup stored: ${result.rotatedBackup.id}`);
|
|
648
|
+
}
|
|
649
|
+
async function contextRotationsProcess() {
|
|
650
|
+
const target = await resolveWorkspaceSyncTarget('rotations-process');
|
|
651
|
+
const result = await processPendingCryptoRotationRequests({
|
|
652
|
+
target: {
|
|
653
|
+
workspaceId: target.workspaceId,
|
|
654
|
+
serverUrl: target.serverUrl,
|
|
655
|
+
credential: target.credential,
|
|
656
|
+
tlsVerify: target.tlsVerify,
|
|
657
|
+
caCertPath: target.caCertPath,
|
|
658
|
+
tlsPins: target.tlsPins,
|
|
659
|
+
},
|
|
660
|
+
home: getFlag('home'),
|
|
661
|
+
});
|
|
662
|
+
if (isJsonMode()) {
|
|
663
|
+
printJson({ command: 'context rotations-process', ok: true, ...result });
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
console.log(`Crypto rotation requests processed: ${result.processed}`);
|
|
667
|
+
if (result.userRotations > 0) {
|
|
668
|
+
console.log(`User epoch rotations: ${result.userRotations}`);
|
|
669
|
+
}
|
|
670
|
+
if (result.teamRotations > 0) {
|
|
671
|
+
console.log(`Team epoch rotations: ${result.teamRotations}`);
|
|
672
|
+
}
|
|
673
|
+
if (result.teamMemberGrants > 0) {
|
|
674
|
+
console.log(`Team epoch member grants created: ${result.teamMemberGrants}`);
|
|
675
|
+
}
|
|
676
|
+
if (result.skipped > 0) {
|
|
677
|
+
console.log(`Skipped rotation requests: ${result.skipped}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function epochRotationReason(value) {
|
|
681
|
+
if (value === 'device_revoked' ||
|
|
682
|
+
value === 'member_added' ||
|
|
683
|
+
value === 'member_revoked' ||
|
|
684
|
+
value === 'manual_rotation' ||
|
|
685
|
+
value === 'recovery') {
|
|
686
|
+
return value;
|
|
687
|
+
}
|
|
688
|
+
throw new Error('Epoch rotation reason must be device_revoked, member_added, member_revoked, manual_rotation, or recovery.');
|
|
689
|
+
}
|
|
690
|
+
async function contextDeviceEnrollRequest() {
|
|
691
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-request');
|
|
692
|
+
const deviceId = requiredFlag('device', 'vpd context device-enroll-request --device <id>');
|
|
693
|
+
const enrollment = await requestDeviceEpochEnrollment({
|
|
694
|
+
target: {
|
|
695
|
+
workspaceId: target.workspaceId,
|
|
696
|
+
serverUrl: target.serverUrl,
|
|
697
|
+
credential: target.credential,
|
|
698
|
+
tlsVerify: target.tlsVerify,
|
|
699
|
+
caCertPath: target.caCertPath,
|
|
700
|
+
tlsPins: target.tlsPins,
|
|
701
|
+
},
|
|
702
|
+
deviceId,
|
|
703
|
+
deviceLabel: getFlag('label') ?? deviceId,
|
|
704
|
+
home: getFlag('home'),
|
|
705
|
+
});
|
|
706
|
+
if (isJsonMode()) {
|
|
707
|
+
printJson({ command: 'context device-enroll-request', ok: true, enrollment });
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
console.log(`Device enrollment requested: ${enrollment.enrollmentId}`);
|
|
711
|
+
console.log(`Fingerprint: ${enrollment.fingerprint}`);
|
|
712
|
+
}
|
|
713
|
+
async function contextDeviceEnrollApprove() {
|
|
714
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-approve');
|
|
715
|
+
const enrollment = await approveDeviceEpochEnrollment({
|
|
716
|
+
target: {
|
|
717
|
+
workspaceId: target.workspaceId,
|
|
718
|
+
serverUrl: target.serverUrl,
|
|
719
|
+
credential: target.credential,
|
|
720
|
+
tlsVerify: target.tlsVerify,
|
|
721
|
+
caCertPath: target.caCertPath,
|
|
722
|
+
tlsPins: target.tlsPins,
|
|
723
|
+
},
|
|
724
|
+
enrollmentId: requiredFlag('enrollment', 'vpd context device-enroll-approve --enrollment <id>'),
|
|
725
|
+
home: getFlag('home'),
|
|
726
|
+
});
|
|
727
|
+
if (isJsonMode()) {
|
|
728
|
+
printJson({ command: 'context device-enroll-approve', ok: true, enrollment });
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
console.log(`Device enrollment approved: ${enrollment.id}`);
|
|
732
|
+
console.log(`Status: ${enrollment.status}`);
|
|
733
|
+
}
|
|
734
|
+
async function contextDeviceEnrollAccept() {
|
|
735
|
+
const target = await resolveWorkspaceSyncTarget('device-enroll-accept');
|
|
736
|
+
const epoch = await acceptDeviceEpochEnrollment({
|
|
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
|
+
enrollmentId: requiredFlag('enrollment', 'vpd context device-enroll-accept --enrollment <id>'),
|
|
746
|
+
home: getFlag('home'),
|
|
747
|
+
});
|
|
748
|
+
if (isJsonMode()) {
|
|
749
|
+
printJson({ command: 'context device-enroll-accept', ok: true, epoch });
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
console.log(`Device enrollment accepted. User crypto epoch ready: ${epoch.fingerprint}`);
|
|
753
|
+
}
|
|
754
|
+
async function contextDeviceEnrollments() {
|
|
755
|
+
const target = await resolveWorkspaceSyncTarget('device-enrollments');
|
|
756
|
+
const enrollments = await listDeviceEpochEnrollments({
|
|
757
|
+
target: {
|
|
758
|
+
workspaceId: target.workspaceId,
|
|
759
|
+
serverUrl: target.serverUrl,
|
|
760
|
+
credential: target.credential,
|
|
761
|
+
tlsVerify: target.tlsVerify,
|
|
762
|
+
caCertPath: target.caCertPath,
|
|
763
|
+
tlsPins: target.tlsPins,
|
|
764
|
+
},
|
|
765
|
+
});
|
|
766
|
+
if (isJsonMode()) {
|
|
767
|
+
printJson({ command: 'context device-enrollments', ok: true, enrollments });
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (enrollments.length === 0) {
|
|
771
|
+
console.log('No device enrollments found.');
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
for (const enrollment of enrollments) {
|
|
775
|
+
console.log(`${enrollment.id} ${enrollment.status} ${enrollment.device_label} ${enrollment.fingerprint}`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async function contextTeamGrantCreate() {
|
|
779
|
+
const target = await resolveWorkspaceSyncTarget('team-grant-create');
|
|
780
|
+
const grant = await grantTeamEpochToUserEpoch({
|
|
781
|
+
target: {
|
|
782
|
+
workspaceId: target.workspaceId,
|
|
783
|
+
serverUrl: target.serverUrl,
|
|
784
|
+
credential: target.credential,
|
|
785
|
+
tlsVerify: target.tlsVerify,
|
|
786
|
+
caCertPath: target.caCertPath,
|
|
787
|
+
tlsPins: target.tlsPins,
|
|
788
|
+
},
|
|
789
|
+
teamCryptoEpochId: requiredFlag('team-epoch', 'vpd context team-grant-create --team-epoch <id> --recipient-epoch <id>'),
|
|
790
|
+
recipientUserCryptoEpochId: requiredFlag('recipient-epoch', 'vpd context team-grant-create --team-epoch <id> --recipient-epoch <id>'),
|
|
791
|
+
home: getFlag('home'),
|
|
792
|
+
});
|
|
793
|
+
if (isJsonMode()) {
|
|
794
|
+
printJson({ command: 'context team-grant-create', ok: true, grant });
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
console.log(`Team epoch member grant created: ${grant.id}`);
|
|
798
|
+
}
|
|
799
|
+
async function contextTeamGrantsAccept() {
|
|
800
|
+
const target = await resolveWorkspaceSyncTarget('team-grants-accept');
|
|
801
|
+
const result = await acceptTeamEpochMemberGrants({
|
|
802
|
+
target: {
|
|
803
|
+
workspaceId: target.workspaceId,
|
|
804
|
+
serverUrl: target.serverUrl,
|
|
805
|
+
credential: target.credential,
|
|
806
|
+
tlsVerify: target.tlsVerify,
|
|
807
|
+
caCertPath: target.caCertPath,
|
|
808
|
+
tlsPins: target.tlsPins,
|
|
809
|
+
},
|
|
276
810
|
home: getFlag('home'),
|
|
277
811
|
});
|
|
278
812
|
if (isJsonMode()) {
|
|
279
|
-
printJson({
|
|
813
|
+
printJson({
|
|
814
|
+
command: 'context team-grants-accept',
|
|
815
|
+
ok: true,
|
|
816
|
+
accepted: result.accepted,
|
|
817
|
+
teamEpochs: result.teamEpochs.map(publicEpochForOutput),
|
|
818
|
+
});
|
|
280
819
|
return;
|
|
281
820
|
}
|
|
282
|
-
console.log(`
|
|
283
|
-
if (result.fingerprint)
|
|
284
|
-
console.log(`Fingerprint: ${result.fingerprint}`);
|
|
821
|
+
console.log(`Team epoch grants accepted: ${result.accepted}`);
|
|
285
822
|
}
|
|
286
823
|
async function contextGrantsProcess() {
|
|
287
824
|
const target = await resolveContextSyncTarget('grants-process');
|