@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.
Files changed (69) hide show
  1. package/README.md +62 -15
  2. package/dist/args.d.ts +20 -2
  3. package/dist/args.d.ts.map +1 -1
  4. package/dist/args.js +48 -3
  5. package/dist/args.js.map +1 -1
  6. package/dist/client/index.d.ts +9 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/index.js +13 -0
  9. package/dist/client/index.js.map +1 -0
  10. package/dist/client/keys.d.ts +22 -0
  11. package/dist/client/keys.d.ts.map +1 -0
  12. package/dist/client/keys.js +170 -0
  13. package/dist/client/keys.js.map +1 -0
  14. package/dist/client/maps.d.ts +115 -0
  15. package/dist/client/maps.d.ts.map +1 -0
  16. package/dist/client/maps.js +798 -0
  17. package/dist/client/maps.js.map +1 -0
  18. package/dist/client/personal-maps-key.d.ts +42 -0
  19. package/dist/client/personal-maps-key.d.ts.map +1 -0
  20. package/dist/client/personal-maps-key.js +168 -0
  21. package/dist/client/personal-maps-key.js.map +1 -0
  22. package/dist/client/project-scope.d.ts +87 -0
  23. package/dist/client/project-scope.d.ts.map +1 -0
  24. package/dist/client/project-scope.js +132 -0
  25. package/dist/client/project-scope.js.map +1 -0
  26. package/dist/client/projects.d.ts +53 -0
  27. package/dist/client/projects.d.ts.map +1 -0
  28. package/dist/client/projects.js +129 -0
  29. package/dist/client/projects.js.map +1 -0
  30. package/dist/client/status.d.ts +21 -0
  31. package/dist/client/status.d.ts.map +1 -0
  32. package/dist/client/status.js +71 -0
  33. package/dist/client/status.js.map +1 -0
  34. package/dist/client/team-key-bootstrap.d.ts +55 -0
  35. package/dist/client/team-key-bootstrap.d.ts.map +1 -0
  36. package/dist/client/team-key-bootstrap.js +127 -0
  37. package/dist/client/team-key-bootstrap.js.map +1 -0
  38. package/dist/client/vault-key.d.ts +5 -0
  39. package/dist/client/vault-key.d.ts.map +1 -0
  40. package/dist/client/vault-key.js +35 -0
  41. package/dist/client/vault-key.js.map +1 -0
  42. package/dist/client/wording.d.ts +62 -0
  43. package/dist/client/wording.d.ts.map +1 -0
  44. package/dist/client/wording.js +358 -0
  45. package/dist/client/wording.js.map +1 -0
  46. package/dist/cloud-commands.d.ts +34 -43
  47. package/dist/cloud-commands.d.ts.map +1 -1
  48. package/dist/cloud-commands.js +282 -466
  49. package/dist/cloud-commands.js.map +1 -1
  50. package/dist/cloud.d.ts +101 -2
  51. package/dist/cloud.d.ts.map +1 -1
  52. package/dist/cloud.js +91 -7
  53. package/dist/cloud.js.map +1 -1
  54. package/dist/commands.d.ts +14 -1
  55. package/dist/commands.d.ts.map +1 -1
  56. package/dist/commands.js +24 -0
  57. package/dist/commands.js.map +1 -1
  58. package/dist/credential.d.ts +7 -0
  59. package/dist/credential.d.ts.map +1 -1
  60. package/dist/credential.js +10 -1
  61. package/dist/credential.js.map +1 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +24 -5
  64. package/dist/index.js.map +1 -1
  65. package/dist/team-namespace.d.ts +74 -2
  66. package/dist/team-namespace.d.ts.map +1 -1
  67. package/dist/team-namespace.js +78 -18
  68. package/dist/team-namespace.js.map +1 -1
  69. 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"}