@veilio-inc/cli 0.5.4 → 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/README.md +62 -15
- package/dist/args.d.ts +20 -2
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +48 -3
- package/dist/args.js.map +1 -1
- package/dist/client/index.d.ts +9 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +13 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/keys.d.ts +22 -0
- package/dist/client/keys.d.ts.map +1 -0
- package/dist/client/keys.js +170 -0
- package/dist/client/keys.js.map +1 -0
- package/dist/client/maps.d.ts +115 -0
- package/dist/client/maps.d.ts.map +1 -0
- package/dist/client/maps.js +798 -0
- package/dist/client/maps.js.map +1 -0
- package/dist/client/personal-maps-key.d.ts +42 -0
- package/dist/client/personal-maps-key.d.ts.map +1 -0
- package/dist/client/personal-maps-key.js +168 -0
- package/dist/client/personal-maps-key.js.map +1 -0
- package/dist/client/project-scope.d.ts +87 -0
- package/dist/client/project-scope.d.ts.map +1 -0
- package/dist/client/project-scope.js +132 -0
- package/dist/client/project-scope.js.map +1 -0
- package/dist/client/projects.d.ts +53 -0
- package/dist/client/projects.d.ts.map +1 -0
- package/dist/client/projects.js +129 -0
- package/dist/client/projects.js.map +1 -0
- package/dist/client/status.d.ts +21 -0
- package/dist/client/status.d.ts.map +1 -0
- package/dist/client/status.js +71 -0
- package/dist/client/status.js.map +1 -0
- package/dist/client/team-key-bootstrap.d.ts +55 -0
- package/dist/client/team-key-bootstrap.d.ts.map +1 -0
- package/dist/client/team-key-bootstrap.js +127 -0
- package/dist/client/team-key-bootstrap.js.map +1 -0
- package/dist/client/vault-key.d.ts +5 -0
- package/dist/client/vault-key.d.ts.map +1 -0
- package/dist/client/vault-key.js +35 -0
- package/dist/client/vault-key.js.map +1 -0
- package/dist/client/wording.d.ts +62 -0
- package/dist/client/wording.d.ts.map +1 -0
- package/dist/client/wording.js +358 -0
- package/dist/client/wording.js.map +1 -0
- package/dist/cloud-commands.d.ts +34 -43
- package/dist/cloud-commands.d.ts.map +1 -1
- package/dist/cloud-commands.js +282 -466
- package/dist/cloud-commands.js.map +1 -1
- package/dist/cloud.d.ts +101 -2
- package/dist/cloud.d.ts.map +1 -1
- package/dist/cloud.js +91 -7
- package/dist/cloud.js.map +1 -1
- package/dist/commands.d.ts +14 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +24 -0
- package/dist/commands.js.map +1 -1
- package/dist/credential.d.ts +7 -0
- package/dist/credential.d.ts.map +1 -1
- package/dist/credential.js +10 -1
- package/dist/credential.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -5
- package/dist/index.js.map +1 -1
- package/dist/team-namespace.d.ts +74 -2
- package/dist/team-namespace.d.ts.map +1 -1
- package/dist/team-namespace.js +78 -18
- package/dist/team-namespace.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Projects: which one this sign-in works in (spec 052 US3, research §4).
|
|
2
|
+
//
|
|
3
|
+
// The choice is a field on the credential, not a file in the project
|
|
4
|
+
// directory: a project is something the ACCOUNT's team works in, and the MCP
|
|
5
|
+
// server started later for the same sign-in must find the same answer without
|
|
6
|
+
// being told (FR-012). A second sign-in, to another instance, has its own.
|
|
7
|
+
//
|
|
8
|
+
// Choosing one the team does not work in is refused here, against the server's
|
|
9
|
+
// own list, and changes nothing (FR-014). The server refuses it as well - 404
|
|
10
|
+
// on every map call that names it - so a client that skipped this check would
|
|
11
|
+
// still not reach another team's maps; this is about saying so up front.
|
|
12
|
+
import { listProjects } from '../cloud.js';
|
|
13
|
+
import { readCredential, writeCredential } from '../credential.js';
|
|
14
|
+
import { refuse, refusalFromCloudError } from './wording.js';
|
|
15
|
+
// VEILIO_PROJECT, when it is set, as this listing resolves it (FR-012a). The
|
|
16
|
+
// listing is the one command that does not refuse a value that matches
|
|
17
|
+
// nothing: it is how somebody finds the value to set instead.
|
|
18
|
+
import { PROJECT_ENV, matchProject, overrideSentence, projectOverride, } from './project-scope.js';
|
|
19
|
+
export async function listTeamProjects(ctx) {
|
|
20
|
+
const credential = readCredential(ctx.home);
|
|
21
|
+
if (!credential)
|
|
22
|
+
return refuse('not_signed_in');
|
|
23
|
+
let answer;
|
|
24
|
+
try {
|
|
25
|
+
answer = await listProjects(credential);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
return { ok: false, refusal: refusalFromCloudError(err, { project: credential.project }) };
|
|
29
|
+
}
|
|
30
|
+
const rows = Array.isArray(answer?.projects) ? answer.projects : [];
|
|
31
|
+
if (rows.length === 0)
|
|
32
|
+
return refuse('no_projects');
|
|
33
|
+
const fallback = answer.defaultProjectId ?? rows[0].id;
|
|
34
|
+
// Resolved against THIS listing - the server's answer, just read - so the
|
|
35
|
+
// mark costs no second request.
|
|
36
|
+
const value = projectOverride(ctx.env);
|
|
37
|
+
let override = null;
|
|
38
|
+
if (value !== null) {
|
|
39
|
+
const found = matchProject(rows, value);
|
|
40
|
+
override = found.ok
|
|
41
|
+
? { value, id: found.project.id }
|
|
42
|
+
: found.reason === 'ambiguous'
|
|
43
|
+
? { value, id: null, reason: 'ambiguous', count: found.count }
|
|
44
|
+
: { value, id: null, reason: 'not_found' };
|
|
45
|
+
}
|
|
46
|
+
const inUse = override ? override.id : (credential.project ?? null);
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
value: {
|
|
50
|
+
chosen: credential.project ?? null,
|
|
51
|
+
override,
|
|
52
|
+
projects: rows.map((p) => ({
|
|
53
|
+
id: p.id,
|
|
54
|
+
name: p.name,
|
|
55
|
+
maps: typeof p.maps === 'number' ? p.maps : null,
|
|
56
|
+
inUse: p.id === inUse,
|
|
57
|
+
isDefault: p.id === fallback,
|
|
58
|
+
})),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The listing as text, once for both surfaces: one line per project (the one
|
|
64
|
+
* in use marked `*`, and saying so when the variable chose it), and the note
|
|
65
|
+
* that follows - the variable's sentence, or that none is chosen. The only
|
|
66
|
+
* thing a surface supplies is how it names the way to choose one.
|
|
67
|
+
*/
|
|
68
|
+
export function formatProjectsList(listing, opts) {
|
|
69
|
+
const { projects, chosen, override } = listing;
|
|
70
|
+
const lines = projects.map((p) => {
|
|
71
|
+
const marks = [
|
|
72
|
+
...(p.inUse ? [override ? `in use, from ${PROJECT_ENV}` : 'in use'] : []),
|
|
73
|
+
...(p.isDefault ? ['default'] : []),
|
|
74
|
+
...(p.maps !== null ? [`${p.maps} team map${p.maps === 1 ? '' : 's'}`] : []),
|
|
75
|
+
];
|
|
76
|
+
return `${p.inUse ? '*' : ' '} ${p.id} ${p.name} (${marks.join(', ')})`;
|
|
77
|
+
});
|
|
78
|
+
if (override)
|
|
79
|
+
return { lines, note: overrideSentence(override, chosen) };
|
|
80
|
+
if (chosen !== null)
|
|
81
|
+
return { lines, note: null };
|
|
82
|
+
return {
|
|
83
|
+
lines,
|
|
84
|
+
note: opts.surface === 'cli'
|
|
85
|
+
? 'No project is chosen, so team maps from every project are listed and used, and a new ' +
|
|
86
|
+
'team map lands in the default. Run `veilio projects use <name>` to work in one.'
|
|
87
|
+
: 'No project is chosen, so team maps from every project are used. projects_use chooses one.',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** What `projects use` says on stdout: where the work happens now, or - when
|
|
91
|
+
* the variable overrides the choice just stored - that it does not. */
|
|
92
|
+
export function usedSentence(used, opts) {
|
|
93
|
+
if (used.overriddenBy !== null)
|
|
94
|
+
return (`Saved project ${used.name} (${used.id}) for this sign-in. ${PROJECT_ENV} (=${used.overriddenBy}) ` +
|
|
95
|
+
`is in use here, so ${opts.surface === 'cli' ? 'this shell' : 'this server'} keeps working in that project.`);
|
|
96
|
+
return opts.surface === 'cli'
|
|
97
|
+
? `Now working in project ${used.name} (${used.id}). Team maps, pushes, updates and ` +
|
|
98
|
+
'`scrub --team` use this project only; the MCP server picks it up on its next call.'
|
|
99
|
+
: `Now working in project ${used.name} (${used.id}). Team maps and masking with the team's names use this project only.`;
|
|
100
|
+
}
|
|
101
|
+
/** Choose a project by its id or its name. A name two projects share is
|
|
102
|
+
* refused rather than guessed: guessing is how two projects' maps meet. */
|
|
103
|
+
export async function useProject(ctx, ref) {
|
|
104
|
+
const credential = readCredential(ctx.home);
|
|
105
|
+
if (!credential)
|
|
106
|
+
return refuse('not_signed_in');
|
|
107
|
+
if (!ref)
|
|
108
|
+
return refuse('which_project');
|
|
109
|
+
// The project list is asked without the current choice, so a chosen
|
|
110
|
+
// project that is gone never stops somebody choosing another.
|
|
111
|
+
const listed = await listTeamProjects(ctx);
|
|
112
|
+
if (!listed.ok)
|
|
113
|
+
return listed;
|
|
114
|
+
const found = matchProject(listed.value.projects, ref);
|
|
115
|
+
if (!found.ok) {
|
|
116
|
+
return found.reason === 'ambiguous'
|
|
117
|
+
? refuse('project_ambiguous', { project: ref, count: found.count })
|
|
118
|
+
: refuse('project_not_found', { project: ref });
|
|
119
|
+
}
|
|
120
|
+
const chosen = found.project;
|
|
121
|
+
writeCredential({ ...credential, project: chosen.id }, ctx.home);
|
|
122
|
+
// Stored all the same - it is what applies once the variable is unset - but
|
|
123
|
+
// whoever chose it must hear that this shell goes on using the variable's.
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
value: { id: chosen.id, name: chosen.name, overriddenBy: listed.value.override?.value ?? null },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/client/projects.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PROJECT_ENV } from './project-scope.js';
|
|
2
|
+
import { type Outcome } from './wording.js';
|
|
3
|
+
import type { ClientContext } from './maps.js';
|
|
4
|
+
export interface TeamStatus {
|
|
5
|
+
account: string;
|
|
6
|
+
instance: string;
|
|
7
|
+
teamId: string | null;
|
|
8
|
+
/** The chosen project, with its name when the server could say it. */
|
|
9
|
+
project: {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string | null;
|
|
12
|
+
from?: typeof PROJECT_ENV;
|
|
13
|
+
} | null;
|
|
14
|
+
teamKeyUntil: string | null;
|
|
15
|
+
personalKeyUntil: string | null;
|
|
16
|
+
/** Since when a rotation is owed; null when none is; undefined when the
|
|
17
|
+
* server did not say (an older instance, or no team). */
|
|
18
|
+
rotationOwedSince: string | null | undefined;
|
|
19
|
+
}
|
|
20
|
+
export declare function teamStatus(ctx: ClientContext): Promise<Outcome<TeamStatus>>;
|
|
21
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/client/status.ts"],"names":[],"mappings":"AAQA,OAAO,EAAsC,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGpF,OAAO,EAAyB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAE9C,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,sEAAsE;IACtE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,WAAW,CAAA;KAAE,GAAG,IAAI,CAAA;IAC9E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;8DAC0D;IAC1D,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAC7C;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAuDjF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Where this sign-in stands: the state `veilio whoami` and `team unlock`
|
|
2
|
+
// report, in one answer, for the MCP server's `team_status` (spec 052 FR-008).
|
|
3
|
+
//
|
|
4
|
+
// It says whether keys are unlocked HERE and until when - never the keys. And
|
|
5
|
+
// whether the team owes a key rotation, read from the team's key status; the
|
|
6
|
+
// rest of that answer names teammates, and stays out of this one.
|
|
7
|
+
import { getTeamKeyPending, listMaps, listProjects } from '../cloud.js';
|
|
8
|
+
import { readScopedCredential, refusalScope } from './project-scope.js';
|
|
9
|
+
import { readTeamUnlock } from '../team-unlock.js';
|
|
10
|
+
import { personalMapsKeyExpiry } from './personal-maps-key.js';
|
|
11
|
+
import { refusalFromCloudError } from './wording.js';
|
|
12
|
+
export async function teamStatus(ctx) {
|
|
13
|
+
const scoped = await readScopedCredential(ctx);
|
|
14
|
+
if (!scoped.ok)
|
|
15
|
+
return scoped;
|
|
16
|
+
const credential = scoped.value;
|
|
17
|
+
let teamId;
|
|
18
|
+
try {
|
|
19
|
+
teamId = (await listMaps(credential)).team?.id ?? null;
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return { ok: false, refusal: refusalFromCloudError(err, refusalScope(credential)) };
|
|
23
|
+
}
|
|
24
|
+
let project = null;
|
|
25
|
+
if (credential.projectFrom) {
|
|
26
|
+
// Named by the listing that resolved VEILIO_PROJECT: no second one.
|
|
27
|
+
project = {
|
|
28
|
+
id: credential.project,
|
|
29
|
+
name: credential.projectName ?? null,
|
|
30
|
+
from: credential.projectFrom,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
else if (credential.project) {
|
|
34
|
+
let name = null;
|
|
35
|
+
try {
|
|
36
|
+
name =
|
|
37
|
+
(await listProjects(credential)).projects.find((p) => p.id === credential.project)?.name ??
|
|
38
|
+
null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// The id alone is still the answer to "which project".
|
|
42
|
+
}
|
|
43
|
+
project = { id: credential.project, name };
|
|
44
|
+
}
|
|
45
|
+
const binding = { instance: credential.instance, account: credential.account };
|
|
46
|
+
const unlock = readTeamUnlock(binding, ctx.home);
|
|
47
|
+
let rotationOwedSince;
|
|
48
|
+
if (teamId) {
|
|
49
|
+
try {
|
|
50
|
+
const pending = await getTeamKeyPending(credential, teamId);
|
|
51
|
+
rotationOwedSince =
|
|
52
|
+
typeof pending?.rotationNeededSince === 'string' ? pending.rotationNeededSince : null;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
rotationOwedSince = undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
value: {
|
|
61
|
+
account: credential.account,
|
|
62
|
+
instance: credential.instance,
|
|
63
|
+
teamId,
|
|
64
|
+
project,
|
|
65
|
+
teamKeyUntil: unlock && teamId && unlock.teamId === teamId ? unlock.expiresAt : null,
|
|
66
|
+
personalKeyUntil: personalMapsKeyExpiry(binding, ctx.home),
|
|
67
|
+
rotationOwedSince,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/client/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type CryptoKeyLike } from '@veilio-inc/engine';
|
|
2
|
+
import type { Credential } from '../credential.js';
|
|
3
|
+
/** Wrap a team key for one recipient - the browser's `wrapTeamKeyFor`.
|
|
4
|
+
* `iv` is for the test vector only. */
|
|
5
|
+
export declare function wrapTeamKeyFor(teamKeyRaw: Uint8Array, myPrivateKey: CryptoKeyLike, myPublicKey: string, recipientPublicKey: string, ctx: {
|
|
6
|
+
teamId: string;
|
|
7
|
+
version: number;
|
|
8
|
+
}, iv?: Uint8Array): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* This account's keypair, opened with the vault key - and its public half
|
|
11
|
+
* DERIVED HERE from the private half, never taken from the server.
|
|
12
|
+
*
|
|
13
|
+
* Why (security review): the first team key is wrapped to this account's own
|
|
14
|
+
* public key. Taken as `GET /api/auth/keys` returned it, a dishonest or
|
|
15
|
+
* compromised server could hand back ITS public key and be wrapped the team
|
|
16
|
+
* key, which ends end-to-end confidentiality for every team map after. The
|
|
17
|
+
* private key comes out of a blob only the vault key opens, so the public key
|
|
18
|
+
* computed from it is the account's, whatever the server says. A server
|
|
19
|
+
* answer that differs is refused, fail closed.
|
|
20
|
+
*/
|
|
21
|
+
export declare function openOwnKeypair(vaultKey: CryptoKeyLike, privateKeyEncrypted: string, servedPublicKey: string): Promise<{
|
|
22
|
+
ok: true;
|
|
23
|
+
privateKey: CryptoKeyLike;
|
|
24
|
+
publicKey: string;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
reason: 'mismatch';
|
|
28
|
+
}>;
|
|
29
|
+
export type Bootstrap =
|
|
30
|
+
/** Minted and granted to this account: the team's key, raw. */
|
|
31
|
+
{
|
|
32
|
+
status: 'created';
|
|
33
|
+
version: number;
|
|
34
|
+
raw: Uint8Array;
|
|
35
|
+
}
|
|
36
|
+
/** The server refused: the team already has a key, or this account may not
|
|
37
|
+
* start one. `reason` is the server's own words when it gave some. */
|
|
38
|
+
| {
|
|
39
|
+
status: 'refused';
|
|
40
|
+
reason: string | null;
|
|
41
|
+
}
|
|
42
|
+
/** Granted, but what the server hands back is not the key minted here:
|
|
43
|
+
* nothing may be sealed under it. */
|
|
44
|
+
| {
|
|
45
|
+
status: 'readback_mismatch';
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Try to mint the team's first key. Throws only for a failure that is not an
|
|
49
|
+
* answer (the network, the server's own error), which the caller reports.
|
|
50
|
+
*/
|
|
51
|
+
export declare function bootstrapTeamKey(credential: Credential, teamId: string, me: {
|
|
52
|
+
privateKey: CryptoKeyLike;
|
|
53
|
+
publicKey: string;
|
|
54
|
+
}): Promise<Bootstrap>;
|
|
55
|
+
//# sourceMappingURL=team-key-bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-key-bootstrap.d.ts","sourceRoot":"","sources":["../../src/client/team-key-bootstrap.ts"],"names":[],"mappings":"AAqBA,OAAO,EASL,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;wCACwC;AACxC,wBAAsB,cAAc,CAClC,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,aAAa,EAC3B,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACxC,EAAE,GAAE,UAA4B,GAC/B,OAAO,CAAC,MAAM,CAAC,CAajB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,aAAa,EACvB,mBAAmB,EAAE,MAAM,EAC3B,eAAe,EAAE,MAAM,GACtB,OAAO,CACR;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,aAAa,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,CAC/F,CAsCA;AAED,MAAM,MAAM,SAAS;AACnB,+DAA+D;AAC7D;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE;AACzD;uEACuE;GACrE;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE;AAC9C;sCACsC;GACpC;IAAE,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAA;AAEnC;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE;IAAE,UAAU,EAAE,aAAa,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACnD,OAAO,CAAC,SAAS,CAAC,CA6CpB"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// The team's FIRST key, minted here when the team has none (spec 052 FR-016).
|
|
2
|
+
//
|
|
3
|
+
// Somebody has to mint it, and it cannot be the server: by design it holds no
|
|
4
|
+
// team key and could not make one that meant anything. The web app does it for
|
|
5
|
+
// the first owner or admin who opens it (frontend `bootstrapTeamKey.ts`). A
|
|
6
|
+
// solo developer's team of one may never get there: with no maps the web app
|
|
7
|
+
// no longer asks for the vault, so no key was ever minted, and `team unlock`
|
|
8
|
+
// sent them to ask a teammate who does not exist (staging, 2026-10-26).
|
|
9
|
+
//
|
|
10
|
+
// So `team unlock`, which holds the vault key anyway, does what the browser
|
|
11
|
+
// does, byte for byte: a fresh 32-byte AES-GCM key, wrapped to this account's
|
|
12
|
+
// OWN public key with the same ECDH + HKDF context and the same
|
|
13
|
+
// `{v, iv, data, from}` wrap, posted as the next unused version.
|
|
14
|
+
//
|
|
15
|
+
// THE SERVER DECIDES whether it really is a bootstrap - no wraps at all, the
|
|
16
|
+
// next version, recipient = granter, an owner or admin. This client cannot
|
|
17
|
+
// check the first: `GET /key` returns only its own wraps. So it tries, and a
|
|
18
|
+
// refusal is the answer. Two members unlocking at once cannot both succeed; the
|
|
19
|
+
// loser stands down rather than mint a rival key that would strand every map
|
|
20
|
+
// sealed under the winner's.
|
|
21
|
+
import { deriveWrappingKey, exportTeamKey, fromBase64, importPublicKey, unwrapTeamKey, randomBytes, toBase64, webCryptoSubtle, } from '@veilio-inc/engine';
|
|
22
|
+
import { CloudError, getMe, getTeamKeyPending, getTeamKeyWraps, grantTeamKey } from '../cloud.js';
|
|
23
|
+
/** Wrap a team key for one recipient - the browser's `wrapTeamKeyFor`.
|
|
24
|
+
* `iv` is for the test vector only. */
|
|
25
|
+
export async function wrapTeamKeyFor(teamKeyRaw, myPrivateKey, myPublicKey, recipientPublicKey, ctx, iv = randomBytes(12)) {
|
|
26
|
+
const wrappingKey = await deriveWrappingKey(myPrivateKey, await importPublicKey(recipientPublicKey), {
|
|
27
|
+
teamId: ctx.teamId,
|
|
28
|
+
version: ctx.version,
|
|
29
|
+
granterPublicKey: myPublicKey,
|
|
30
|
+
recipientPublicKey,
|
|
31
|
+
});
|
|
32
|
+
const data = await webCryptoSubtle().encrypt({ name: 'AES-GCM', iv }, wrappingKey, teamKeyRaw);
|
|
33
|
+
return JSON.stringify({ v: 1, iv: toBase64(iv), data: toBase64(data), from: myPublicKey });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* This account's keypair, opened with the vault key - and its public half
|
|
37
|
+
* DERIVED HERE from the private half, never taken from the server.
|
|
38
|
+
*
|
|
39
|
+
* Why (security review): the first team key is wrapped to this account's own
|
|
40
|
+
* public key. Taken as `GET /api/auth/keys` returned it, a dishonest or
|
|
41
|
+
* compromised server could hand back ITS public key and be wrapped the team
|
|
42
|
+
* key, which ends end-to-end confidentiality for every team map after. The
|
|
43
|
+
* private key comes out of a blob only the vault key opens, so the public key
|
|
44
|
+
* computed from it is the account's, whatever the server says. A server
|
|
45
|
+
* answer that differs is refused, fail closed.
|
|
46
|
+
*/
|
|
47
|
+
export async function openOwnKeypair(vaultKey, privateKeyEncrypted, servedPublicKey) {
|
|
48
|
+
const env = JSON.parse(privateKeyEncrypted);
|
|
49
|
+
if (env.v !== 1 ||
|
|
50
|
+
env.alg !== 'AES-GCM' ||
|
|
51
|
+
typeof env.iv !== 'string' ||
|
|
52
|
+
typeof env.data !== 'string') {
|
|
53
|
+
throw new Error('the stored private key is not an envelope this client knows');
|
|
54
|
+
}
|
|
55
|
+
const subtle = webCryptoSubtle();
|
|
56
|
+
const pkcs8 = await subtle.decrypt({ name: 'AES-GCM', iv: fromBase64(env.iv) }, vaultKey, fromBase64(env.data));
|
|
57
|
+
// Extractable only for this one export of the public half; the key used
|
|
58
|
+
// from here on is imported again, non-extractable.
|
|
59
|
+
const exportable = await subtle.importKey('pkcs8', pkcs8, { name: 'X25519' }, true, [
|
|
60
|
+
'deriveBits',
|
|
61
|
+
]);
|
|
62
|
+
const jwk = await subtle.exportKey('jwk', exportable);
|
|
63
|
+
if (typeof jwk.x !== 'string')
|
|
64
|
+
throw new Error('the private key did not yield a public key');
|
|
65
|
+
const derived = toBase64(fromBase64(jwk.x.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (jwk.x.length % 4)) % 4)));
|
|
66
|
+
if (derived !== servedPublicKey)
|
|
67
|
+
return { ok: false, reason: 'mismatch' };
|
|
68
|
+
const privateKey = await subtle.importKey('pkcs8', pkcs8, { name: 'X25519' }, false, [
|
|
69
|
+
'deriveBits',
|
|
70
|
+
]);
|
|
71
|
+
return { ok: true, privateKey: privateKey, publicKey: derived };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Try to mint the team's first key. Throws only for a failure that is not an
|
|
75
|
+
* answer (the network, the server's own error), which the caller reports.
|
|
76
|
+
*/
|
|
77
|
+
export async function bootstrapTeamKey(credential, teamId, me) {
|
|
78
|
+
const user = await getMe(credential);
|
|
79
|
+
const userId = user.user?.id;
|
|
80
|
+
if (typeof userId !== 'string' || userId === '') {
|
|
81
|
+
throw new CloudError('server', 'the instance did not say which account this is');
|
|
82
|
+
}
|
|
83
|
+
// Not always 1: when the last holder of a key left, its number is leaked,
|
|
84
|
+
// and the server refuses a new key under it (spec 014).
|
|
85
|
+
const pending = await getTeamKeyPending(credential, teamId);
|
|
86
|
+
const version = typeof pending?.nextVersion === 'number' ? pending.nextVersion : 1;
|
|
87
|
+
const raw = randomBytes(32);
|
|
88
|
+
// Wrapped to ourselves; our own public key is not pinned (pinning is for
|
|
89
|
+
// teammates). A substituted key would give a wrap we cannot open, which the
|
|
90
|
+
// unwrap right after this reports at once.
|
|
91
|
+
const wrapped = await wrapTeamKeyFor(raw, me.privateKey, me.publicKey, me.publicKey, {
|
|
92
|
+
teamId,
|
|
93
|
+
version,
|
|
94
|
+
});
|
|
95
|
+
try {
|
|
96
|
+
await grantTeamKey(credential, teamId, { userId, version, wrappedKey: wrapped });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// 403: the team has wraps already, or this member may not start one.
|
|
100
|
+
// 409: a wrap for this version exists. Somebody got there first.
|
|
101
|
+
if (err instanceof CloudError && (err.status === 403 || err.status === 409)) {
|
|
102
|
+
return { status: 'refused', reason: err.message || null };
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
// Read back and opened, and compared byte for byte with the key minted: the
|
|
107
|
+
// proof that what the team's maps will be sealed under is this key, and not
|
|
108
|
+
// one the server swapped in (security review). Anything else fails closed.
|
|
109
|
+
const { wraps } = await getTeamKeyWraps(credential, teamId);
|
|
110
|
+
const wrap = wraps.find((w) => w.version === version);
|
|
111
|
+
if (!wrap)
|
|
112
|
+
return { status: 'readback_mismatch' };
|
|
113
|
+
try {
|
|
114
|
+
const opened = await unwrapTeamKey(wrap.wrapped_key, me.privateKey, {
|
|
115
|
+
teamId,
|
|
116
|
+
version,
|
|
117
|
+
myPublicKey: me.publicKey,
|
|
118
|
+
});
|
|
119
|
+
if ((await exportTeamKey(opened)) !== toBase64(raw))
|
|
120
|
+
return { status: 'readback_mismatch' };
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return { status: 'readback_mismatch' };
|
|
124
|
+
}
|
|
125
|
+
return { status: 'created', version, raw };
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=team-key-bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-key-bootstrap.js","sourceRoot":"","sources":["../../src/client/team-key-bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type CryptoKeyLike } from '@veilio-inc/engine';
|
|
2
|
+
import type { Credential } from '../credential.js';
|
|
3
|
+
import { type Outcome } from './wording.js';
|
|
4
|
+
export declare function vaultKeyFor(credential: Credential, passphrase: (() => Promise<string>) | undefined): Promise<Outcome<CryptoKeyLike>>;
|
|
5
|
+
//# sourceMappingURL=vault-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-key.d.ts","sourceRoot":"","sources":["../../src/client/vault-key.ts"],"names":[],"mappings":"AASA,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAiC,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE1E,wBAAsB,WAAW,CAC/B,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,GAC9C,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAsBjC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The vault key, derived here from the passphrase and checked - once, for
|
|
2
|
+
// every caller: `team unlock`, `vault unlock`, and a personal map opened or
|
|
3
|
+
// sealed with the passphrase. Three copies of this had grown, and a KDF change
|
|
4
|
+
// or a new check fixed in one would have left the other two wrong (review).
|
|
5
|
+
//
|
|
6
|
+
// The server holds the salt, the KDF cost and a verifier - all it can hold.
|
|
7
|
+
// The passphrase never leaves this machine, and the key is never sent or
|
|
8
|
+
// written: it is non-extractable, used in memory and dropped.
|
|
9
|
+
import { checkVaultVerifier, deriveVaultKey, fromBase64, } from '@veilio-inc/engine';
|
|
10
|
+
import { getVault } from '../cloud.js';
|
|
11
|
+
import { refuse, refusalFromCloudError } from './wording.js';
|
|
12
|
+
export async function vaultKeyFor(credential, passphrase) {
|
|
13
|
+
let vault;
|
|
14
|
+
try {
|
|
15
|
+
vault = await getVault(credential);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
return { ok: false, refusal: refusalFromCloudError(err, { project: credential.project }) };
|
|
19
|
+
}
|
|
20
|
+
// Said plainly rather than as a decryption failure: there is nothing to try
|
|
21
|
+
// a passphrase against.
|
|
22
|
+
if (!vault.initialized)
|
|
23
|
+
return refuse('no_vault');
|
|
24
|
+
const typed = passphrase ? await passphrase() : '';
|
|
25
|
+
if (typed === '')
|
|
26
|
+
return refuse('no_passphrase');
|
|
27
|
+
const key = await deriveVaultKey(typed, fromBase64(vault.salt), vault.kdf ? { name: 'PBKDF2-SHA256', iterations: vault.kdf.iterations } : undefined);
|
|
28
|
+
// Before anything is opened, sealed or written: a wrong passphrase is named
|
|
29
|
+
// as one, rather than surfacing later as an authentication-tag failure that
|
|
30
|
+
// reads like corruption - or as a key wrapped for good under the wrong one.
|
|
31
|
+
if (!(await checkVaultVerifier(key, vault.verifier)))
|
|
32
|
+
return refuse('wrong_passphrase');
|
|
33
|
+
return { ok: true, value: key };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=vault-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-key.js","sourceRoot":"","sources":["../../src/client/vault-key.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,4EAA4E;AAC5E,+EAA+E;AAC/E,4EAA4E;AAC5E,EAAE;AACF,4EAA4E;AAC5E,yEAAyE;AACzE,8DAA8D;AAE9D,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,UAAU,GAEX,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,OAAO,EAAE,MAAM,EAAE,qBAAqB,EAAgB,MAAM,cAAc,CAAA;AAE1E,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,UAAsB,EACtB,UAA+C;IAE/C,IAAI,KAAK,CAAA;IACT,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAA;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qBAAqB,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,EAAE,CAAA;IAC5F,CAAC;IACD,4EAA4E;IAC5E,wBAAwB;IACxB,IAAI,CAAC,KAAK,CAAC,WAAW;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC,CAAA;IACjD,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAClD,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,MAAM,CAAC,eAAe,CAAC,CAAA;IAChD,MAAM,GAAG,GAAG,MAAM,cAAc,CAC9B,KAAK,EACL,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EACtB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS,CACpF,CAAA;IACD,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAC5E,IAAI,CAAC,CAAC,MAAM,kBAAkB,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC,kBAAkB,CAAC,CAAA;IACvF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAA;AACjC,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export declare const REFUSAL_CODES: readonly ["not_signed_in", "which_map", "map_not_found", "map_not_in_project", "not_yours_remove", "not_yours_update", "no_local_map", "no_vault", "no_passphrase", "wrong_passphrase", "vault_locked", "no_pmk_yet", "pmk_unopenable", "keys_not_set_up", "team_key_waiting", "own_public_key_mismatch", "team_key_readback_mismatch", "team_key_not_startable", "team_key_solo_unavailable", "personal_old_seal", "personal_unopenable", "not_envelope", "team_locked", "team_key_unreadable", "team_key_mismatch", "not_in_team", "team_maps_unreadable", "team_map_unopenable", "clash", "would_drop", "pull_would_drop", "diverged", "stale", "rotation_owed", "too_large", "abuse_limit", "which_project", "project_not_found", "project_ambiguous", "project_gone", "project_env_not_found", "project_env_ambiguous", "no_projects", "destructive_off", "confirm_name", "confirm_unknown", "session_rejected", "unentitled", "suspended", "forbidden", "unreachable", "server"];
|
|
2
|
+
export type RefusalCode = (typeof REFUSAL_CODES)[number];
|
|
3
|
+
/** What a sentence may be filled with. Every field is optional; each entry
|
|
4
|
+
* uses the ones it needs. */
|
|
5
|
+
export interface WordingParams {
|
|
6
|
+
id?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
count?: number;
|
|
9
|
+
detail?: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
from?: string;
|
|
12
|
+
to?: string;
|
|
13
|
+
/** A short, already-joined list - placeholders, map names, ids. */
|
|
14
|
+
list?: string;
|
|
15
|
+
project?: string;
|
|
16
|
+
/** The project came from VEILIO_PROJECT rather than the stored choice. */
|
|
17
|
+
override?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface Refusal {
|
|
20
|
+
code: RefusalCode;
|
|
21
|
+
/** The reason, as a sentence. */
|
|
22
|
+
message: string;
|
|
23
|
+
/** What resolves it, as a sentence that names something a person can do. */
|
|
24
|
+
resolve: string;
|
|
25
|
+
/** Extra lines for the terminal only - they may hold real identifiers, so the
|
|
26
|
+
* MCP server never renders them. */
|
|
27
|
+
details?: string[];
|
|
28
|
+
}
|
|
29
|
+
export type Outcome<T> = {
|
|
30
|
+
ok: true;
|
|
31
|
+
value: T;
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
refusal: Refusal;
|
|
35
|
+
};
|
|
36
|
+
type Words = (p: WordingParams) => {
|
|
37
|
+
message: string;
|
|
38
|
+
resolve: string;
|
|
39
|
+
};
|
|
40
|
+
export declare const WORDING: Record<RefusalCode, Words>;
|
|
41
|
+
/** A refusal outcome, worded from the table. */
|
|
42
|
+
export declare function refuse(code: RefusalCode, params?: WordingParams, details?: string[]): {
|
|
43
|
+
ok: false;
|
|
44
|
+
refusal: Refusal;
|
|
45
|
+
};
|
|
46
|
+
export declare function refusal(code: RefusalCode, params?: WordingParams, details?: string[]): Refusal;
|
|
47
|
+
/** How the terminal prints one. The MCP server has its own (no details). */
|
|
48
|
+
export declare function renderRefusal(r: Refusal): string;
|
|
49
|
+
/**
|
|
50
|
+
* A failed request, as a refusal with the remedy that fits it.
|
|
51
|
+
*
|
|
52
|
+
* `project` is the project the call carried. The server answers a project that
|
|
53
|
+
* is not the team's - or no longer is - with 404 "Project not found", and that
|
|
54
|
+
* must be said as such, never as "the instance reported an error", or nobody
|
|
55
|
+
* thinks to choose another (spec 052 edge case: the chosen project removed).
|
|
56
|
+
*/
|
|
57
|
+
export declare function refusalFromCloudError(err: unknown, ctx?: {
|
|
58
|
+
project?: string;
|
|
59
|
+
override?: boolean;
|
|
60
|
+
}): Refusal;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=wording.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wording.d.ts","sourceRoot":"","sources":["../../src/client/wording.ts"],"names":[],"mappings":"AAwBA,eAAO,MAAM,aAAa,w7BAqDhB,CAAA;AAEV,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD;8BAC8B;AAC9B,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAA;IACjB,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAA;IACf;yCACqC;IACrC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAA;AAajF,KAAK,KAAK,GAAG,CAAC,CAAC,EAAE,aAAa,KAAK;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAIvE,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,KAAK,CA8P9C,CAAA;AAED,gDAAgD;AAChD,wBAAgB,MAAM,CACpB,IAAI,EAAE,WAAW,EACjB,MAAM,GAAE,aAAkB,EAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAEjC;AAED,wBAAgB,OAAO,CACrB,IAAI,EAAE,WAAW,EACjB,MAAM,GAAE,aAAkB,EAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,OAAO,CAGT;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAGhD;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,GAAG,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GACjD,OAAO,CA2BT"}
|