@vatio-ai/cli 0.51.0 → 0.53.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 CHANGED
@@ -71,7 +71,7 @@ kb write|cat|rm-entry Entries, from a file or stdin
71
71
  kb follow|unfollow|refresh Read a site into a base, nightly
72
72
 
73
73
  secrets list|set|rm Credentials your tools read
74
- tokens list|create|revoke Publishable tokens for the widget and the SDK
74
+ tokens list|create|origins|revoke Publishable tokens and the origins each accepts
75
75
  widget [--env NAME] What the platform enforces, and the tokens there are
76
76
  auth --new-key Keypair that signs the visitor's JWT
77
77
 
@@ -151,12 +151,17 @@ export class ApiClient extends HttpClient {
151
151
  return this.get("/publishable_tokens");
152
152
  }
153
153
 
154
- createPublishableToken({ environment, label = null }) {
154
+ createPublishableToken({ environment, label = null, allowedOrigins = [] }) {
155
155
  const body = { environment };
156
156
  if (String(label ?? "").trim() !== "") body.label = String(label).trim();
157
+ if (allowedOrigins.length > 0) body.allowed_origins = allowedOrigins;
157
158
  return this.post("/publishable_tokens", body);
158
159
  }
159
160
 
161
+ setPublishableTokenOrigins({ prefix, allowedOrigins }) {
162
+ return this.patch(`/publishable_tokens/${encode(prefix)}`, { allowed_origins: allowedOrigins });
163
+ }
164
+
160
165
  revokePublishableToken({ prefix }) {
161
166
  return this.delete(`/publishable_tokens/${encode(prefix)}`);
162
167
  }
@@ -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.
@@ -106,8 +106,8 @@ export async function push(config, args) {
106
106
  await ensureAuthorized(config);
107
107
 
108
108
  // The remote has to exist before the workspace can be checked: the slug is in
109
- // the URL of the call that checks it. It is created unnamed -- the push a few
110
- // lines down applies `business.name` from the manifest anyway.
109
+ // the URL of the call that checks it. It is created unnamed; the display name
110
+ // is a workspace setting edited in the console, not part of a push.
111
111
  const clients = workspacesClient(config);
112
112
  const created = !(await clients.workspaceExists(workspace));
113
113
  if (created) {
@@ -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) {
@@ -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: {
@@ -105,17 +106,18 @@ const COMMANDS = {
105
106
  annotations: { readOnlyHint: false, openWorldHint: true }
106
107
  },
107
108
  tokens: {
108
- usage: "list | create [--env NAME] [--label NAME] | revoke PREFIX",
109
+ usage: "list | create [--env NAME] [--label NAME] [--origin URL]... | origins PREFIX URL... | revoke PREFIX",
109
110
  summary:
110
111
  "Publishable tokens for the widget and anything built on the SDK. A created token is shown in " +
111
- "full exactly once.",
112
+ "full exactly once. Each token works only from its own allowed origins (--origin, repeatable, or " +
113
+ "`origins PREFIX URL...` to replace the list); the install snippet's token is edited in the console.",
112
114
  annotations: { readOnlyHint: false }
113
115
  },
114
116
  widget: {
115
117
  usage: "[--env NAME]",
116
118
  summary:
117
- "What the platform will actually enforce for the widget, the origin allowlist, and the tokens " +
118
- "that exist. Read-only: vatio.yml owns every field, so push is what changes it.",
119
+ "What the platform will actually enforce for the widget, the origins its tokens accept, and the " +
120
+ "tokens that exist. Read-only: the look is edited on the console's Brand page, whose URL it prints.",
119
121
  annotations: { readOnlyHint: true }
120
122
  },
121
123
  whatsapp: {
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { existsSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
- import { stringify as stringifyYaml } from "yaml";
6
5
 
7
6
  import { KEYS, SLUG_FORMAT } from "../config.mjs";
8
7
  import { MANIFEST_FILE, manifestPath } from "../workspace.mjs";
@@ -110,10 +109,13 @@ export async function init(config, args) {
110
109
  }
111
110
 
112
111
  const manifest = join(target, MANIFEST_FILE);
113
- writeFileSync(manifest, starterManifest(slug, name));
112
+ writeFileSync(manifest, starterManifest(slug));
114
113
 
115
114
  console.log(`Wrote ${manifest}`);
116
115
  if (!authorized) console.log(`Local only for now — \`vatio push\` is what creates ${slug} on the platform.`);
116
+ if (!authorized && String(name ?? "").trim() !== "") {
117
+ console.log("The display name is set in the console (the workspace's Brand page) once it exists.");
118
+ }
117
119
  console.log("");
118
120
  console.log("Next:");
119
121
  console.log(` edit ${MANIFEST_FILE}, then \`vatio push\` and \`vatio publish\``);
@@ -121,14 +123,10 @@ export async function init(config, args) {
121
123
  }
122
124
 
123
125
  // The smallest file that deploys: which workspace, and what the agent does.
124
- // The slug is already validated as a slug, so it needs no quoting; a display
125
- // name is whatever the developer typed, so it goes through the YAML emitter.
126
- function starterManifest(slug, name) {
126
+ // The slug is already validated as a slug, so it needs no quoting. The display
127
+ // name is not here: it is a workspace setting, edited in the console.
128
+ function starterManifest(slug) {
127
129
  const lines = ["# Which Vatio workspace this directory deploys to.", `workspace: ${slug}`, ""];
128
- if (String(name ?? "").trim() !== "") {
129
- lines.push(stringifyYaml({ business: { name: String(name).trim() } }).trimEnd());
130
- lines.push("");
131
- }
132
130
  lines.push("agent:");
133
131
  lines.push(" instructions: |");
134
132
  lines.push(" Describe what this agent does and how it should answer.");
@@ -1,10 +1,9 @@
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, and the widget
5
- // screen is a read of what the platform will actually enforce. None of them
6
- // belong in a file that gets committed, which is why none of them are in
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
9
  import { askSecret, fail, paint, takeValue } from "../support.mjs";
@@ -66,9 +65,19 @@ export async function secrets(config, args) {
66
65
  fail("Usage: vatio secrets list|set KEY [VALUE]|rm KEY");
67
66
  }
68
67
 
68
+ // Repeatable: `--origin https://a.com --origin https://b.com`.
69
+ function takeOrigins(args) {
70
+ const origins = [];
71
+ for (let value = takeValue(args, "--origin"); value !== null; value = takeValue(args, "--origin")) {
72
+ origins.push(value);
73
+ }
74
+ return origins;
75
+ }
76
+
69
77
  export async function tokens(config, args) {
70
78
  const environment = environmentOption(args, "live");
71
79
  const label = takeValue(args, "--label") ?? takeValue(args, "-l");
80
+ const origins = takeOrigins(args);
72
81
  const workspace = config.resolveWorkspaceRequired();
73
82
  const client = deployClient(config, workspace);
74
83
  const sub = args.shift() ?? "list";
@@ -78,7 +87,7 @@ export async function tokens(config, args) {
78
87
  const list = asArray(payload.publishable_tokens);
79
88
  if (list.length === 0) {
80
89
  console.log(`No publishable tokens on workspace ${workspace}`);
81
- console.log(" Create one: vatio tokens create --env live");
90
+ console.log(" Create one: vatio tokens create --env live --origin https://your-site.com");
82
91
  return;
83
92
  }
84
93
  console.log(`Publishable tokens on workspace ${workspace} (${list.length}):`);
@@ -86,12 +95,14 @@ export async function tokens(config, args) {
86
95
  const parts = [token.key_prefix ?? token.prefix, token.environment];
87
96
  if (token.label) parts.push(token.label);
88
97
  console.log(` ${parts.filter(Boolean).join(" ")}`);
98
+ console.log(` origins: ${originsLine(token.allowed_origins)}`);
89
99
  }
90
100
  return;
91
101
  }
92
102
  if (sub === "create") {
93
- const payload = await client.createPublishableToken({ environment, label });
103
+ const payload = await client.createPublishableToken({ environment, label, allowedOrigins: origins });
94
104
  console.log(`Created a publishable token for ${environment} on workspace ${workspace}`);
105
+ console.log(` origins: ${originsLine(payload.allowed_origins)}`);
95
106
  console.log("");
96
107
  // Shown in full exactly once: the platform stores a digest, so this is the
97
108
  // only moment the value exists anywhere the developer can copy it.
@@ -100,6 +111,13 @@ export async function tokens(config, args) {
100
111
  console.log("Copy it now — it is not shown again.");
101
112
  return;
102
113
  }
114
+ if (sub === "origins") {
115
+ const prefix = args.shift();
116
+ if (!prefix) fail("Usage: vatio tokens origins PREFIX [URL...] (no URL clears the list)");
117
+ const payload = await client.setPublishableTokenOrigins({ prefix, allowedOrigins: args.splice(0) });
118
+ console.log(`Origins for ${prefix}: ${originsLine(payload.allowed_origins)}`);
119
+ return;
120
+ }
103
121
  if (sub === "revoke") {
104
122
  const prefix = args.shift();
105
123
  if (!prefix) fail("Usage: vatio tokens revoke PREFIX");
@@ -108,7 +126,16 @@ export async function tokens(config, args) {
108
126
  return;
109
127
  }
110
128
 
111
- fail("Usage: vatio tokens list|create [--env live|preview|NAME] [--label NAME]|revoke PREFIX");
129
+ fail(
130
+ "Usage: vatio tokens list|create [--env live|preview|NAME] [--label NAME] [--origin URL]...|" +
131
+ "origins PREFIX URL...|revoke PREFIX"
132
+ );
133
+ }
134
+
135
+ // An empty list is not "anywhere": only Vatio's own pages can use the token.
136
+ function originsLine(origins) {
137
+ const list = asArray(origins);
138
+ return list.length > 0 ? list.join(", ") : "none (add one with `vatio tokens origins PREFIX URL`)";
112
139
  }
113
140
 
114
141
  export async function widget(config, args) {
@@ -118,8 +145,9 @@ export async function widget(config, args) {
118
145
 
119
146
  console.log(JSON.stringify(payload, null, 2));
120
147
  console.log("");
121
- console.log("The widget's look comes from vatio.yml's `widget:` block — edit it and push.");
122
- console.log(`A token for this environment: vatio tokens create --env ${environment}`);
148
+ console.log(`The widget's look is edited in the console: ${payload.brand_url ?? "the workspace's Brand page"}`);
149
+ console.log(`Each token's allowed origins: ${payload.tokens_url ?? "the workspace's Tokens page"}`);
150
+ console.log(`A token for this environment: vatio tokens create --env ${environment} --origin https://your-site.com`);
123
151
  }
124
152
 
125
153
  function asArray(value) {
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)
@@ -56,8 +57,11 @@ flag, the directory decides.
56
57
  Credentials and the widget:
57
58
  secrets list|set KEY [VALUE]|rm KEY
58
59
  Without VALUE, set asks for it with the input hidden
59
- tokens list|create [--env NAME] [--label NAME]|revoke PREFIX
60
- widget [--env NAME] What the platform enforces, and the tokens there are
60
+ tokens list|create [--env NAME] [--label NAME] [--origin URL]...|revoke PREFIX
61
+ tokens origins PREFIX URL...
62
+ The origins a token is accepted from (replaces the list)
63
+ widget [--env NAME] What the platform enforces, and the tokens there are;
64
+ the look is edited in the console
61
65
  auth --new-key Keypair that signs the JWT saying who a visitor is
62
66
 
63
67
  Channels (your own account serves live; the shared preview always serves preview):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vatio-ai/cli",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "Vatio CLI — deploy and manage Vatio agent workspaces",
5
5
  "keywords": [
6
6
  "vatio",