@vatio-ai/cli 0.53.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/bin/vatio.mjs +3 -0
- package/lib/api-client.mjs +79 -33
- 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 +15 -10
- package/lib/commands/env.mjs +90 -0
- package/lib/commands/knowledge.mjs +99 -81
- package/lib/commands/mcp.mjs +36 -18
- package/lib/commands/workspace-admin.mjs +29 -21
- package/lib/help.mjs +30 -16
- package/lib/http.mjs +4 -1
- package/lib/support.mjs +23 -10
- package/package.json +1 -1
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
// vatio.yml is a list of *names*, and a push neither fills nor empties one.
|
|
5
5
|
// That is why this is a command and not part of the manifest.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// preview,
|
|
7
|
+
// The entries are live. Reads and writes here are as one environment sees
|
|
8
|
+
// them: on a branch, the branch's own (see branch.mjs), which keeps the
|
|
9
|
+
// entries it changed on top of live and answers from them in its preview;
|
|
10
|
+
// otherwise `preview`; `--env live` writes live directly, the way a supervisor
|
|
11
|
+
// in the inbox does. `vatio publish`, or merging the branch's pull request,
|
|
12
|
+
// takes the changes live with the agent -- `vatio kb publish` takes them alone.
|
|
13
|
+
// So: write, try it on the branch's preview, publish.
|
|
11
14
|
//
|
|
12
15
|
// A base holds entries, and an entry is markdown. Either you wrote it, or a
|
|
13
16
|
// site wrote it from a page it read — and a site is one URL or one pattern,
|
|
@@ -16,32 +19,48 @@
|
|
|
16
19
|
import { readFileSync, statSync } from "node:fs";
|
|
17
20
|
|
|
18
21
|
import { deployClient } from "./deploy.mjs";
|
|
19
|
-
import { fail } from "../support.mjs";
|
|
22
|
+
import { fail, takeEnv, takeValue } from "../support.mjs";
|
|
23
|
+
|
|
24
|
+
// Only what reads or writes entries takes an environment; bases and sites
|
|
25
|
+
// belong to the workspace.
|
|
26
|
+
const ENVIRONMENT_SCOPED = new Set(["list", "show", "write", "cat", "rm-entry", "status", "publish"]);
|
|
20
27
|
|
|
21
28
|
export async function kb(config, args) {
|
|
22
29
|
const workspace = config.resolveWorkspaceRequired();
|
|
23
30
|
const client = deployClient(config, workspace);
|
|
24
31
|
const sub = args.shift() ?? "list";
|
|
32
|
+
const env = ENVIRONMENT_SCOPED.has(sub) ? takeEnv(args, { fallback: null, cwd: config.workspaceRoot }) : null;
|
|
33
|
+
const ctx = { client, workspace, env };
|
|
25
34
|
|
|
26
35
|
switch (sub) {
|
|
27
|
-
case "list": return await list(
|
|
28
|
-
case "show": return await show(
|
|
36
|
+
case "list": return await list(ctx);
|
|
37
|
+
case "show": return await show(ctx, args.shift());
|
|
29
38
|
case "create": return await create(client, workspace, args.splice(0));
|
|
30
39
|
case "rm": return await remove(client, workspace, args.shift());
|
|
31
|
-
case "write": return await write(
|
|
32
|
-
case "cat": return await cat(
|
|
33
|
-
case "rm-entry": return await removeEntry(
|
|
40
|
+
case "write": return await write(ctx, args);
|
|
41
|
+
case "cat": return await cat(ctx, args);
|
|
42
|
+
case "rm-entry": return await removeEntry(ctx, args.shift(), args.shift());
|
|
34
43
|
case "follow": return await follow(client, workspace, args.shift(), args.shift());
|
|
35
44
|
case "unfollow": return await unfollow(client, workspace, args.shift(), args.shift());
|
|
36
45
|
case "refresh": return await refresh(client, workspace, args.shift(), args.shift());
|
|
37
|
-
case "status": return await status(
|
|
38
|
-
case "publish": return await publish(
|
|
46
|
+
case "status": return await status(ctx, args.shift());
|
|
47
|
+
case "publish": return await publish(ctx, args.shift());
|
|
39
48
|
case "history": return await history(client, workspace, args.shift());
|
|
40
|
-
case "rollback":
|
|
49
|
+
case "rollback":
|
|
50
|
+
return fail(
|
|
51
|
+
"`vatio kb rollback` was removed. Restore one entry instead: `vatio kb history BASE` lists what " +
|
|
52
|
+
"reached live, `vatio kb cat BASE ENTRY --version N > old.md` prints one, and " +
|
|
53
|
+
"`vatio kb write BASE ENTRY old.md --env live` brings it back."
|
|
54
|
+
);
|
|
41
55
|
default: fail(`Unknown kb subcommand: ${sub}\n\n${usage()}`);
|
|
42
56
|
}
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
// `preview` when the platform chose, for messages.
|
|
60
|
+
function envName(env) {
|
|
61
|
+
return env ?? "preview";
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
function usage() {
|
|
46
65
|
return `usage:
|
|
47
66
|
vatio kb List knowledge bases
|
|
@@ -49,19 +68,21 @@ function usage() {
|
|
|
49
68
|
vatio kb create NAME [NAME...] Create one base, or several
|
|
50
69
|
vatio kb rm NAME Delete a base (must be unreferenced)
|
|
51
70
|
vatio kb write BASE ENTRY [FILE] Write an entry, from FILE or stdin
|
|
52
|
-
vatio kb cat BASE ENTRY
|
|
71
|
+
vatio kb cat BASE ENTRY [--version N] Print an entry's markdown, or one of its versions
|
|
53
72
|
vatio kb rm-entry BASE ENTRY Delete one entry
|
|
54
73
|
vatio kb follow BASE URL Read a site into the base, nightly
|
|
55
74
|
vatio kb unfollow BASE URL Stop reading it, and drop its entries
|
|
56
75
|
vatio kb refresh BASE [URL] Read the sites again now
|
|
57
|
-
vatio kb status BASE What
|
|
58
|
-
vatio kb publish BASE
|
|
59
|
-
vatio kb history BASE
|
|
60
|
-
|
|
76
|
+
vatio kb status BASE What this environment changed that live does not have
|
|
77
|
+
vatio kb publish BASE Take this environment's changes live, without the agent
|
|
78
|
+
vatio kb history BASE What reached live, newest first
|
|
79
|
+
|
|
80
|
+
Entry commands, status and publish take --env NAME; on a branch they default
|
|
81
|
+
to the branch's environment, otherwise to preview. --env live writes live.`;
|
|
61
82
|
}
|
|
62
83
|
|
|
63
|
-
async function list(client, workspace) {
|
|
64
|
-
const bases = asArray((await client.knowledgeBases()).data);
|
|
84
|
+
async function list({ client, workspace, env }) {
|
|
85
|
+
const bases = asArray((await client.knowledgeBases({ environment: env })).data);
|
|
65
86
|
if (bases.length === 0) {
|
|
66
87
|
console.log(`No knowledge bases on workspace ${workspace}`);
|
|
67
88
|
console.log("Create one with `vatio kb create docs`, then add `knowledge: [docs]` to vatio.yml");
|
|
@@ -70,9 +91,10 @@ async function list(client, workspace) {
|
|
|
70
91
|
|
|
71
92
|
console.log(`Knowledge bases on workspace ${workspace} (${bases.length}):`);
|
|
72
93
|
for (const row of bases) {
|
|
94
|
+
const pending = row.pending_changes ? ` ${row.pending_changes} unpublished in ${envName(env)}` : "";
|
|
73
95
|
console.log(
|
|
74
96
|
` ${row.name} entries=${row.entries_count} ready=${row.answerable_count} ` +
|
|
75
|
-
`sites=${row.sites_count}${referenceNote(row)}`
|
|
97
|
+
`sites=${row.sites_count}${pending}${referenceNote(row)}`
|
|
76
98
|
);
|
|
77
99
|
}
|
|
78
100
|
}
|
|
@@ -84,15 +106,17 @@ function referenceNote(row) {
|
|
|
84
106
|
return row.referenced ? "" : " (not referenced by any deployed agent)";
|
|
85
107
|
}
|
|
86
108
|
|
|
87
|
-
async function show(client, workspace, name) {
|
|
109
|
+
async function show({ client, workspace, env }, name) {
|
|
88
110
|
if (!name) fail("usage: vatio kb show NAME");
|
|
89
111
|
|
|
90
|
-
const base = await client.knowledgeBase(name);
|
|
112
|
+
const base = await client.knowledgeBase(name, { environment: env });
|
|
91
113
|
console.log(
|
|
92
114
|
`${base.name} on workspace ${workspace}: entries=${base.entries_count} ` +
|
|
93
115
|
`ready=${base.answerable_count}${referenceNote(base)}`
|
|
94
116
|
);
|
|
95
|
-
|
|
117
|
+
if (base.pending_changes) {
|
|
118
|
+
console.log(` ${base.pending_changes} change(s) in ${envName(env)} that live does not have — \`vatio kb status ${base.name}\``);
|
|
119
|
+
}
|
|
96
120
|
|
|
97
121
|
const sites = asArray(base.sites);
|
|
98
122
|
if (sites.length > 0) {
|
|
@@ -116,23 +140,15 @@ async function show(client, workspace, name) {
|
|
|
116
140
|
return;
|
|
117
141
|
}
|
|
118
142
|
|
|
119
|
-
console.log(
|
|
143
|
+
console.log(`\n entries, as ${envName(env)} sees them:`);
|
|
120
144
|
for (const entry of entries) {
|
|
121
145
|
const origin = entry.written ? "written here" : entry.url;
|
|
122
146
|
const version = entry.version ? ` v${entry.version}` : "";
|
|
123
|
-
|
|
147
|
+
const unpublished = entry.published === false ? " (not live yet)" : "";
|
|
148
|
+
console.log(` ${entry.name}${version} ${entry.state ?? "?"} ${origin}${unpublished}`);
|
|
124
149
|
}
|
|
125
150
|
}
|
|
126
151
|
|
|
127
|
-
// Where live stands, and whether the draft has moved past it.
|
|
128
|
-
function liveNote(base) {
|
|
129
|
-
const live = base.live_snapshot ? `live=publication #${base.live_snapshot}` : "live=never published";
|
|
130
|
-
const pending = base.pending_changes
|
|
131
|
-
? ` ${base.pending_changes} unpublished change${base.pending_changes === 1 ? "" : "s"} — \`vatio kb publish ${base.name}\``
|
|
132
|
-
: "";
|
|
133
|
-
return `${live}${pending}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
152
|
// Several names, because the moment this command is most often run is a push
|
|
137
153
|
// that just refused for naming bases that do not exist yet -- and that push
|
|
138
154
|
// names all of them at once. One base per invocation turned a first deploy
|
|
@@ -177,7 +193,7 @@ async function remove(client, workspace, name) {
|
|
|
177
193
|
// vatio kb cat docs horarios > horarios.md
|
|
178
194
|
// $EDITOR horarios.md
|
|
179
195
|
// vatio kb write docs horarios horarios.md
|
|
180
|
-
async function write(client, workspace, args) {
|
|
196
|
+
async function write({ client, workspace, env }, args) {
|
|
181
197
|
const [base, name, file] = args;
|
|
182
198
|
if (!base || !name) fail("usage: vatio kb write BASE ENTRY [FILE] (or pipe the markdown in)");
|
|
183
199
|
if (file && !isFile(file)) fail(`No such file: ${file}`);
|
|
@@ -187,27 +203,33 @@ async function write(client, workspace, args) {
|
|
|
187
203
|
fail("Nothing to write: pass a FILE, or pipe the markdown in on stdin.");
|
|
188
204
|
}
|
|
189
205
|
|
|
190
|
-
const row = await client.writeKnowledgeEntry({ base, name, content });
|
|
206
|
+
const row = await client.writeKnowledgeEntry({ base, name, content, environment: env });
|
|
191
207
|
const version = row.version ? ` (v${row.version})` : "";
|
|
192
|
-
|
|
193
|
-
|
|
208
|
+
if (env === "live") {
|
|
209
|
+
console.log(`Wrote ${row.name}${version} to ${base} on workspace ${workspace}. Live answers with it now.`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
console.log(`Wrote ${row.name}${version} to ${base} in ${envName(env)} on workspace ${workspace}`);
|
|
213
|
+
console.log(`${envName(env)} answers with it now; \`vatio publish\` takes it live with the agent.`);
|
|
194
214
|
}
|
|
195
215
|
|
|
196
216
|
// Prints exactly what the platform holds, so a diff against the file you
|
|
197
217
|
// pushed is a real answer to "is what I sent what it has".
|
|
198
|
-
async function cat(client,
|
|
199
|
-
|
|
218
|
+
async function cat({ client, env }, args) {
|
|
219
|
+
const version = takeValue(args, "--version");
|
|
220
|
+
const [base, name] = args;
|
|
221
|
+
if (!base || !name) fail("usage: vatio kb cat BASE ENTRY [--version N]");
|
|
200
222
|
|
|
201
|
-
const row = await client.knowledgeEntry({ base, name });
|
|
223
|
+
const row = await client.knowledgeEntry({ base, name, environment: env, version });
|
|
202
224
|
const content = row.content ?? "";
|
|
203
225
|
process.stdout.write(content.endsWith("\n") ? content : `${content}\n`);
|
|
204
226
|
}
|
|
205
227
|
|
|
206
|
-
async function removeEntry(client, workspace, base, name) {
|
|
228
|
+
async function removeEntry({ client, workspace, env }, base, name) {
|
|
207
229
|
if (!base || !name) fail("usage: vatio kb rm-entry BASE ENTRY");
|
|
208
230
|
|
|
209
|
-
await client.deleteKnowledgeEntry({ base, name });
|
|
210
|
-
console.log(`Deleted entry ${name} from ${base} on workspace ${workspace}`);
|
|
231
|
+
await client.deleteKnowledgeEntry({ base, name, environment: env });
|
|
232
|
+
console.log(`Deleted entry ${name} from ${base} in ${envName(env)} on workspace ${workspace}`);
|
|
211
233
|
}
|
|
212
234
|
|
|
213
235
|
// One URL, and the shape of it is the whole configuration. A URL is that URL;
|
|
@@ -246,65 +268,61 @@ async function refresh(client, workspace, base, url) {
|
|
|
246
268
|
console.log(`Runs in the background — check progress with \`vatio kb show ${base}\``);
|
|
247
269
|
}
|
|
248
270
|
|
|
249
|
-
//
|
|
250
|
-
|
|
271
|
+
// What this environment changed against live, entry by entry -- what
|
|
272
|
+
// publishing it would do, and which entries live changed underneath.
|
|
273
|
+
async function status({ client, workspace, env }, base) {
|
|
251
274
|
if (!base) fail("usage: vatio kb status BASE");
|
|
252
275
|
|
|
253
|
-
const
|
|
276
|
+
const payload = await client.knowledgeChanges(base, { environment: env });
|
|
277
|
+
const changes = asArray(payload.data);
|
|
254
278
|
if (changes.length === 0) {
|
|
255
|
-
console.log(`${base} on workspace ${workspace}:
|
|
279
|
+
console.log(`${base} in ${envName(env)} on workspace ${workspace}: nothing that live does not have`);
|
|
256
280
|
return;
|
|
257
281
|
}
|
|
258
282
|
|
|
259
|
-
console.log(`${base} on workspace ${workspace}: ${changes.length}
|
|
283
|
+
console.log(`${base} in ${envName(env)} on workspace ${workspace}: ${changes.length} change${changes.length === 1 ? "" : "s"} live does not have`);
|
|
260
284
|
for (const change of changes) {
|
|
261
285
|
const from = change.live_version ? `v${change.live_version}` : "—";
|
|
262
|
-
const to = change.
|
|
263
|
-
|
|
286
|
+
const to = change.version ?? change.draft_version;
|
|
287
|
+
const conflict = change.conflict ? " CONFLICT: live changed it after you did" : "";
|
|
288
|
+
console.log(` ${change.change.padEnd(8)} ${change.name} ${from} → v${to}${conflict}`);
|
|
289
|
+
}
|
|
290
|
+
if (changes.some((change) => change.conflict)) {
|
|
291
|
+
console.log(`\nRead each conflicting entry from live (\`vatio kb cat ${base} ENTRY --env live\`), write your change on top, and it clears.`);
|
|
292
|
+
if (payload.review_url) console.log(`Or resolve them side by side: ${payload.review_url}`);
|
|
293
|
+
} else {
|
|
294
|
+
console.log("\n`vatio publish` takes them live with the agent; `vatio kb publish` takes them alone.");
|
|
264
295
|
}
|
|
265
|
-
console.log(`\nPublish with \`vatio kb publish ${base} -m "what changed"\``);
|
|
266
296
|
}
|
|
267
297
|
|
|
268
|
-
async function publish(client, workspace,
|
|
269
|
-
|
|
270
|
-
if (!base) fail("usage: vatio kb publish BASE [-m NOTE]");
|
|
298
|
+
async function publish({ client, workspace, env }, base) {
|
|
299
|
+
if (!base) fail("usage: vatio kb publish BASE [--env NAME]");
|
|
271
300
|
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const snapshot = await client.publishKnowledgeBase({ base, note });
|
|
277
|
-
console.log(`Published ${base} on workspace ${workspace} as #${snapshot.number} (${snapshot.entries} entries)`);
|
|
278
|
-
console.log(`Live answers from it now. Undo with \`vatio kb rollback ${base} NUMBER\` — see \`vatio kb history ${base}\`.`);
|
|
301
|
+
const result = await client.publishKnowledgeBase({ base, environment: env });
|
|
302
|
+
const names = asArray(result.published);
|
|
303
|
+
console.log(`Published ${names.length} change${names.length === 1 ? "" : "s"} to ${base} from ${envName(env)} on workspace ${workspace}: ${names.join(", ")}`);
|
|
304
|
+
console.log("Live answers from them now.");
|
|
279
305
|
}
|
|
280
306
|
|
|
307
|
+
// Every version that reached live, newest first. Going back is writing one
|
|
308
|
+
// of them again.
|
|
281
309
|
async function history(client, workspace, base) {
|
|
282
310
|
if (!base) fail("usage: vatio kb history BASE");
|
|
283
311
|
|
|
284
|
-
const
|
|
285
|
-
if (
|
|
286
|
-
console.log(
|
|
312
|
+
const versions = asArray((await client.knowledgeHistory(base)).data);
|
|
313
|
+
if (versions.length === 0) {
|
|
314
|
+
console.log(`Nothing in ${base} on workspace ${workspace} has reached live yet`);
|
|
287
315
|
return;
|
|
288
316
|
}
|
|
289
317
|
|
|
290
|
-
console.log(`
|
|
291
|
-
for (const
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
console.log(` #${snapshot.number} ${snapshot.created_at}${who} entries=${snapshot.entries}${note}${live}`);
|
|
318
|
+
console.log(`What reached live in ${base} on workspace ${workspace}:`);
|
|
319
|
+
for (const row of versions) {
|
|
320
|
+
const who = row.author ? ` ${row.author}` : "";
|
|
321
|
+
const what = row.deleted ? " deleted" : "";
|
|
322
|
+
console.log(` ${row.published_at} ${row.name} v${row.version} ${row.source}${who}${what}`);
|
|
296
323
|
}
|
|
297
324
|
}
|
|
298
325
|
|
|
299
|
-
// Only the pointer moves: the draft, and every later publication, stay.
|
|
300
|
-
async function rollback(client, workspace, base, number) {
|
|
301
|
-
if (!base || !number || !/^\d+$/.test(number)) fail("usage: vatio kb rollback BASE NUMBER");
|
|
302
|
-
|
|
303
|
-
await client.rollbackKnowledgeBase({ base, number });
|
|
304
|
-
console.log(`Live for ${base} on workspace ${workspace} is publication #${number} again`);
|
|
305
|
-
console.log("The draft is untouched; publish again whenever it is ready.");
|
|
306
|
-
}
|
|
307
|
-
|
|
308
326
|
// Blocking on purpose: `vatio kb write docs refunds < refunds.md` should behave
|
|
309
327
|
// like every other filter, and a terminal with nobody piping into it returns ""
|
|
310
328
|
// rather than hanging on a read that will never end.
|
package/lib/commands/mcp.mjs
CHANGED
|
@@ -60,22 +60,31 @@ const COMMANDS = {
|
|
|
60
60
|
push: {
|
|
61
61
|
usage: "[--env NAME]",
|
|
62
62
|
summary:
|
|
63
|
-
"Validate the workspace and update a preview deployment. Never touches live.
|
|
64
|
-
"
|
|
63
|
+
"Validate the workspace and update a preview deployment. Never touches live. On a git branch " +
|
|
64
|
+
"other than the default one it lands on that branch's own environment (fix/pagos -> fix-pagos), " +
|
|
65
|
+
"the same one the branch's pull request deploys to; elsewhere on `preview`.",
|
|
65
66
|
annotations: { readOnlyHint: false }
|
|
66
67
|
},
|
|
67
68
|
publish: {
|
|
68
69
|
usage: "[--env NAME]",
|
|
69
70
|
summary:
|
|
70
|
-
"Promote
|
|
71
|
-
"
|
|
72
|
-
"the
|
|
73
|
-
"
|
|
71
|
+
"Promote an environment to live, with the knowledge changes made in it — what customers see. " +
|
|
72
|
+
"Ask the developer first: this is the one step that changes what real visitors get. Refused " +
|
|
73
|
+
"when the workspace is connected to GitHub: there, live changes only by merging the branch's " +
|
|
74
|
+
"pull request. Refused too when live changed an entry this environment also changed.",
|
|
74
75
|
annotations: { readOnlyHint: false, destructiveHint: true }
|
|
75
76
|
},
|
|
77
|
+
env: {
|
|
78
|
+
usage: "[NAME] | list | rm NAME",
|
|
79
|
+
summary:
|
|
80
|
+
"The environment this workspace is working in — the branch's own on a branch — with its " +
|
|
81
|
+
"publishable token, widget snippet, share link, the knowledge entries it changed and any that " +
|
|
82
|
+
"live changed underneath. Use its token to point an app being developed at this environment.",
|
|
83
|
+
annotations: { readOnlyHint: true }
|
|
84
|
+
},
|
|
76
85
|
rollback: {
|
|
77
86
|
usage: "",
|
|
78
|
-
summary: "
|
|
87
|
+
summary: "Put live back to the previous snapshot — its configuration and its knowledge together. Same weight as publish — ask first.",
|
|
79
88
|
annotations: { readOnlyHint: false, destructiveHint: true }
|
|
80
89
|
},
|
|
81
90
|
chat: {
|
|
@@ -88,21 +97,24 @@ const COMMANDS = {
|
|
|
88
97
|
annotations: { readOnlyHint: false }
|
|
89
98
|
},
|
|
90
99
|
secrets: {
|
|
91
|
-
usage: "list | set KEY VALUE | rm KEY",
|
|
100
|
+
usage: "list | set KEY VALUE | rm KEY [--env NAME]",
|
|
92
101
|
summary:
|
|
93
|
-
"Credentials the workspace's
|
|
94
|
-
"keys, never values."
|
|
102
|
+
"Credentials the workspace's tools read as $env.KEY. Values are write-only — listing shows " +
|
|
103
|
+
"keys, never values. On a branch (or with --env) a value applies to that environment only, " +
|
|
104
|
+
"replacing the one everywhere else: point a branch's tools at staging without touching live.",
|
|
95
105
|
annotations: { readOnlyHint: false }
|
|
96
106
|
},
|
|
97
107
|
kb: {
|
|
98
108
|
usage:
|
|
99
|
-
"[list] | show NAME | create NAME | write BASE ENTRY [FILE] | cat BASE ENTRY
|
|
100
|
-
"follow BASE URL | unfollow BASE URL | refresh BASE [URL] | status BASE |
|
|
101
|
-
"
|
|
109
|
+
"[list] | show NAME | create NAME | write BASE ENTRY [FILE] | cat BASE ENTRY [--version N] | " +
|
|
110
|
+
"rm-entry BASE ENTRY | follow BASE URL | unfollow BASE URL | refresh BASE [URL] | status BASE | " +
|
|
111
|
+
"publish BASE | history BASE (entry commands take --env NAME)",
|
|
102
112
|
summary:
|
|
103
113
|
"Knowledge bases, which belong to the workspace rather than to a deployment: `knowledge:` in " +
|
|
104
|
-
"vatio.yml is a list of names, and a push neither fills nor empties one.
|
|
105
|
-
"preview answers from
|
|
114
|
+
"vatio.yml is a list of names, and a push neither fills nor empties one. The entries are live; " +
|
|
115
|
+
"writes on a branch are changes only that branch's preview answers from, and they go live with " +
|
|
116
|
+
"vatio_publish or the merge of its pull request. `status` names entries live changed underneath: " +
|
|
117
|
+
"read those from live (`cat --env live`), write your change on top, and the conflict clears.",
|
|
106
118
|
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
107
119
|
},
|
|
108
120
|
tokens: {
|
|
@@ -125,7 +137,7 @@ const COMMANDS = {
|
|
|
125
137
|
summary:
|
|
126
138
|
"The workspace's own WhatsApp number, which serves live: whether it is connected, whether Meta " +
|
|
127
139
|
"delivers to it, and whether it is answering — three things that fail separately. `numbers` is " +
|
|
128
|
-
"the other thing: the shared WhatsApp preview, test phones that reach
|
|
140
|
+
"the other thing: the shared WhatsApp preview, test phones that reach the environment they point at (`point PHONE --env NAME`) and need no Meta " +
|
|
129
141
|
"account. A code goes to the handset, so a human has to read it back to you.",
|
|
130
142
|
annotations: { readOnlyHint: false, openWorldHint: true }
|
|
131
143
|
},
|
|
@@ -155,6 +167,7 @@ const WITHHELD = {
|
|
|
155
167
|
const BLOCKED_SUBCOMMANDS = {
|
|
156
168
|
chat: { destroy: "destroys a conversation; ask the developer" },
|
|
157
169
|
kb: { rm: "deletes an indexed base; ask the developer" },
|
|
170
|
+
env: { rm: "removes an environment with its unpublished knowledge changes; ask the developer" },
|
|
158
171
|
whatsapp: { connect: "takes Meta credentials the developer holds", disconnect: "ask the developer" },
|
|
159
172
|
instagram: { connect: "needs Meta's consent screen in a browser", disconnect: "ask the developer" }
|
|
160
173
|
};
|
|
@@ -218,8 +231,13 @@ function initializeResult(params) {
|
|
|
218
231
|
"Start with vatio_docs: it is the contract for vatio.yml, tools, auth, visitor identity and",
|
|
219
232
|
"knowledge, fetched live from the platform, so it is what the platform actually accepts.",
|
|
220
233
|
"",
|
|
221
|
-
"
|
|
222
|
-
"
|
|
234
|
+
"Work on a git branch (a worktree is ideal): every command then uses that branch's own",
|
|
235
|
+
"environment — its agent, its knowledge changes, its secret values — and nothing you do there",
|
|
236
|
+
"reaches live or another branch. vatio_env shows which one and the token to point an app at it.",
|
|
237
|
+
"",
|
|
238
|
+
"The loop is: edit files and knowledge (vatio_kb write), vatio_push, vatio_chat to read the",
|
|
239
|
+
"agent's answer, then publish when the developer says so — by merging the branch's pull request",
|
|
240
|
+
"when GitHub is connected, vatio_publish otherwise. The prompt and the knowledge go live together.",
|
|
223
241
|
"",
|
|
224
242
|
"Anything that needs a browser or a person — login, issue, instagram connect — is not here;",
|
|
225
243
|
"ask the developer to run it in their terminal."
|
|
@@ -6,63 +6,71 @@
|
|
|
6
6
|
// will actually enforce, edited in the console. None of them are in vatio.yml.
|
|
7
7
|
|
|
8
8
|
import { deployClient } from "./deploy.mjs";
|
|
9
|
-
import { askSecret, fail, paint, takeValue } from "../support.mjs";
|
|
9
|
+
import { askSecret, branchDefault, fail, paint, takeValue } from "../support.mjs";
|
|
10
10
|
|
|
11
|
-
// live, preview, or a
|
|
12
|
-
// first two, which meant a workspace could deploy to `pr-42` and then have no
|
|
13
|
-
// way to mint the publishable token that makes it embeddable.
|
|
11
|
+
// live, preview, or a branch's own like fix-pagos.
|
|
14
12
|
const ENVIRONMENT_NAME = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
|
|
15
13
|
const ENVIRONMENT_MAX = 40;
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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;
|
|
20
20
|
if (String(value).length > ENVIRONMENT_MAX || !ENVIRONMENT_NAME.test(String(value))) {
|
|
21
|
-
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)`);
|
|
22
22
|
}
|
|
23
23
|
return String(value);
|
|
24
24
|
}
|
|
25
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.
|
|
26
29
|
export async function secrets(config, args) {
|
|
27
30
|
const workspace = config.resolveWorkspaceRequired();
|
|
28
31
|
const client = deployClient(config, workspace);
|
|
32
|
+
const environment = environmentOption(args, null, config.workspaceRoot);
|
|
33
|
+
const where = environment ? `${environment} on workspace ${workspace}` : `workspace ${workspace}`;
|
|
29
34
|
const sub = args.shift() ?? "list";
|
|
30
35
|
|
|
31
36
|
if (sub === "list") {
|
|
32
|
-
const payload = await client.listSecrets();
|
|
37
|
+
const payload = await client.listSecrets({ environment });
|
|
33
38
|
const keys = asArray(payload.secrets);
|
|
34
39
|
if (keys.length === 0) {
|
|
35
|
-
console.log(`No secrets on
|
|
40
|
+
console.log(`No secrets on ${where}`);
|
|
36
41
|
console.log(" Set one: vatio secrets set STRIPE_KEY sk_live_…");
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
39
44
|
// Keys only, never values. The platform does not hand a value back once it
|
|
40
45
|
// is stored, and a CLI that printed them would be a CLI that leaks them
|
|
41
46
|
// into a terminal scrollback.
|
|
42
|
-
console.log(`Secrets on
|
|
43
|
-
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
|
+
}
|
|
44
52
|
return;
|
|
45
53
|
}
|
|
46
54
|
if (sub === "set") {
|
|
47
55
|
const key = args.shift();
|
|
48
|
-
if (!key) fail("Usage: vatio secrets set KEY [VALUE]");
|
|
56
|
+
if (!key) fail("Usage: vatio secrets set KEY [VALUE] [--env NAME]");
|
|
49
57
|
// Asked for rather than typed on the command line, where it would stay in
|
|
50
58
|
// shell history.
|
|
51
59
|
const value = args.shift() ?? (await askSecret(`${paint("green", "?")} Paste your secret: `));
|
|
52
60
|
if (value === "") fail(`No value given for ${key}`);
|
|
53
|
-
await client.upsertSecret({ key, value });
|
|
54
|
-
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}`);
|
|
55
63
|
return;
|
|
56
64
|
}
|
|
57
65
|
if (sub === "rm" || sub === "remove" || sub === "delete") {
|
|
58
66
|
const key = args.shift();
|
|
59
|
-
if (!key) fail("Usage: vatio secrets rm KEY");
|
|
60
|
-
await client.deleteSecret({ key });
|
|
61
|
-
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}`);
|
|
62
70
|
return;
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
fail("Usage: vatio secrets list|set KEY [VALUE]|rm KEY");
|
|
73
|
+
fail("Usage: vatio secrets list|set KEY [VALUE]|rm KEY [--env NAME]");
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
// Repeatable: `--origin https://a.com --origin https://b.com`.
|
|
@@ -75,7 +83,7 @@ function takeOrigins(args) {
|
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
export async function tokens(config, args) {
|
|
78
|
-
const environment = environmentOption(args, "live");
|
|
86
|
+
const environment = environmentOption(args, "live", config.workspaceRoot);
|
|
79
87
|
const label = takeValue(args, "--label") ?? takeValue(args, "-l");
|
|
80
88
|
const origins = takeOrigins(args);
|
|
81
89
|
const workspace = config.resolveWorkspaceRequired();
|
|
@@ -139,7 +147,7 @@ function originsLine(origins) {
|
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
export async function widget(config, args) {
|
|
142
|
-
const environment = environmentOption(args, "live");
|
|
150
|
+
const environment = environmentOption(args, "live", config.workspaceRoot);
|
|
143
151
|
const workspace = config.resolveWorkspaceRequired();
|
|
144
152
|
const payload = await deployClient(config, workspace).widget();
|
|
145
153
|
|
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,18 +52,23 @@ 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
|
|
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
|
|
60
72
|
tokens list|create [--env NAME] [--label NAME] [--origin URL]...|revoke PREFIX
|
|
61
73
|
tokens origins PREFIX URL...
|
|
62
74
|
The origins a token is accepted from (replaces the list)
|
|
@@ -64,11 +76,13 @@ flag, the directory decides.
|
|
|
64
76
|
the look is edited in the console
|
|
65
77
|
auth --new-key Keypair that signs the JWT saying who a visitor is
|
|
66
78
|
|
|
67
|
-
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):
|
|
68
82
|
whatsapp [status]|connect|check|activate|deactivate|disconnect
|
|
69
|
-
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
|
|
70
84
|
instagram [status]|connect|check|disconnect
|
|
71
|
-
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
|
|
72
86
|
|
|
73
87
|
Everything else:
|
|
74
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";
|