@vatio-ai/cli 0.50.0 → 0.52.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.
@@ -127,6 +127,24 @@ export class ApiClient extends HttpClient {
127
127
  return this.post(`/knowledge_bases/${encode(base)}/sites/refresh`, { url });
128
128
  }
129
129
 
130
+ // Writes land in the base's draft, which preview answers from; live moves
131
+ // only on publish or rollback.
132
+ knowledgeChanges(base) {
133
+ return this.get(`/knowledge_bases/${encode(base)}/changes`);
134
+ }
135
+
136
+ publishKnowledgeBase({ base, note }) {
137
+ return this.post(`/knowledge_bases/${encode(base)}/publish`, { note });
138
+ }
139
+
140
+ knowledgeSnapshots(base) {
141
+ return this.get(`/knowledge_bases/${encode(base)}/snapshots`);
142
+ }
143
+
144
+ rollbackKnowledgeBase({ base, number }) {
145
+ return this.post(`/knowledge_bases/${encode(base)}/snapshots/${encode(String(number))}/rollback`, {});
146
+ }
147
+
130
148
  // --- publishable tokens and the widget ------------------------------
131
149
 
132
150
  listPublishableTokens() {
@@ -18,7 +18,7 @@ import { buildBundle, BundleError } from "../bundle.mjs";
18
18
  import { ApiClient, CliClient } from "../api-client.mjs";
19
19
  import { NotFoundError } from "../http.mjs";
20
20
  import { deviceLogin } from "./auth.mjs";
21
- import { detectGitSha, fail, takeEnv, takeFlag, takeValue } from "../support.mjs";
21
+ import { detectGitSha, fail, paint, takeEnv, takeFlag, takeValue } from "../support.mjs";
22
22
 
23
23
  // The first command that makes something exist on a server, so the first one
24
24
  // that needs to know whose it is.
@@ -141,6 +141,16 @@ export async function push(config, args) {
141
141
  console.log(hint.message);
142
142
  console.log(` ${hint.url}`);
143
143
  }
144
+ printNotices(payload);
145
+ }
146
+
147
+ // Something the platform wants the developer to know about the command they
148
+ // just ran -- a deprecation, usually. On stderr so scripts reading stdout keep
149
+ // working.
150
+ function printNotices(payload) {
151
+ for (const notice of asArray(payload.notices)) {
152
+ process.stderr.write(`\n${paint("yellow", `Notice: ${notice}`)}\n`);
153
+ }
144
154
  }
145
155
 
146
156
  export async function publish(config, args) {
@@ -156,12 +166,14 @@ export async function publish(config, args) {
156
166
  const from = asArray(payload.published_from);
157
167
  const source = from.length > 0 ? ` from ${from.join(", ")}` : "";
158
168
  console.log(`Published live deployment #${payload.deployment_id}${source} (git_sha=${JSON.stringify(payload.git_sha ?? null)})`);
169
+ printNotices(payload);
159
170
  }
160
171
 
161
172
  export async function rollback(config) {
162
173
  const workspace = config.resolveWorkspaceRequired();
163
174
  const payload = await deployClient(config, workspace).rollback({ createdBy: process.env.USER ?? null });
164
175
  console.log(`Rolled back to deployment #${payload.deployment_id} (from #${payload.rolled_back_from_id})`);
176
+ printNotices(payload);
165
177
  }
166
178
 
167
179
  export async function status(config) {
@@ -2,8 +2,12 @@
2
2
  //
3
3
  // A base belongs to the workspace, not to a deployment: `knowledge:` in
4
4
  // vatio.yml is a list of *names*, and a push neither fills nor empties one.
5
- // That is why this is a command and not part of the manifest, and why live and
6
- // preview read the same base.
5
+ // That is why this is a command and not part of the manifest.
6
+ //
7
+ // Every write goes to the base's draft, which preview answers from. Live
8
+ // answers from the last published snapshot and changes only on `publish` --
9
+ // or `rollback`, which points it at an earlier one. So: write, try it on
10
+ // preview, publish.
7
11
  //
8
12
  // A base holds entries, and an entry is markdown. Either you wrote it, or a
9
13
  // site wrote it from a page it read — and a site is one URL or one pattern,
@@ -30,6 +34,10 @@ export async function kb(config, args) {
30
34
  case "follow": return await follow(client, workspace, args.shift(), args.shift());
31
35
  case "unfollow": return await unfollow(client, workspace, args.shift(), args.shift());
32
36
  case "refresh": return await refresh(client, workspace, args.shift(), args.shift());
37
+ case "status": return await status(client, workspace, args.shift());
38
+ case "publish": return await publish(client, workspace, args);
39
+ case "history": return await history(client, workspace, args.shift());
40
+ case "rollback": return await rollback(client, workspace, args.shift(), args.shift());
33
41
  default: fail(`Unknown kb subcommand: ${sub}\n\n${usage()}`);
34
42
  }
35
43
  }
@@ -45,7 +53,11 @@ function usage() {
45
53
  vatio kb rm-entry BASE ENTRY Delete one entry
46
54
  vatio kb follow BASE URL Read a site into the base, nightly
47
55
  vatio kb unfollow BASE URL Stop reading it, and drop its entries
48
- vatio kb refresh BASE [URL] Read the sites again now`;
56
+ vatio kb refresh BASE [URL] Read the sites again now
57
+ vatio kb status BASE What the draft has that live does not
58
+ vatio kb publish BASE [-m NOTE] Take the draft live
59
+ vatio kb history BASE Every publication, newest first
60
+ vatio kb rollback BASE NUMBER Point live back at publication NUMBER`;
49
61
  }
50
62
 
51
63
  async function list(client, workspace) {
@@ -80,6 +92,7 @@ async function show(client, workspace, name) {
80
92
  `${base.name} on workspace ${workspace}: entries=${base.entries_count} ` +
81
93
  `ready=${base.answerable_count}${referenceNote(base)}`
82
94
  );
95
+ console.log(` ${liveNote(base)}`);
83
96
 
84
97
  const sites = asArray(base.sites);
85
98
  if (sites.length > 0) {
@@ -106,10 +119,20 @@ async function show(client, workspace, name) {
106
119
  console.log("\n entries:");
107
120
  for (const entry of entries) {
108
121
  const origin = entry.written ? "written here" : entry.url;
109
- console.log(` ${entry.name} ${entry.state ?? "?"} ${origin}`);
122
+ const version = entry.version ? ` v${entry.version}` : "";
123
+ console.log(` ${entry.name}${version} ${entry.state ?? "?"} ${origin}`);
110
124
  }
111
125
  }
112
126
 
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
+
113
136
  // Several names, because the moment this command is most often run is a push
114
137
  // that just refused for naming bases that do not exist yet -- and that push
115
138
  // names all of them at once. One base per invocation turned a first deploy
@@ -165,7 +188,9 @@ async function write(client, workspace, args) {
165
188
  }
166
189
 
167
190
  const row = await client.writeKnowledgeEntry({ base, name, content });
168
- console.log(`Wrote ${row.name} in ${base} on workspace ${workspace}`);
191
+ const version = row.version ? ` (v${row.version})` : "";
192
+ console.log(`Wrote ${row.name}${version} to the draft of ${base} on workspace ${workspace}`);
193
+ console.log(`Preview answers with it now; \`vatio kb publish ${base}\` takes it live.`);
169
194
  }
170
195
 
171
196
  // Prints exactly what the platform holds, so a diff against the file you
@@ -221,6 +246,65 @@ async function refresh(client, workspace, base, url) {
221
246
  console.log(`Runs in the background — check progress with \`vatio kb show ${base}\``);
222
247
  }
223
248
 
249
+ // The draft against live, entry by entry -- what `publish` would do.
250
+ async function status(client, workspace, base) {
251
+ if (!base) fail("usage: vatio kb status BASE");
252
+
253
+ const changes = asArray((await client.knowledgeChanges(base)).data);
254
+ if (changes.length === 0) {
255
+ console.log(`${base} on workspace ${workspace}: everything is published`);
256
+ return;
257
+ }
258
+
259
+ console.log(`${base} on workspace ${workspace}: ${changes.length} unpublished change${changes.length === 1 ? "" : "s"}`);
260
+ for (const change of changes) {
261
+ const from = change.live_version ? `v${change.live_version}` : "—";
262
+ const to = change.draft_version ? `v${change.draft_version}` : "—";
263
+ console.log(` ${change.change.padEnd(8)} ${change.name} ${from} → ${to}`);
264
+ }
265
+ console.log(`\nPublish with \`vatio kb publish ${base} -m "what changed"\``);
266
+ }
267
+
268
+ async function publish(client, workspace, args) {
269
+ const base = args.shift();
270
+ if (!base) fail("usage: vatio kb publish BASE [-m NOTE]");
271
+
272
+ let note = "";
273
+ const flag = args.findIndex((arg) => arg === "-m" || arg === "--note");
274
+ if (flag !== -1) note = args[flag + 1] ?? "";
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}\`.`);
279
+ }
280
+
281
+ async function history(client, workspace, base) {
282
+ if (!base) fail("usage: vatio kb history BASE");
283
+
284
+ const snapshots = asArray((await client.knowledgeSnapshots(base)).data);
285
+ if (snapshots.length === 0) {
286
+ console.log(`${base} on workspace ${workspace} was never published — \`vatio kb publish ${base}\``);
287
+ return;
288
+ }
289
+
290
+ console.log(`Publications of ${base} on workspace ${workspace}:`);
291
+ for (const snapshot of snapshots) {
292
+ const live = snapshot.live ? " ← live" : "";
293
+ const who = snapshot.author ? ` ${snapshot.author}` : "";
294
+ const note = snapshot.note ? ` ${snapshot.note}` : "";
295
+ console.log(` #${snapshot.number} ${snapshot.created_at}${who} entries=${snapshot.entries}${note}${live}`);
296
+ }
297
+ }
298
+
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
+
224
308
  // Blocking on purpose: `vatio kb write docs refunds < refunds.md` should behave
225
309
  // like every other filter, and a terminal with nobody piping into it returns ""
226
310
  // rather than hanging on a read that will never end.
@@ -69,7 +69,8 @@ const COMMANDS = {
69
69
  summary:
70
70
  "Promote the latest push to live (or the preview `--env` names), which is what customers " +
71
71
  "see. Ask the developer first: this is " +
72
- "the one step that changes what real visitors get.",
72
+ "the one step that changes what real visitors get. Refused when the workspace is connected " +
73
+ "to GitHub: there, live changes only by merging to the default branch.",
73
74
  annotations: { readOnlyHint: false, destructiveHint: true }
74
75
  },
75
76
  rollback: {
@@ -94,10 +95,14 @@ const COMMANDS = {
94
95
  annotations: { readOnlyHint: false }
95
96
  },
96
97
  kb: {
97
- usage: "[list] | show NAME | create NAME | add-source BASE NAME URL | upload BASE FILE... | reindex BASE",
98
+ usage:
99
+ "[list] | show NAME | create NAME | write BASE ENTRY [FILE] | cat BASE ENTRY | rm-entry BASE ENTRY | " +
100
+ "follow BASE URL | unfollow BASE URL | refresh BASE [URL] | status BASE | publish BASE [-m NOTE] | " +
101
+ "history BASE | rollback BASE NUMBER",
98
102
  summary:
99
103
  "Knowledge bases, which belong to the workspace rather than to a deployment: `knowledge:` in " +
100
- "vatio.yml is a list of names, and a push neither fills nor empties one.",
104
+ "vatio.yml is a list of names, and a push neither fills nor empties one. Writes go to a draft that " +
105
+ "preview answers from; live changes only on publish.",
101
106
  annotations: { readOnlyHint: false, openWorldHint: true }
102
107
  },
103
108
  tokens: {
package/lib/help.mjs CHANGED
@@ -25,7 +25,8 @@ flag, the directory decides.
25
25
  Deploy:
26
26
  push [--env NAME] Validate the workspace, then update a preview
27
27
  publish [--env NAME] Promote the latest push to live. --env promotes
28
- a specific preview instead
28
+ a specific preview instead. Refused when GitHub
29
+ is connected: merge to the default branch
29
30
  rollback Restore the previous live deployment
30
31
  status Preview and live deployment state
31
32
  diff [--env NAME] What this directory would change (default: preview)
@@ -49,6 +50,9 @@ flag, the directory decides.
49
50
  kb rm-entry BASE ENTRY Delete one entry
50
51
  kb follow BASE URL Read a site into the base, nightly
51
52
  kb unfollow BASE URL | refresh BASE [URL]
53
+ kb status BASE What the draft has that live does not
54
+ kb publish BASE [-m NOTE] Take the draft live (preview already has it)
55
+ kb history BASE | rollback BASE NUMBER
52
56
 
53
57
  Credentials and the widget:
54
58
  secrets list|set KEY [VALUE]|rm KEY
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vatio-ai/cli",
3
- "version": "0.50.0",
3
+ "version": "0.52.0",
4
4
  "description": "Vatio CLI — deploy and manage Vatio agent workspaces",
5
5
  "keywords": [
6
6
  "vatio",