@vatio-ai/cli 0.52.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/vatio.mjs +3 -0
- package/lib/api-client.mjs +85 -34
- package/lib/branch.mjs +51 -0
- package/lib/commands/channels.mjs +39 -20
- package/lib/commands/chat.mjs +1 -1
- package/lib/commands/deploy.mjs +17 -12
- package/lib/commands/env.mjs +90 -0
- package/lib/commands/knowledge.mjs +99 -81
- package/lib/commands/mcp.mjs +41 -22
- package/lib/commands/misc.mjs +7 -9
- package/lib/commands/workspace-admin.mjs +66 -30
- package/lib/help.mjs +35 -18
- package/lib/http.mjs +4 -1
- package/lib/support.mjs +23 -10
- package/package.json +1 -1
|
@@ -1,74 +1,91 @@
|
|
|
1
1
|
// secrets / tokens / widget — the three things a workspace has that its
|
|
2
2
|
// manifest deliberately does not carry.
|
|
3
3
|
//
|
|
4
|
-
// A secret is a credential, a publishable token is a credential
|
|
5
|
-
// screen is a read of what the platform
|
|
6
|
-
//
|
|
7
|
-
// vatio.yml and all of them are here.
|
|
4
|
+
// A secret is a credential, a publishable token is a credential (with the
|
|
5
|
+
// origins it accepts), and the widget screen is a read of what the platform
|
|
6
|
+
// will actually enforce, edited in the console. None of them are in vatio.yml.
|
|
8
7
|
|
|
9
8
|
import { deployClient } from "./deploy.mjs";
|
|
10
|
-
import { askSecret, fail, paint, takeValue } from "../support.mjs";
|
|
9
|
+
import { askSecret, branchDefault, fail, paint, takeValue } from "../support.mjs";
|
|
11
10
|
|
|
12
|
-
// live, preview, or a
|
|
13
|
-
// first two, which meant a workspace could deploy to `pr-42` and then have no
|
|
14
|
-
// way to mint the publishable token that makes it embeddable.
|
|
11
|
+
// live, preview, or a branch's own like fix-pagos.
|
|
15
12
|
const ENVIRONMENT_NAME = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
|
|
16
13
|
const ENVIRONMENT_MAX = 40;
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// On a branch, its environment unless told otherwise (see branch.mjs).
|
|
16
|
+
function environmentOption(args, fallback, cwd) {
|
|
17
|
+
const given = takeValue(args, "--env") ?? takeValue(args, "--environment") ?? takeValue(args, "-e");
|
|
18
|
+
const value = given ?? branchDefault(cwd, fallback);
|
|
19
|
+
if (value === null) return null;
|
|
21
20
|
if (String(value).length > ENVIRONMENT_MAX || !ENVIRONMENT_NAME.test(String(value))) {
|
|
22
|
-
fail(`Unknown environment "${value}" (use live, preview, or a
|
|
21
|
+
fail(`Unknown environment "${value}" (use live, preview, or a branch's environment like fix-pagos)`);
|
|
23
22
|
}
|
|
24
23
|
return String(value);
|
|
25
24
|
}
|
|
26
25
|
|
|
26
|
+
// Without an environment a secret is the value everywhere. On a branch, or
|
|
27
|
+
// with --env, it is the value only that environment uses -- a preview pointing
|
|
28
|
+
// its tools at staging while live keeps production.
|
|
27
29
|
export async function secrets(config, args) {
|
|
28
30
|
const workspace = config.resolveWorkspaceRequired();
|
|
29
31
|
const client = deployClient(config, workspace);
|
|
32
|
+
const environment = environmentOption(args, null, config.workspaceRoot);
|
|
33
|
+
const where = environment ? `${environment} on workspace ${workspace}` : `workspace ${workspace}`;
|
|
30
34
|
const sub = args.shift() ?? "list";
|
|
31
35
|
|
|
32
36
|
if (sub === "list") {
|
|
33
|
-
const payload = await client.listSecrets();
|
|
37
|
+
const payload = await client.listSecrets({ environment });
|
|
34
38
|
const keys = asArray(payload.secrets);
|
|
35
39
|
if (keys.length === 0) {
|
|
36
|
-
console.log(`No secrets on
|
|
40
|
+
console.log(`No secrets on ${where}`);
|
|
37
41
|
console.log(" Set one: vatio secrets set STRIPE_KEY sk_live_…");
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
40
44
|
// Keys only, never values. The platform does not hand a value back once it
|
|
41
45
|
// is stored, and a CLI that printed them would be a CLI that leaks them
|
|
42
46
|
// into a terminal scrollback.
|
|
43
|
-
console.log(`Secrets on
|
|
44
|
-
for (const secret of keys)
|
|
47
|
+
console.log(`Secrets on ${where} (${keys.length}):`);
|
|
48
|
+
for (const secret of keys) {
|
|
49
|
+
const scope = secret.environment ? ` (only ${secret.environment})` : "";
|
|
50
|
+
console.log(` ${secret.key ?? secret}${scope}`);
|
|
51
|
+
}
|
|
45
52
|
return;
|
|
46
53
|
}
|
|
47
54
|
if (sub === "set") {
|
|
48
55
|
const key = args.shift();
|
|
49
|
-
if (!key) fail("Usage: vatio secrets set KEY [VALUE]");
|
|
56
|
+
if (!key) fail("Usage: vatio secrets set KEY [VALUE] [--env NAME]");
|
|
50
57
|
// Asked for rather than typed on the command line, where it would stay in
|
|
51
58
|
// shell history.
|
|
52
59
|
const value = args.shift() ?? (await askSecret(`${paint("green", "?")} Paste your secret: `));
|
|
53
60
|
if (value === "") fail(`No value given for ${key}`);
|
|
54
|
-
await client.upsertSecret({ key, value });
|
|
55
|
-
console.log(`${paint("green", "✓")} Set secret ${key} on
|
|
61
|
+
await client.upsertSecret({ key, value, environment });
|
|
62
|
+
console.log(`${paint("green", "✓")} Set secret ${key} on ${where}`);
|
|
56
63
|
return;
|
|
57
64
|
}
|
|
58
65
|
if (sub === "rm" || sub === "remove" || sub === "delete") {
|
|
59
66
|
const key = args.shift();
|
|
60
|
-
if (!key) fail("Usage: vatio secrets rm KEY");
|
|
61
|
-
await client.deleteSecret({ key });
|
|
62
|
-
console.log(`Removed ${key} from
|
|
67
|
+
if (!key) fail("Usage: vatio secrets rm KEY [--env NAME]");
|
|
68
|
+
await client.deleteSecret({ key, environment });
|
|
69
|
+
console.log(`Removed ${key} from ${where}`);
|
|
63
70
|
return;
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
fail("Usage: vatio secrets list|set KEY [VALUE]|rm KEY");
|
|
73
|
+
fail("Usage: vatio secrets list|set KEY [VALUE]|rm KEY [--env NAME]");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Repeatable: `--origin https://a.com --origin https://b.com`.
|
|
77
|
+
function takeOrigins(args) {
|
|
78
|
+
const origins = [];
|
|
79
|
+
for (let value = takeValue(args, "--origin"); value !== null; value = takeValue(args, "--origin")) {
|
|
80
|
+
origins.push(value);
|
|
81
|
+
}
|
|
82
|
+
return origins;
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
export async function tokens(config, args) {
|
|
70
|
-
const environment = environmentOption(args, "live");
|
|
86
|
+
const environment = environmentOption(args, "live", config.workspaceRoot);
|
|
71
87
|
const label = takeValue(args, "--label") ?? takeValue(args, "-l");
|
|
88
|
+
const origins = takeOrigins(args);
|
|
72
89
|
const workspace = config.resolveWorkspaceRequired();
|
|
73
90
|
const client = deployClient(config, workspace);
|
|
74
91
|
const sub = args.shift() ?? "list";
|
|
@@ -78,7 +95,7 @@ export async function tokens(config, args) {
|
|
|
78
95
|
const list = asArray(payload.publishable_tokens);
|
|
79
96
|
if (list.length === 0) {
|
|
80
97
|
console.log(`No publishable tokens on workspace ${workspace}`);
|
|
81
|
-
console.log(" Create one: vatio tokens create --env live");
|
|
98
|
+
console.log(" Create one: vatio tokens create --env live --origin https://your-site.com");
|
|
82
99
|
return;
|
|
83
100
|
}
|
|
84
101
|
console.log(`Publishable tokens on workspace ${workspace} (${list.length}):`);
|
|
@@ -86,12 +103,14 @@ export async function tokens(config, args) {
|
|
|
86
103
|
const parts = [token.key_prefix ?? token.prefix, token.environment];
|
|
87
104
|
if (token.label) parts.push(token.label);
|
|
88
105
|
console.log(` ${parts.filter(Boolean).join(" ")}`);
|
|
106
|
+
console.log(` origins: ${originsLine(token.allowed_origins)}`);
|
|
89
107
|
}
|
|
90
108
|
return;
|
|
91
109
|
}
|
|
92
110
|
if (sub === "create") {
|
|
93
|
-
const payload = await client.createPublishableToken({ environment, label });
|
|
111
|
+
const payload = await client.createPublishableToken({ environment, label, allowedOrigins: origins });
|
|
94
112
|
console.log(`Created a publishable token for ${environment} on workspace ${workspace}`);
|
|
113
|
+
console.log(` origins: ${originsLine(payload.allowed_origins)}`);
|
|
95
114
|
console.log("");
|
|
96
115
|
// Shown in full exactly once: the platform stores a digest, so this is the
|
|
97
116
|
// only moment the value exists anywhere the developer can copy it.
|
|
@@ -100,6 +119,13 @@ export async function tokens(config, args) {
|
|
|
100
119
|
console.log("Copy it now — it is not shown again.");
|
|
101
120
|
return;
|
|
102
121
|
}
|
|
122
|
+
if (sub === "origins") {
|
|
123
|
+
const prefix = args.shift();
|
|
124
|
+
if (!prefix) fail("Usage: vatio tokens origins PREFIX [URL...] (no URL clears the list)");
|
|
125
|
+
const payload = await client.setPublishableTokenOrigins({ prefix, allowedOrigins: args.splice(0) });
|
|
126
|
+
console.log(`Origins for ${prefix}: ${originsLine(payload.allowed_origins)}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
103
129
|
if (sub === "revoke") {
|
|
104
130
|
const prefix = args.shift();
|
|
105
131
|
if (!prefix) fail("Usage: vatio tokens revoke PREFIX");
|
|
@@ -108,18 +134,28 @@ export async function tokens(config, args) {
|
|
|
108
134
|
return;
|
|
109
135
|
}
|
|
110
136
|
|
|
111
|
-
fail(
|
|
137
|
+
fail(
|
|
138
|
+
"Usage: vatio tokens list|create [--env live|preview|NAME] [--label NAME] [--origin URL]...|" +
|
|
139
|
+
"origins PREFIX URL...|revoke PREFIX"
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// An empty list is not "anywhere": only Vatio's own pages can use the token.
|
|
144
|
+
function originsLine(origins) {
|
|
145
|
+
const list = asArray(origins);
|
|
146
|
+
return list.length > 0 ? list.join(", ") : "none (add one with `vatio tokens origins PREFIX URL`)";
|
|
112
147
|
}
|
|
113
148
|
|
|
114
149
|
export async function widget(config, args) {
|
|
115
|
-
const environment = environmentOption(args, "live");
|
|
150
|
+
const environment = environmentOption(args, "live", config.workspaceRoot);
|
|
116
151
|
const workspace = config.resolveWorkspaceRequired();
|
|
117
152
|
const payload = await deployClient(config, workspace).widget();
|
|
118
153
|
|
|
119
154
|
console.log(JSON.stringify(payload, null, 2));
|
|
120
155
|
console.log("");
|
|
121
|
-
console.log(
|
|
122
|
-
console.log(`
|
|
156
|
+
console.log(`The widget's look is edited in the console: ${payload.brand_url ?? "the workspace's Brand page"}`);
|
|
157
|
+
console.log(`Each token's allowed origins: ${payload.tokens_url ?? "the workspace's Tokens page"}`);
|
|
158
|
+
console.log(`A token for this environment: vatio tokens create --env ${environment} --origin https://your-site.com`);
|
|
123
159
|
}
|
|
124
160
|
|
|
125
161
|
function asArray(value) {
|
package/lib/help.mjs
CHANGED
|
@@ -12,7 +12,10 @@ A workspace is any directory with a vatio.yml. Every command except init reads
|
|
|
12
12
|
\`workspace:\` from the vatio.yml at or above the cwd — there is no --workspace
|
|
13
13
|
flag, the directory decides.
|
|
14
14
|
|
|
15
|
-
--env NAME targets
|
|
15
|
+
--env NAME targets an environment: live, preview, or a branch's own. On a git
|
|
16
|
+
branch other than the default one, the branch's environment is the default
|
|
17
|
+
(fix/pagos -> fix-pagos): its own agent, knowledge changes and secret values,
|
|
18
|
+
and the one its pull request deploys to. Elsewhere commands default as listed.
|
|
16
19
|
|
|
17
20
|
Getting started:
|
|
18
21
|
init [SLUG] [--name NAME] Create vatio.yml here, and the remote to match
|
|
@@ -24,11 +27,15 @@ flag, the directory decides.
|
|
|
24
27
|
|
|
25
28
|
Deploy:
|
|
26
29
|
push [--env NAME] Validate the workspace, then update a preview
|
|
27
|
-
publish [--env NAME] Promote
|
|
28
|
-
|
|
29
|
-
is connected: merge
|
|
30
|
-
|
|
30
|
+
publish [--env NAME] Promote an environment to live, with its knowledge
|
|
31
|
+
changes. Without --env or a branch, the latest
|
|
32
|
+
push. Refused when GitHub is connected: merge
|
|
33
|
+
the branch's pull request instead
|
|
34
|
+
rollback Put live back to the previous snapshot: config and knowledge
|
|
31
35
|
status Preview and live deployment state
|
|
36
|
+
env [NAME] This environment: token, widget snippet, link,
|
|
37
|
+
knowledge changes and conflicts
|
|
38
|
+
env list | rm NAME Every environment / remove one and what it holds
|
|
32
39
|
diff [--env NAME] What this directory would change (default: preview)
|
|
33
40
|
--env live answers "what would publishing change?"
|
|
34
41
|
diff --stat|--name-only|--format json|--full
|
|
@@ -45,27 +52,37 @@ flag, the directory decides.
|
|
|
45
52
|
kb [list] Bases, and whether a deployed agent reads each
|
|
46
53
|
kb show NAME One base: its sites and its entries
|
|
47
54
|
kb create NAME | rm NAME
|
|
48
|
-
kb write BASE ENTRY [FILE] Write an entry, from FILE or stdin
|
|
49
|
-
kb cat BASE ENTRY
|
|
50
|
-
|
|
55
|
+
kb write BASE ENTRY [FILE] Write an entry in this environment, from FILE or stdin
|
|
56
|
+
kb cat BASE ENTRY [--version N]
|
|
57
|
+
Print an entry's markdown, or one old version
|
|
58
|
+
kb rm-entry BASE ENTRY Delete one entry in this environment
|
|
51
59
|
kb follow BASE URL Read a site into the base, nightly
|
|
52
60
|
kb unfollow BASE URL | refresh BASE [URL]
|
|
53
|
-
kb status BASE What
|
|
54
|
-
|
|
55
|
-
kb
|
|
61
|
+
kb status BASE What this environment changed that live does not
|
|
62
|
+
have, and what live changed underneath
|
|
63
|
+
kb publish BASE Take this environment's changes live, alone
|
|
64
|
+
kb history BASE What reached live, newest first
|
|
65
|
+
(entry commands take --env; --env live writes live)
|
|
56
66
|
|
|
57
67
|
Credentials and the widget:
|
|
58
|
-
secrets list|set KEY [VALUE]|rm KEY
|
|
59
|
-
Without VALUE, set asks for it with the input hidden
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
secrets list|set KEY [VALUE]|rm KEY [--env NAME]
|
|
69
|
+
Without VALUE, set asks for it with the input hidden.
|
|
70
|
+
On a branch or with --env, the value only that
|
|
71
|
+
environment uses; otherwise the value everywhere
|
|
72
|
+
tokens list|create [--env NAME] [--label NAME] [--origin URL]...|revoke PREFIX
|
|
73
|
+
tokens origins PREFIX URL...
|
|
74
|
+
The origins a token is accepted from (replaces the list)
|
|
75
|
+
widget [--env NAME] What the platform enforces, and the tokens there are;
|
|
76
|
+
the look is edited in the console
|
|
62
77
|
auth --new-key Keypair that signs the JWT saying who a visitor is
|
|
63
78
|
|
|
64
|
-
Channels (your own account serves live;
|
|
79
|
+
Channels (your own account serves live; each test phone or account on the shared
|
|
80
|
+
preview reaches the environment it points at -- add and point take --env, and
|
|
81
|
+
default to the branch you are on):
|
|
65
82
|
whatsapp [status]|connect|check|activate|deactivate|disconnect
|
|
66
|
-
whatsapp numbers list|add PHONE|verify PHONE CODE|resend PHONE|remove PHONE
|
|
83
|
+
whatsapp numbers list|add PHONE|point PHONE|verify PHONE CODE|resend PHONE|remove PHONE
|
|
67
84
|
instagram [status]|connect|check|disconnect
|
|
68
|
-
instagram accounts list|add @HANDLE|verify CODE|resend ID|remove ID
|
|
85
|
+
instagram accounts list|add @HANDLE|point ID|verify CODE|resend ID|remove ID
|
|
69
86
|
|
|
70
87
|
Everything else:
|
|
71
88
|
issue "what should change"|list|show ID|comment ID "reply"|--template
|
package/lib/http.mjs
CHANGED
|
@@ -37,7 +37,10 @@ function unprocessableMessage(body) {
|
|
|
37
37
|
.map((entry) => (typeof entry === "object" ? String(entry.message ?? "") : String(entry)));
|
|
38
38
|
if (errors.length > 0) return errors.join("; ");
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// `{ error: { code, message } }` is how the knowledge and token endpoints
|
|
41
|
+
// say it.
|
|
42
|
+
const error = body.error && typeof body.error === "object" && !Array.isArray(body.error) ? body.error.message : body.error;
|
|
43
|
+
const message = body.error_message ?? error;
|
|
41
44
|
if (message != null) return Array.isArray(message) ? message.join("; ") : String(message);
|
|
42
45
|
|
|
43
46
|
return "Unprocessable";
|
package/lib/support.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
|
|
6
|
+
import { branchEnvironment } from "./branch.mjs";
|
|
7
|
+
|
|
6
8
|
export class UserError extends Error {
|
|
7
9
|
constructor(message) {
|
|
8
10
|
super(message);
|
|
@@ -16,15 +18,17 @@ export function fail(message) {
|
|
|
16
18
|
throw new UserError(message);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
// `--env NAME` is the one way to say which
|
|
20
|
-
// live, preview, or a
|
|
21
|
+
// `--env NAME` is the one way to say which environment a command is about:
|
|
22
|
+
// live, preview, or a branch's own like fix-pagos.
|
|
21
23
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// Without it, a workspace checked out on a branch other than the default one
|
|
25
|
+
// means that branch's environment (see branch.mjs), said once on stderr so it
|
|
26
|
+
// is never a surprise. Otherwise the default is the command's own and is passed
|
|
27
|
+
// in, because the sensible answer differs: push writes a preview, tokens are
|
|
28
|
+
// for live. `--as` was the spelling on push and publish and stays as an alias
|
|
29
|
+
// -- it is sitting in CI jobs, and there is nothing wrong with what it does.
|
|
30
|
+
export function takeEnv(args, { fallback = null, cwd = null } = {}) {
|
|
31
|
+
let environment;
|
|
28
32
|
const rest = [];
|
|
29
33
|
|
|
30
34
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -39,13 +43,22 @@ export function takeEnv(args, { fallback = null } = {}) {
|
|
|
39
43
|
continue;
|
|
40
44
|
}
|
|
41
45
|
i += 1;
|
|
42
|
-
if (i >= args.length) fail(`${match[0]} needs
|
|
46
|
+
if (i >= args.length) fail(`${match[0]} needs an environment name (live, preview, or a branch's)`);
|
|
43
47
|
environment = args[i];
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
args.length = 0;
|
|
47
51
|
args.push(...rest);
|
|
48
|
-
return environment;
|
|
52
|
+
return environment !== undefined ? environment : branchDefault(cwd, fallback);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The branch's environment when the workspace is on one, else `fallback`.
|
|
56
|
+
export function branchDefault(cwd, fallback) {
|
|
57
|
+
const fromBranch = cwd ? branchEnvironment(cwd) : null;
|
|
58
|
+
if (!fromBranch) return fallback;
|
|
59
|
+
|
|
60
|
+
process.stderr.write(paint("cyan", `vatio: environment ${fromBranch.environment} (branch ${fromBranch.branch})`) + "\n");
|
|
61
|
+
return fromBranch.environment;
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
export function takeFlag(args, name) {
|