@veilio-inc/cli 0.2.0 → 0.3.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/LICENSE +30 -5
- package/README.md +23 -16
- package/dist/args.d.ts +11 -1
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +32 -1
- package/dist/args.js.map +1 -1
- package/dist/cloud-commands.d.ts +30 -0
- package/dist/cloud-commands.d.ts.map +1 -1
- package/dist/cloud-commands.js +284 -6
- package/dist/cloud-commands.js.map +1 -1
- package/dist/cloud.d.ts +80 -8
- package/dist/cloud.d.ts.map +1 -1
- package/dist/cloud.js +38 -4
- package/dist/cloud.js.map +1 -1
- package/dist/commands.d.ts +14 -2
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +63 -10
- package/dist/commands.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/rules.d.ts +35 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +103 -0
- package/dist/rules.js.map +1 -0
- package/dist/team-namespace.d.ts +38 -0
- package/dist/team-namespace.d.ts.map +1 -0
- package/dist/team-namespace.js +188 -0
- package/dist/team-namespace.js.map +1 -0
- package/dist/team-unlock.d.ts +49 -0
- package/dist/team-unlock.d.ts.map +1 -0
- package/dist/team-unlock.js +134 -0
- package/dist/team-unlock.js.map +1 -0
- package/package.json +7 -4
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Veilio Community License
|
|
2
2
|
|
|
3
|
-
_Version 1.
|
|
3
|
+
_Version 1.1 · Effective 2026-09-25_
|
|
4
4
|
|
|
5
5
|
**Copyright © 2026 Veilio**
|
|
6
6
|
|
|
@@ -10,7 +10,8 @@ _Version 1.0 · Effective 2026-08-08_
|
|
|
10
10
|
> The one thing you may not do without written permission is sell Veilio itself:
|
|
11
11
|
> reselling it, white-labeling it, or running it as a hosted service for other
|
|
12
12
|
> people. Contributions are welcome. For a commercial or redistribution license,
|
|
13
|
-
> email `hello@veilio.dev`.
|
|
13
|
+
> email `hello@veilio.dev`. Veilio is not licensed for use in or from Russia or
|
|
14
|
+
> Belarus, or by sanctioned persons (Section 5A).
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
@@ -48,11 +49,17 @@ For the purposes of this License:
|
|
|
48
49
|
|
|
49
50
|
**"Competing Product"** means a commercially offered product or service whose primary purpose and core functionality substantially overlap with the Software or Cloud Edition.
|
|
50
51
|
|
|
52
|
+
**"Applicable Sanctions"** means any economic, financial, trade or other restrictive measures applicable to Veilio or the relevant transaction under the laws of the European Union or the Republic of Poland, including applicable asset-freezing and prohibition-on-making-available measures, and any other sanctions or trade restrictions that are legally binding on Veilio.
|
|
53
|
+
|
|
54
|
+
**"Restricted Person"** means any natural or legal person, entity or body that: (a) is designated or otherwise subject to restrictive measures under Applicable Sanctions, (b) is directly or indirectly owned or controlled by such a person, entity or body, (c) acts on behalf of or at the direction of such a person, entity or body; or (d) is otherwise a person to whom Veilio is prohibited under Applicable Sanctions from providing the Software, funds or economic resources.
|
|
55
|
+
|
|
56
|
+
**"Restricted Territory"** means the Russian Federation and the Republic of Belarus.
|
|
57
|
+
|
|
51
58
|
---
|
|
52
59
|
|
|
53
60
|
## 3. Permissions
|
|
54
61
|
|
|
55
|
-
Subject to the restrictions in this License, you are granted a worldwide, royalty-free, non-exclusive license to:
|
|
62
|
+
Subject to the restrictions in this License, including Section 5A, you are granted a worldwide, royalty-free, non-exclusive license to:
|
|
56
63
|
|
|
57
64
|
* use the Software for personal purposes;
|
|
58
65
|
* use the Software for internal commercial purposes;
|
|
@@ -114,6 +121,24 @@ Requests for written permission may be sent to `hello@veilio.dev`.
|
|
|
114
121
|
|
|
115
122
|
---
|
|
116
123
|
|
|
124
|
+
## 5A. Restricted Territories and Sanctions
|
|
125
|
+
|
|
126
|
+
**5A.1.** No license or other right to the Software is granted to:
|
|
127
|
+
|
|
128
|
+
* persons or entities incorporated, registered, established or headquartered in a Restricted Territory;
|
|
129
|
+
* natural persons whose principal place of business or ordinary residence is in a Restricted Territory; or
|
|
130
|
+
* Restricted Persons.
|
|
131
|
+
|
|
132
|
+
**5A.2.** You must not access or use the Software from a Restricted Territory, make the Software available to any person located in a Restricted Territory, or use the Software directly or indirectly for the benefit of a Restricted Person or a person or entity incorporated, registered, established or headquartered in a Restricted Territory.
|
|
133
|
+
|
|
134
|
+
**5A.3.** By using the Software, you represent and warrant, both when you first use it and throughout your use of it, that you are not a Restricted Person and do not fall within any of the territorial restrictions set out in this Section.
|
|
135
|
+
|
|
136
|
+
**5A.4.** You must not use an intermediary, affiliate, nominee, proxy, VPN or other arrangement for the purpose of circumventing the restrictions set out in this Section or Applicable Sanctions.
|
|
137
|
+
|
|
138
|
+
**5A.5.** Any breach of this Section is a material violation of this License and terminates your rights under it (Section 14). Veilio does not grant written permission for exceptions to this Section.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
117
142
|
## 6. Cloud Edition
|
|
118
143
|
|
|
119
144
|
The Cloud Edition is the official commercial hosted offering of the project.
|
|
@@ -218,7 +243,7 @@ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL THE AUTHORS
|
|
|
218
243
|
|
|
219
244
|
## 14. Termination
|
|
220
245
|
|
|
221
|
-
Your rights under this License terminate automatically if you materially violate its terms.
|
|
246
|
+
Your rights under this License terminate automatically if you materially violate its terms. Any breach of Section 5A is a material violation.
|
|
222
247
|
|
|
223
248
|
Upon termination, you must cease the activities that are prohibited by this License.
|
|
224
249
|
|
|
@@ -244,5 +269,5 @@ Each version will be identified by a version number. Unless explicitly stated ot
|
|
|
244
269
|
|
|
245
270
|
**Project:** Veilio
|
|
246
271
|
**Copyright:** © 2026 Veilio
|
|
247
|
-
**License:** Veilio Community License, Version 1.
|
|
272
|
+
**License:** Veilio Community License, Version 1.1
|
|
248
273
|
**Contact:** `hello@veilio.dev`
|
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ pbpaste | veilio restore # bring the answer back
|
|
|
8
8
|
git diff --cached | veilio scan # refuse to commit a live key
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Full guide - terminal, Cloud sign-in, and connecting AI assistants (Claude Code, Claude Desktop, Xcode, Cursor, VS Code): [docs/USING-THE-CLI-AND-MCP.md](../../docs/USING-THE-CLI-AND-MCP.md).
|
|
12
|
+
|
|
11
13
|
## Install
|
|
12
14
|
|
|
13
15
|
```bash
|
|
@@ -32,17 +34,15 @@ behavior; it only unlocks the four commands below that name Cloud explicitly.
|
|
|
32
34
|
|
|
33
35
|
## Cloud sync (Individual plan and above)
|
|
34
36
|
|
|
35
|
-
> **None of this is in `0.1.0`, which is what npm currently has.** These
|
|
36
|
-
> commands are merged here and ship with the next release; until then they run
|
|
37
|
-
> from a clone — `npm run build:packages`, then
|
|
38
|
-
> `node packages/cli/dist/index.js --help`.
|
|
39
|
-
|
|
40
37
|
```bash
|
|
41
|
-
veilio login #
|
|
38
|
+
veilio login # email, password, and the authentication code if 2FA is on
|
|
42
39
|
veilio whoami # who you're signed in as, no request made
|
|
43
40
|
veilio maps list # ids, scope and symbol counts for everything in Cloud
|
|
44
41
|
veilio maps pull <id> # decrypt locally, write second — never a partial write
|
|
45
42
|
veilio maps push <name> # upload the local map under a new Cloud name
|
|
43
|
+
veilio team unlock # open the team key with your vault passphrase (kept 7 days)
|
|
44
|
+
veilio team lock # remove the unlocked team key from this machine
|
|
45
|
+
veilio rules pull # fetch your custom rules; scrub then applies them offline
|
|
46
46
|
veilio logout # revokes server-side, then removes the local credential
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -53,10 +53,16 @@ accepted the sign-in. `0600` is the floor, not the final answer — the OS
|
|
|
53
53
|
keychain is a stronger option and a planned follow-up. A personal map is
|
|
54
54
|
zero-knowledge: pulling or pushing one prompts for the vault passphrase and
|
|
55
55
|
derives the key here, on your machine — the passphrase and the key never
|
|
56
|
-
leave it. A team map
|
|
57
|
-
|
|
56
|
+
leave it. A team map is sealed under the team key, which the server cannot
|
|
57
|
+
read either; `veilio team unlock` opens it here with your vault passphrase.
|
|
58
|
+
|
|
59
|
+
Custom rules (whitelist and replace, set in the web app) are applied by
|
|
60
|
+
`scrub` from a local copy that `veilio rules pull` writes to
|
|
61
|
+
`~/.veilio/rules.json` (`0600`). `scrub` never fetches them itself - it stays
|
|
62
|
+
offline - and says on every run how many rules it applied and how old the copy
|
|
63
|
+
is. Pull again after the rules change; `logout` removes the copy.
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
The map commands are clients of `GET /api/maps`, `GET /api/maps/:id` and
|
|
60
66
|
`POST /api/maps` — the exact same routes the browser uses, behind the exact
|
|
61
67
|
same entitlement check. There is no CLI-shaped API and nothing here can reach
|
|
62
68
|
a plan feature the web app couldn't.
|
|
@@ -113,13 +119,14 @@ Redacted credentials are **not** in the map. `restore` cannot bring them back, b
|
|
|
113
119
|
sarif: veilio.sarif # optional: upload to code scanning
|
|
114
120
|
```
|
|
115
121
|
|
|
116
|
-
**This example does not work yet, and needs
|
|
117
|
-
`@v0.1.0` ref is a git tag on this repository
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
that
|
|
122
|
-
|
|
122
|
+
**This example does not work yet, and needs a tag that does not exist.** The
|
|
123
|
+
`@v0.1.0` ref is a git tag on this repository, and the tags here are `engine-v*`
|
|
124
|
+
(cut by semantic-release for the engine) and `@veilio-inc/cli@*` /
|
|
125
|
+
`@veilio-inc/mcp@*` (cut by Changesets when the tools release) — neither shape
|
|
126
|
+
matches. The Action's own script runs `npx --yes @veilio-inc/cli@<version>`,
|
|
127
|
+
which is satisfied now that the CLI is published; what is left is a `v*.*.*` tag
|
|
128
|
+
someone has to cut, and note that `v*.*.*` is also what triggers the Community
|
|
129
|
+
Edition release workflow, so the two are not independent.
|
|
123
130
|
|
|
124
131
|
Scans the **pull-request diff** by default, not the whole tree. A repo adopting
|
|
125
132
|
this mid-life almost always has a historical finding somewhere; blocking every PR
|
package/dist/args.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type LanguageOption } from '@veilio-inc/engine';
|
|
2
2
|
import type { SecretPolicy } from '@veilio-inc/engine';
|
|
3
|
-
export type Command = 'scrub' | 'restore' | 'scan' | 'map' | 'login' | 'logout' | 'whoami' | 'maps' | 'help' | 'version';
|
|
3
|
+
export type Command = 'scrub' | 'restore' | 'scan' | 'map' | 'login' | 'logout' | 'whoami' | 'maps' | 'team' | 'rules' | 'help' | 'version';
|
|
4
4
|
export interface ParsedArgs {
|
|
5
5
|
command: Command;
|
|
6
6
|
/** Positional file paths. Empty means read stdin (or, for `scan`, error). */
|
|
@@ -32,6 +32,16 @@ export interface ParsedArgs {
|
|
|
32
32
|
* visibly separate from the offline one.
|
|
33
33
|
*/
|
|
34
34
|
mapsAction: 'list' | 'pull' | 'push' | null;
|
|
35
|
+
/**
|
|
36
|
+
* `team unlock` / `team lock`.
|
|
37
|
+
*
|
|
38
|
+
* A subcommand for the same reason `maps` is one: `veilio unlock` alone would
|
|
39
|
+
* not say what it unlocks, and what it unlocks is specifically the team's
|
|
40
|
+
* keys rather than the account or the local store.
|
|
41
|
+
*/
|
|
42
|
+
teamAction: 'unlock' | 'lock' | null;
|
|
43
|
+
/** `rules pull`. */
|
|
44
|
+
rulesAction: 'pull' | null;
|
|
35
45
|
/**
|
|
36
46
|
* Base URL of the Veilio instance to sign in to. Null means the public Cloud.
|
|
37
47
|
*
|
package/dist/args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,MAAM,OAAO,GACf,OAAO,GACP,SAAS,GACT,MAAM,GACN,KAAK,GACL,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,SAAS,CAAA;AAEb,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,cAAc,CAAA;IACxB,OAAO,EAAE,YAAY,CAAA;IACrB,qEAAqE;IACrE,QAAQ,EAAE,OAAO,CAAA;IACjB,uCAAuC;IACvC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,sCAAsC;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAA;IACd,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAA;IACd,6DAA6D;IAC7D,MAAM,EAAE,OAAO,CAAA;IACf,wCAAwC;IACxC,QAAQ,EAAE,OAAO,CAAA;IACjB,iEAAiE;IACjE,KAAK,EAAE,OAAO,CAAA;IACd;;;;;;;OAOG;IACH,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAE3C;;;;;;OAMG;IACH,UAAU,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAA;IACpC,oBAAoB;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAE1B;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAiBxC;;;;;GAKG;AACH,eAAO,MAAM,cAAc,cAOzB,CAAA;AAWF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CA0J7D"}
|
package/dist/args.js
CHANGED
|
@@ -13,6 +13,8 @@ const COMMANDS = new Set([
|
|
|
13
13
|
'logout',
|
|
14
14
|
'whoami',
|
|
15
15
|
'maps',
|
|
16
|
+
'team',
|
|
17
|
+
'rules',
|
|
16
18
|
'help',
|
|
17
19
|
'version',
|
|
18
20
|
]);
|
|
@@ -22,7 +24,14 @@ const COMMANDS = new Set([
|
|
|
22
24
|
* command without deciding which side of the line it falls on is a compile
|
|
23
25
|
* error rather than an accident.
|
|
24
26
|
*/
|
|
25
|
-
export const CLOUD_COMMANDS = new Set([
|
|
27
|
+
export const CLOUD_COMMANDS = new Set([
|
|
28
|
+
'login',
|
|
29
|
+
'logout',
|
|
30
|
+
'whoami',
|
|
31
|
+
'maps',
|
|
32
|
+
'team',
|
|
33
|
+
'rules',
|
|
34
|
+
]);
|
|
26
35
|
const SECRET_POLICIES = new Set(['redact', 'warn', 'off']);
|
|
27
36
|
const LANGUAGE_VALUES = new Set([...LANGUAGES, 'auto']);
|
|
28
37
|
function requireValue(flag, value) {
|
|
@@ -47,6 +56,8 @@ export function parseArgs(argv) {
|
|
|
47
56
|
force: false,
|
|
48
57
|
instance: null,
|
|
49
58
|
mapsAction: null,
|
|
59
|
+
teamAction: null,
|
|
60
|
+
rulesAction: null,
|
|
50
61
|
};
|
|
51
62
|
if (argv.length === 0)
|
|
52
63
|
return parsed;
|
|
@@ -70,6 +81,26 @@ export function parseArgs(argv) {
|
|
|
70
81
|
i = 2;
|
|
71
82
|
}
|
|
72
83
|
}
|
|
84
|
+
if (parsed.command === 'team') {
|
|
85
|
+
const verb = argv[1];
|
|
86
|
+
if (verb !== undefined && !verb.startsWith('-')) {
|
|
87
|
+
if (verb !== 'unlock' && verb !== 'lock') {
|
|
88
|
+
throw new UsageError(`unknown team action "${verb}" — expected unlock or lock`);
|
|
89
|
+
}
|
|
90
|
+
parsed.teamAction = verb;
|
|
91
|
+
i = 2;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (parsed.command === 'rules') {
|
|
95
|
+
const verb = argv[1];
|
|
96
|
+
if (verb !== undefined && !verb.startsWith('-')) {
|
|
97
|
+
if (verb !== 'pull') {
|
|
98
|
+
throw new UsageError(`unknown rules action "${verb}" — expected pull`);
|
|
99
|
+
}
|
|
100
|
+
parsed.rulesAction = verb;
|
|
101
|
+
i = 2;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
73
104
|
}
|
|
74
105
|
for (; i < argv.length; i++) {
|
|
75
106
|
const arg = argv[i];
|
package/dist/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,2EAA2E;AAC3E,8DAA8D;AAE9D,OAAO,EAAE,SAAS,EAAsC,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
package/dist/cloud-commands.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TeamLayer } from '@veilio-inc/engine';
|
|
1
2
|
import { type Io } from './commands.js';
|
|
2
3
|
/**
|
|
3
4
|
* Sign in, and store the credential only if the account can actually use it.
|
|
@@ -67,4 +68,33 @@ export declare function runMapsPull(id: string | null, mapPath: string, io: Io,
|
|
|
67
68
|
}): Promise<number>;
|
|
68
69
|
/** Upload the local map. Same endpoint, same quotas, same validation. */
|
|
69
70
|
export declare function runMapsPush(name: string | null, mapPath: string, io: Io): Promise<number>;
|
|
71
|
+
/**
|
|
72
|
+
* Fetch the account's custom rules so `veilio scrub` can apply them offline.
|
|
73
|
+
*
|
|
74
|
+
* Merged here, in the web app's order, so every later scrub applies exactly
|
|
75
|
+
* what the browser would. A plan without custom rules REMOVES any cache left
|
|
76
|
+
* from before: rules the account can no longer use must not go on shaping
|
|
77
|
+
* its output. Any other failure keeps the cache and says so.
|
|
78
|
+
*/
|
|
79
|
+
export declare function runRulesPull(io: Io): Promise<number>;
|
|
80
|
+
/**
|
|
81
|
+
* Unlock this team's keys for processes that cannot ask for a passphrase.
|
|
82
|
+
*
|
|
83
|
+
* The MCP server starts inside a coding agent with no terminal attached, so the
|
|
84
|
+
* keys it needs have to be put on disk by a run like this one, where a person
|
|
85
|
+
* is present to type the passphrase. See `team-unlock.ts` for what is stored
|
|
86
|
+
* and why it is the team key rather than the vault key.
|
|
87
|
+
*/
|
|
88
|
+
export declare function runTeamUnlock(io: Io, days?: number): Promise<number>;
|
|
89
|
+
/** Remove the unlocked team keys. */
|
|
90
|
+
export declare function runTeamLock(io: Io): number;
|
|
91
|
+
/**
|
|
92
|
+
* The team's maps for `veilio restore` (spec 028), handed to the local command
|
|
93
|
+
* as a callback so that command stays unlinked from the network. Signed out:
|
|
94
|
+
* no request, no layer - a local restore, as before.
|
|
95
|
+
*/
|
|
96
|
+
export declare function teamLayerFor(home: string | undefined, missing: readonly string[]): Promise<{
|
|
97
|
+
team: TeamLayer | null;
|
|
98
|
+
note: string | null;
|
|
99
|
+
}>;
|
|
70
100
|
//# sourceMappingURL=cloud-commands.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-commands.d.ts","sourceRoot":"","sources":["../src/cloud-commands.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloud-commands.d.ts","sourceRoot":"","sources":["../src/cloud-commands.ts"],"names":[],"mappings":"AAuBA,OAAO,EAeL,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAA;AAa3B,OAAO,EAAuB,KAAK,EAAE,EAAE,MAAM,eAAe,CAAA;AAc5D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2E/E;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgFvD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,CAQxC;AAyDD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBzD;AA+CD;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,MAAM,GAAG,IAAI,EACjB,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,EAAE,EACN,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC,OAAO,CAAC,MAAM,CAAC,CA+GjB;AAED,yEAAyE;AACzE,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgE/F;AAcD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAqC1D;AAID;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmH1E;AAED,qCAAqC;AACrC,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,CAgB1C;AAID;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,OAAO,CAAC;IAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAO1D"}
|
package/dist/cloud-commands.js
CHANGED
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
// with no account touches nothing (FR-002). Putting these here is what keeps
|
|
7
7
|
// that walk meaningful instead of something to be deleted the moment it becomes
|
|
8
8
|
// inconvenient.
|
|
9
|
-
import { CloudError, createMap, getMap, getVault, listMaps, request, } from './cloud.js';
|
|
10
|
-
import { checkVaultVerifier, decryptMapFromVault, deriveVaultKey, encryptMapForVault, fromBase64, parseVaultEnvelope, } from '@veilio-inc/engine';
|
|
9
|
+
import { CloudError, createMap, getMap, getVault, getUserKeys, getTeamKeyWraps, listMaps, listRules, request, } from './cloud.js';
|
|
10
|
+
import { checkVaultVerifier, decryptMapFromVault, deriveVaultKey, unwrapPrivateKey, unwrapTeamKey, exportTeamKey, encryptMapForVault, fromBase64, parseVaultEnvelope, importTeamKey, decryptTeamMapWithAny, } from '@veilio-inc/engine';
|
|
11
11
|
import { loadMap, loadRemote, saveMap } from './store.js';
|
|
12
|
+
import { resolveTeamNamespace, teamLayerNote } from './team-namespace.js';
|
|
13
|
+
import { readTeamUnlock, removeTeamUnlock, writeTeamUnlock, teamUnlockPath, expiryFrom, } from './team-unlock.js';
|
|
12
14
|
import { readCredential, removeCredential, writeCredential } from './credential.js';
|
|
15
|
+
import { mergeRules, removeRules, rulesPath, writeRules } from './rules.js';
|
|
13
16
|
import { EXIT_ERROR, EXIT_OK } from './commands.js';
|
|
14
17
|
import { DEFAULT_INSTANCE } from './cloud.js';
|
|
15
18
|
/** The endpoint a sign-in is checked against. See `runLogin`. */
|
|
@@ -61,6 +64,32 @@ export async function runLogin(instance, io) {
|
|
|
61
64
|
catch (err) {
|
|
62
65
|
return reportLoginFailure(err, io);
|
|
63
66
|
}
|
|
67
|
+
// A second factor. The token that came back is a challenge, not a session:
|
|
68
|
+
// storing it made every later command fail, and the failure read as a wrong
|
|
69
|
+
// password (found on staging, 2026-09-26). Ask for the code and complete the
|
|
70
|
+
// sign-in the way the web app does.
|
|
71
|
+
if (session.secondFactorRequired === true) {
|
|
72
|
+
const code = (await requirePrompt(io, 'Authentication code (or a recovery code): ')).trim();
|
|
73
|
+
if (code === '') {
|
|
74
|
+
io.stderr('veilio: no authentication code given\n');
|
|
75
|
+
return EXIT_ERROR;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
session = await request('/api/auth/2fa/verify', {
|
|
79
|
+
credential: { token: session.token, account, instance: base },
|
|
80
|
+
method: 'POST',
|
|
81
|
+
body: { code },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err instanceof CloudError && err.kind === 'unauthenticated') {
|
|
86
|
+
io.stderr('veilio: that authentication code was not accepted, or it expired. ' +
|
|
87
|
+
'Run `veilio login` again with a fresh code.\n');
|
|
88
|
+
return EXIT_ERROR;
|
|
89
|
+
}
|
|
90
|
+
return reportLoginFailure(err, io);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
64
93
|
if (typeof session.token !== 'string' || session.token === '') {
|
|
65
94
|
// A 200 with no token is not a sign-in. Writing an empty credential here
|
|
66
95
|
// would produce a "signed in" state that fails on every use.
|
|
@@ -104,7 +133,14 @@ export async function runLogout(io) {
|
|
|
104
133
|
if (err instanceof CloudError && err.kind === 'unauthenticated') {
|
|
105
134
|
// Already revoked, or expired. The server-side state is what logout
|
|
106
135
|
// wanted, so finishing the job locally is correct rather than an error.
|
|
136
|
+
//
|
|
137
|
+
// "Locally" has to include the team keys. They outlive the session that
|
|
138
|
+
// fetched them — a revoked token stops working, an unlocked team key goes
|
|
139
|
+
// on opening the team's maps — so leaving them here would be the one
|
|
140
|
+
// branch where signing out quietly kept the more dangerous half.
|
|
107
141
|
try {
|
|
142
|
+
removeTeamUnlock(io.home);
|
|
143
|
+
removeRules(io.home);
|
|
108
144
|
removeCredential(io.home);
|
|
109
145
|
}
|
|
110
146
|
catch (removeErr) {
|
|
@@ -121,6 +157,28 @@ export async function runLogout(io) {
|
|
|
121
157
|
'To end the session now, sign out from the web app.\n');
|
|
122
158
|
return EXIT_ERROR;
|
|
123
159
|
}
|
|
160
|
+
// Before the credential, because this is key material rather than a token
|
|
161
|
+
// the server has already revoked. Signing out while the team's keys stay
|
|
162
|
+
// readable on disk is exactly the gap between what a person believes they
|
|
163
|
+
// gave up and what is still there.
|
|
164
|
+
try {
|
|
165
|
+
removeTeamUnlock(io.home);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
io.stderr(`veilio: the session was revoked, but the unlocked team keys could not be removed ` +
|
|
169
|
+
`(${err instanceof Error ? err.message : String(err)}). Delete ${teamUnlockPath(io.home)} by hand.\n`);
|
|
170
|
+
return EXIT_ERROR;
|
|
171
|
+
}
|
|
172
|
+
// The rules are the account's too, and a whitelist among them keeps names
|
|
173
|
+
// readable: they go with the session rather than outliving it.
|
|
174
|
+
try {
|
|
175
|
+
removeRules(io.home);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
io.stderr(`veilio: the session was revoked, but the cached custom rules could not be removed ` +
|
|
179
|
+
`(${err instanceof Error ? err.message : String(err)}). Delete ${rulesPath(io.home)} by hand.\n`);
|
|
180
|
+
return EXIT_ERROR;
|
|
181
|
+
}
|
|
124
182
|
try {
|
|
125
183
|
removeCredential(io.home);
|
|
126
184
|
}
|
|
@@ -229,6 +287,41 @@ export async function runMapsList(io) {
|
|
|
229
287
|
}
|
|
230
288
|
return EXIT_OK;
|
|
231
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Open a team map with the team keys `veilio team unlock` stored.
|
|
292
|
+
*
|
|
293
|
+
* The same keys the MCP server reads, for the same reason: this machine may
|
|
294
|
+
* have nobody present to type a vault passphrase, and the team key - not the
|
|
295
|
+
* vault key - is what a team map is sealed under. Reports and returns null
|
|
296
|
+
* when it cannot, so the caller writes nothing.
|
|
297
|
+
*/
|
|
298
|
+
async function openTeamEnvelope(credential, mapData, io) {
|
|
299
|
+
const unlock = readTeamUnlock({ instance: credential.instance, account: credential.account }, io.home);
|
|
300
|
+
if (!unlock || unlock.keys.length === 0) {
|
|
301
|
+
io.stderr('veilio: that is a team map, and no team key is unlocked on this machine. ' +
|
|
302
|
+
'Run `veilio team unlock` first. Nothing was written.\n');
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
const keys = [];
|
|
306
|
+
for (const stored of unlock.keys) {
|
|
307
|
+
try {
|
|
308
|
+
keys.push({ version: stored.version, key: await importTeamKey(stored.key) });
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// A damaged entry; the others may still open the map.
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
return await decryptTeamMapWithAny(keys, JSON.parse(mapData));
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
io.stderr(`veilio: that team map could not be opened with the unlocked team keys — ` +
|
|
320
|
+
`${err instanceof Error ? err.message : String(err)}. ` +
|
|
321
|
+
'If the team key was rotated since, run `veilio team unlock` again. Nothing was written.\n');
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
232
325
|
/**
|
|
233
326
|
* Pull a map into the local store: decrypt FIRST, write second.
|
|
234
327
|
*
|
|
@@ -256,11 +349,27 @@ export async function runMapsPull(id, mapPath, io, options = {}) {
|
|
|
256
349
|
catch (err) {
|
|
257
350
|
return reportCloudFailure(err, io);
|
|
258
351
|
}
|
|
259
|
-
//
|
|
260
|
-
// the
|
|
261
|
-
|
|
352
|
+
// Every map is an envelope the server cannot open: a personal map under the
|
|
353
|
+
// vault key, a team map under the team key. Which one is written on the
|
|
354
|
+
// envelope itself.
|
|
262
355
|
if (typeof remote.map_data !== 'string') {
|
|
263
|
-
map
|
|
356
|
+
io.stderr('veilio: that map is not an encrypted envelope, so it was not written.\n');
|
|
357
|
+
return EXIT_ERROR;
|
|
358
|
+
}
|
|
359
|
+
let envelope;
|
|
360
|
+
try {
|
|
361
|
+
envelope = JSON.parse(remote.map_data);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
io.stderr('veilio: that map is not an encrypted envelope, so it was not written.\n');
|
|
365
|
+
return EXIT_ERROR;
|
|
366
|
+
}
|
|
367
|
+
let map;
|
|
368
|
+
if (envelope.alg === 'AES-256-GCM-TEAM') {
|
|
369
|
+
const opened = await openTeamEnvelope(credential, remote.map_data, io);
|
|
370
|
+
if (!opened)
|
|
371
|
+
return EXIT_ERROR;
|
|
372
|
+
map = opened;
|
|
264
373
|
}
|
|
265
374
|
else {
|
|
266
375
|
let vault;
|
|
@@ -391,4 +500,173 @@ function notSignedIn(io) {
|
|
|
391
500
|
function reportCloudFailure(err, io) {
|
|
392
501
|
return reportLoginFailure(err, io);
|
|
393
502
|
}
|
|
503
|
+
// ─── rules: pull ──────────────────────────────────────────────────────────────
|
|
504
|
+
/**
|
|
505
|
+
* Fetch the account's custom rules so `veilio scrub` can apply them offline.
|
|
506
|
+
*
|
|
507
|
+
* Merged here, in the web app's order, so every later scrub applies exactly
|
|
508
|
+
* what the browser would. A plan without custom rules REMOVES any cache left
|
|
509
|
+
* from before: rules the account can no longer use must not go on shaping
|
|
510
|
+
* its output. Any other failure keeps the cache and says so.
|
|
511
|
+
*/
|
|
512
|
+
export async function runRulesPull(io) {
|
|
513
|
+
const credential = readCredential(io.home);
|
|
514
|
+
if (!credential)
|
|
515
|
+
return notSignedIn(io);
|
|
516
|
+
let fetched;
|
|
517
|
+
try {
|
|
518
|
+
fetched = await listRules(credential);
|
|
519
|
+
}
|
|
520
|
+
catch (err) {
|
|
521
|
+
if (err instanceof CloudError && err.kind === 'unentitled') {
|
|
522
|
+
const removed = removeRules(io.home);
|
|
523
|
+
io.stderr(`veilio: this account's plan does not include custom rules${removed ? ' - the cached rules were removed' : ''}.\n`);
|
|
524
|
+
return EXIT_ERROR;
|
|
525
|
+
}
|
|
526
|
+
return reportCloudFailure(err, io);
|
|
527
|
+
}
|
|
528
|
+
const personal = fetched.rules ?? [];
|
|
529
|
+
const team = fetched.teamRules ?? [];
|
|
530
|
+
const rules = mergeRules(personal, team);
|
|
531
|
+
writeRules({
|
|
532
|
+
instance: credential.instance,
|
|
533
|
+
account: credential.account,
|
|
534
|
+
pulledAt: new Date().toISOString(),
|
|
535
|
+
rules,
|
|
536
|
+
}, io.home);
|
|
537
|
+
const skipped = personal.length + team.length - rules.length;
|
|
538
|
+
io.stdout(`Pulled ${rules.length} custom rule${rules.length === 1 ? '' : 's'} ` +
|
|
539
|
+
`(${personal.length} personal, ${team.length} team${skipped > 0 ? `, ${skipped} disabled and skipped` : ''}). ` +
|
|
540
|
+
'`veilio scrub` applies them.\n');
|
|
541
|
+
return EXIT_OK;
|
|
542
|
+
}
|
|
543
|
+
// ─── team: unlock, lock ──────────────────────────────────────────────────────
|
|
544
|
+
/**
|
|
545
|
+
* Unlock this team's keys for processes that cannot ask for a passphrase.
|
|
546
|
+
*
|
|
547
|
+
* The MCP server starts inside a coding agent with no terminal attached, so the
|
|
548
|
+
* keys it needs have to be put on disk by a run like this one, where a person
|
|
549
|
+
* is present to type the passphrase. See `team-unlock.ts` for what is stored
|
|
550
|
+
* and why it is the team key rather than the vault key.
|
|
551
|
+
*/
|
|
552
|
+
export async function runTeamUnlock(io, days) {
|
|
553
|
+
const credential = readCredential(io.home);
|
|
554
|
+
if (!credential) {
|
|
555
|
+
io.stderr('veilio: not signed in. Run `veilio login` first.\n');
|
|
556
|
+
return EXIT_ERROR;
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
const list = await listMaps(credential);
|
|
560
|
+
const teamId = list.team?.id;
|
|
561
|
+
if (!teamId) {
|
|
562
|
+
io.stderr('veilio: this account is not in a team, so there are no team keys to unlock. ' +
|
|
563
|
+
'Shared placeholders are a Team-plan feature.\n');
|
|
564
|
+
return EXIT_ERROR;
|
|
565
|
+
}
|
|
566
|
+
const mine = await getUserKeys(credential);
|
|
567
|
+
if (!mine.initialized) {
|
|
568
|
+
io.stderr('veilio: this account has no keypair yet. Open the web app once — that is where a ' +
|
|
569
|
+
'keypair is created and where a teammate grants you the team key.\n');
|
|
570
|
+
return EXIT_ERROR;
|
|
571
|
+
}
|
|
572
|
+
const { wraps } = await getTeamKeyWraps(credential, teamId);
|
|
573
|
+
if (wraps.length === 0) {
|
|
574
|
+
io.stderr('veilio: no teammate has granted you the team key yet. Ask someone already in the ' +
|
|
575
|
+
'team to open the web app, which is what performs the grant.\n');
|
|
576
|
+
return EXIT_ERROR;
|
|
577
|
+
}
|
|
578
|
+
const vault = await getVault(credential);
|
|
579
|
+
if (!vault.initialized) {
|
|
580
|
+
io.stderr('veilio: this account has no vault, so there is no key to unwrap anything with. ' +
|
|
581
|
+
'Create one in the web app first.\n');
|
|
582
|
+
return EXIT_ERROR;
|
|
583
|
+
}
|
|
584
|
+
const passphrase = io.password ? await io.password('Vault passphrase: ') : '';
|
|
585
|
+
if (passphrase === '') {
|
|
586
|
+
io.stderr('veilio: no passphrase given\n');
|
|
587
|
+
return EXIT_ERROR;
|
|
588
|
+
}
|
|
589
|
+
const vaultKey = await deriveVaultKey(passphrase, fromBase64(vault.salt), vault.kdf ? { name: 'PBKDF2-SHA256', iterations: vault.kdf.iterations } : undefined);
|
|
590
|
+
// Named as a wrong passphrase here rather than surfacing later as an
|
|
591
|
+
// authentication-tag failure that reads like corruption.
|
|
592
|
+
if (!(await checkVaultVerifier(vaultKey, vault.verifier))) {
|
|
593
|
+
io.stderr('veilio: that vault passphrase is not right. Nothing was written.\n');
|
|
594
|
+
return EXIT_ERROR;
|
|
595
|
+
}
|
|
596
|
+
const privateKey = await unwrapPrivateKey(vaultKey, mine.privateKeyEncrypted);
|
|
597
|
+
const keys = [];
|
|
598
|
+
for (const wrap of wraps) {
|
|
599
|
+
try {
|
|
600
|
+
const teamKey = await unwrapTeamKey(wrap.wrapped_key, privateKey, {
|
|
601
|
+
teamId,
|
|
602
|
+
version: wrap.version,
|
|
603
|
+
myPublicKey: mine.publicKey,
|
|
604
|
+
});
|
|
605
|
+
keys.push({ version: wrap.version, key: await exportTeamKey(teamKey) });
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
// A wrap this account cannot open — from a granter whose key has since
|
|
609
|
+
// changed, most likely. Skipped rather than fatal: another version may
|
|
610
|
+
// still open most of the team's maps, and none opening is reported below.
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (keys.length === 0) {
|
|
615
|
+
io.stderr('veilio: none of the team key wraps held for this account could be opened. ' +
|
|
616
|
+
'Ask a teammate to grant access again from the web app.\n');
|
|
617
|
+
return EXIT_ERROR;
|
|
618
|
+
}
|
|
619
|
+
const now = new Date();
|
|
620
|
+
const expiresAt = expiryFrom(now, days);
|
|
621
|
+
writeTeamUnlock({
|
|
622
|
+
v: 1,
|
|
623
|
+
instance: credential.instance,
|
|
624
|
+
account: credential.account,
|
|
625
|
+
teamId,
|
|
626
|
+
expiresAt,
|
|
627
|
+
keys,
|
|
628
|
+
}, io.home);
|
|
629
|
+
const versions = keys.map((k) => `v${k.version}`).join(', ');
|
|
630
|
+
io.stdout(`Unlocked ${keys.length === 1 ? 'the team key' : 'team keys'} (${versions}) until ` +
|
|
631
|
+
`${expiresAt}.\n` +
|
|
632
|
+
`Stored in ${teamUnlockPath(io.home)}, readable only by you. ` +
|
|
633
|
+
`Run \`veilio team lock\` to remove them.\n`);
|
|
634
|
+
return EXIT_OK;
|
|
635
|
+
}
|
|
636
|
+
catch (err) {
|
|
637
|
+
return reportCloudFailure(err, io);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/** Remove the unlocked team keys. */
|
|
641
|
+
export function runTeamLock(io) {
|
|
642
|
+
try {
|
|
643
|
+
if (!removeTeamUnlock(io.home)) {
|
|
644
|
+
io.stdout('No team keys were unlocked.\n');
|
|
645
|
+
return EXIT_OK;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
catch (err) {
|
|
649
|
+
io.stderr(`veilio: the unlocked team keys could not be removed ` +
|
|
650
|
+
`(${err instanceof Error ? err.message : String(err)}). ` +
|
|
651
|
+
`Delete ${teamUnlockPath(io.home)} by hand.\n`);
|
|
652
|
+
return EXIT_ERROR;
|
|
653
|
+
}
|
|
654
|
+
io.stdout('Team keys locked.\n');
|
|
655
|
+
return EXIT_OK;
|
|
656
|
+
}
|
|
657
|
+
// ─── restore: the team layer ────────────────────────────────────────────────
|
|
658
|
+
/**
|
|
659
|
+
* The team's maps for `veilio restore` (spec 028), handed to the local command
|
|
660
|
+
* as a callback so that command stays unlinked from the network. Signed out:
|
|
661
|
+
* no request, no layer - a local restore, as before.
|
|
662
|
+
*/
|
|
663
|
+
export async function teamLayerFor(home, missing) {
|
|
664
|
+
// Signed out, the resolver answers 'local' without a request. At most 5 s: a
|
|
665
|
+
// hung instance must not stall a `... | veilio restore > file` pipeline.
|
|
666
|
+
const found = await resolveTeamNamespace(home, { deadlineMs: RESTORE_CLOUD_WAIT_MS });
|
|
667
|
+
if (found.status === 'team' && found.conflictDetection)
|
|
668
|
+
return { team: found.analysis, note: null };
|
|
669
|
+
return { team: null, note: teamLayerNote(found, missing) };
|
|
670
|
+
}
|
|
671
|
+
const RESTORE_CLOUD_WAIT_MS = 5000;
|
|
394
672
|
//# sourceMappingURL=cloud-commands.js.map
|