@vatio-ai/cli 0.46.0 → 0.47.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/README.md CHANGED
@@ -1,44 +1,86 @@
1
1
  # @vatio-ai/cli
2
2
 
3
- The CLI for [Vatio](https://vatio.ai) — deploy and manage AI agent workspaces.
3
+ The CLI for [Vatio](https://vatio.ai) — a production runtime for
4
+ customer-facing AI agents. Describe an agent in one `vatio.yml` and deploy it to
5
+ web chat, WhatsApp and Instagram; Vatio runs the conversation, the contacts, the
6
+ identity, the safeguards and the handoff to a human.
4
7
 
5
8
  ```bash
6
9
  npx @vatio-ai/cli init my-agent
7
10
  npx @vatio-ai/cli push
8
11
  ```
9
12
 
10
- No runtime to install first: if you have Node, you have this.
13
+ No runtime to install first: if you have Node 20 or newer, you have this.
11
14
 
12
15
  The package is `@vatio-ai/cli`; the command it installs is `vatio`. So
13
16
  `npm install -g @vatio-ai/cli` gives you `vatio push`, and `npx @vatio-ai/cli push`
14
17
  is the same thing without installing anything.
15
18
 
19
+ ## If you are a coding agent
20
+
21
+ ```bash
22
+ vatio mcp # MCP over stdio
23
+ vatio docs # the whole developer contract as markdown, live from Vatio
24
+ ```
25
+
26
+ `vatio mcp` is the short path: it hands you the contract and the deploy commands
27
+ over MCP, with no browser step. Failing that, `vatio docs` prints the same
28
+ contract that [docs.vatio.ai/docs.md](https://docs.vatio.ai/docs.md) serves, and
29
+ `vatio docs --save` writes it next to the workspace.
30
+
31
+ Don't guess `vatio.yml` keys. Unknown root keys fail validation, and the rules
32
+ that judge a workspace live on the server — so the printed contract is the
33
+ current one by construction.
34
+
16
35
  ## What a workspace is
17
36
 
18
- A directory with a `vatio.yml` in it. That file says which Vatio workspace the
19
- directory deploys to, what the agent does, and how the widget looks; `tools/`,
20
- `lib/` and `auth/` next to it are plain JavaScript the agent runs.
37
+ A directory with a `vatio.yml` in it:
38
+
39
+ ```text
40
+ support-agent/
41
+ vatio.yml # required: the agent, its tools, its knowledge
42
+ tools/*.yml # one HTTP request each, described in YAML
43
+ identity.pub # public key that verifies the JWT saying who a visitor is
44
+ ```
21
45
 
22
- Nothing constrains where that directory lives — an agent can sit inside the
23
- repository of the backend it calls, and every command except `init` finds it by
24
- walking up from the current directory. There is no `--workspace` flag.
46
+ Only create the files you need. Nothing constrains where that directory lives —
47
+ an agent can sit inside the repository of the backend it calls, and every
48
+ command except `init` finds it by walking up from the current directory. There
49
+ is no `--workspace` flag.
25
50
 
26
51
  ## Commands
27
52
 
28
53
  ```
29
- init [SLUG] [--name NAME] Create vatio.yml here, and the remote to match
30
- login [--base-url URL] Authorize this machine in a browser
54
+ init [SLUG] Create vatio.yml here, and the remote to match
55
+ login | logout Authorize this machine in a browser, or forget it
56
+ doctor Node, config, workspace and token status
57
+
31
58
  push [--env NAME] Validate the workspace, then update a preview
32
59
  publish [--env NAME] Promote a preview to live
33
60
  diff [--env NAME] What this directory would change
34
61
  status Preview and live deployment state
35
62
  rollback Restore the previous live deployment
36
63
  tools check Validate the workspace against Vatio's contracts
37
- secrets list|set|rm Credentials your tools read as ctx.env
38
- tokens list|create|revoke Publishable tokens for the widget and the SDK
39
- widget [--env NAME] What the platform enforces, and the tokens there are
40
- config show|set|unset base_url and token, per developer
41
- doctor Node, config, workspace and token status
64
+
65
+ chat "message" [--env NAME] Talk to your own agent; the token is the identity
66
+ chat transcript|debug|reset|destroy
67
+
68
+ kb list|show|create|rm Knowledge bases
69
+ kb write|cat|rm-entry Entries, from a file or stdin
70
+ kb follow|unfollow|refresh Read a site into a base, nightly
71
+
72
+ secrets list|set|rm Credentials your tools read
73
+ tokens list|create|revoke Publishable tokens for the widget and the SDK
74
+ widget [--env NAME] What the platform enforces, and the tokens there are
75
+ auth --new-key Keypair that signs the visitor's JWT
76
+
77
+ whatsapp ... Shared preview number, or your own business number
78
+ instagram ... Shared sandbox, or your own account
79
+
80
+ docs [--save [PATH]] The whole developer contract
81
+ mcp Speak MCP over stdio, for a coding agent
82
+ issue "what should change" Send a change request, workspace attached
83
+ config show|set|unset base_url and token, per developer
42
84
  ```
43
85
 
44
86
  `--env NAME` targets a deployment: `live`, `preview`, or a named preview like
@@ -2,17 +2,19 @@
2
2
 
3
3
  import { DeviceAuthClient, ExpiredError, PendingError } from "../device-auth.mjs";
4
4
  import { DEFAULT_BASE_URL } from "../config.mjs";
5
- import { fail, openBrowser, sleep, takeValue } from "../support.mjs";
5
+ import { ask, fail, openBrowser, sleep, takeValue } from "../support.mjs";
6
6
 
7
- // A token is minted by a human approving a code in a browser, not by anything
8
- // the CLI can do on its own. That is the whole point: a secret this CLI holds
9
- // was authorized by someone who was there.
10
- export async function deviceLogin({ baseUrl, message }) {
7
+ // A token is minted by a human approving it in a browser, never by the CLI
8
+ // alone. The email turns that approval into a link we mail, which is what lets
9
+ // somebody with no account finish it; typing nothing falls back to the URL.
10
+ export async function deviceLogin({ baseUrl, message, email }) {
11
11
  const resolved = String(baseUrl || process.env.VATIO_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
12
12
  const client = new DeviceAuthClient({ baseUrl: resolved });
13
13
 
14
14
  console.log(message);
15
- const auth = await client.start();
15
+ const address = String(email ?? (await ask("Your email (we send you a link): "))).trim();
16
+
17
+ const auth = await client.start({ email: address });
16
18
  const deviceCode = auth.device_code;
17
19
  const userCode = auth.user_code;
18
20
  const verificationUri = auth.verification_uri_complete ?? auth.verification_uri;
@@ -21,10 +23,16 @@ export async function deviceLogin({ baseUrl, message }) {
21
23
  if (!deviceCode || !verificationUri) fail("The platform did not return a device code");
22
24
 
23
25
  console.log("");
24
- console.log(`Open: ${verificationUri}`);
25
- console.log(`Code: ${userCode}`);
26
+ if (auth.email_sent) {
27
+ console.log(`We emailed ${address} a link.`);
28
+ console.log(`This terminal is ${userCode} — check it matches before you approve.`);
29
+ } else {
30
+ console.log(`Open: ${verificationUri}`);
31
+ console.log(`Code: ${userCode}`);
32
+ openBrowser(verificationUri);
33
+ }
26
34
  console.log("");
27
- openBrowser(verificationUri);
35
+ console.log("Waiting…");
28
36
 
29
37
  const deadline = Date.now() + expiresIn * 1000;
30
38
  for (;;) {
@@ -17,8 +17,20 @@ import { stringify as stringifyYaml } from "yaml";
17
17
  import { buildBundle, BundleError } from "../bundle.mjs";
18
18
  import { ApiClient, CliClient } from "../api-client.mjs";
19
19
  import { NotFoundError } from "../http.mjs";
20
+ import { deviceLogin } from "./auth.mjs";
20
21
  import { detectGitSha, fail, takeEnv, takeFlag, takeValue } from "../support.mjs";
21
22
 
23
+ // The first command that makes something exist on a server, so the first one
24
+ // that needs to know whose it is.
25
+ export async function ensureAuthorized(config) {
26
+ if (config.resolveToken()) return;
27
+
28
+ const auth = await deviceLogin({ message: "Before I can push this, I need to know whose it is." });
29
+ config.writeAuth({ baseUrl: auth.baseUrl, token: auth.token });
30
+ console.log(`Token saved to ${config.configPath}`);
31
+ console.log("");
32
+ }
33
+
22
34
  export function deployClient(config, workspace) {
23
35
  const base = config.resolveBaseUrl();
24
36
  if (!base) fail("VATIO_BASE_URL is required (env or `vatio config set base_url …`)");
@@ -91,6 +103,8 @@ export async function push(config, args) {
91
103
  const environment = takeEnv(args, { fallback: null });
92
104
  const workspace = config.resolveWorkspaceRequired();
93
105
 
106
+ await ensureAuthorized(config);
107
+
94
108
  // The remote has to exist before the workspace can be checked: the slug is in
95
109
  // the URL of the call that checks it. It is created unnamed -- the push a few
96
110
  // lines down applies `business.name` from the manifest anyway.
@@ -121,7 +135,7 @@ export async function push(config, args) {
121
135
  for (const warning of asArray(payload.warnings)) console.log(` warning: ${warning}`);
122
136
  if (payload.preview_url) console.log(`Preview: ${payload.preview_url}`);
123
137
 
124
- const hint = payload.whatsapp_preview_hint;
138
+ const hint = payload.first_push_hint;
125
139
  if (hint) {
126
140
  console.log("");
127
141
  console.log(hint.message);
@@ -8,7 +8,6 @@ import { KEYS, SLUG_FORMAT } from "../config.mjs";
8
8
  import { MANIFEST_FILE, manifestPath } from "../workspace.mjs";
9
9
  import { VERSION } from "../version.mjs";
10
10
  import { fail, takeValue } from "../support.mjs";
11
- import { deviceLogin } from "./auth.mjs";
12
11
  import { workspacesClient } from "./deploy.mjs";
13
12
 
14
13
  export const DOCS_URL = "https://docs.vatio.ai";
@@ -81,7 +80,9 @@ export function configCommand(config, args) {
81
80
  // the point of the file is that an agent can live inside the repository of the
82
81
  // backend it calls, not in a separate folder of agents.
83
82
  export async function init(config, args) {
84
- const baseUrl = takeValue(args, "--base-url");
83
+ // Still accepted and still ignored here -- `vatio login` owns --base-url,
84
+ // and swallowing it keeps it out of the slug below.
85
+ takeValue(args, "--base-url");
85
86
  const name = takeValue(args, "--name");
86
87
 
87
88
  const target = process.cwd();
@@ -100,25 +101,19 @@ export async function init(config, args) {
100
101
  fail(`Invalid slug "${slug}" (use lowercase letters, numbers, hyphens)`);
101
102
  }
102
103
 
103
- // Logging in first means the remote is created against a base_url the
104
- // developer has actually authorized, and a fresh machine gets through
105
- // `vatio init` in one command instead of two.
106
- if (!config.resolveToken()) {
107
- const auth = await deviceLogin({ baseUrl, message: "Starting device authorization for Vatio CLI…" });
108
- config.writeAuth({ baseUrl: auth.baseUrl, token: auth.token });
109
- console.log(`Token saved to ${config.configPath}`);
110
- console.log("");
104
+ // No login here: identity is what `push` needs, because `push` is the first
105
+ // thing that makes something run on a server. This writes a file.
106
+ const authorized = Boolean(config.resolveToken());
107
+ if (authorized) {
108
+ const clients = workspacesClient(config);
109
+ if (!(await clients.workspaceExists(slug))) await clients.createWorkspace({ slug, name });
111
110
  }
112
111
 
113
- const clients = workspacesClient(config);
114
- const created = !(await clients.workspaceExists(slug));
115
- if (created) await clients.createWorkspace({ slug, name });
116
-
117
112
  const manifest = join(target, MANIFEST_FILE);
118
113
  writeFileSync(manifest, starterManifest(slug, name));
119
114
 
120
- console.log(`${created ? "Created" : "Linked"} remote workspace ${slug}`);
121
115
  console.log(`Wrote ${manifest}`);
116
+ if (!authorized) console.log(`Local only for now — \`vatio push\` is what creates ${slug} on the platform.`);
122
117
  console.log("");
123
118
  console.log("Next:");
124
119
  console.log(` edit ${MANIFEST_FILE}, then \`vatio push\` and \`vatio publish\``);
@@ -15,8 +15,12 @@ export class DeviceAuthClient {
15
15
  this.baseUrl = String(baseUrl ?? "").replace(/\/$/, "");
16
16
  }
17
17
 
18
- start() {
19
- return this.#post("/cli/device_authorizations", {});
18
+ // With an `email` the platform mails the approval link; without one the
19
+ // response is exactly what it always was.
20
+ start({ email } = {}) {
21
+ const body = {};
22
+ if (String(email ?? "").trim() !== "") body.email = String(email).trim();
23
+ return this.#post("/cli/device_authorizations", body);
20
24
  }
21
25
 
22
26
  poll({ deviceCode }) {
package/lib/support.mjs CHANGED
@@ -104,3 +104,17 @@ export function openBrowser(url) {
104
104
  export function sleep(ms) {
105
105
  return new Promise((resolve) => setTimeout(resolve, ms));
106
106
  }
107
+
108
+ // Returns "" with nothing attached to stdin, so an agent running `vatio push`
109
+ // in a pipeline falls back to the URL instead of hanging on a prompt.
110
+ export async function ask(question) {
111
+ if (!process.stdin.isTTY) return "";
112
+
113
+ const { createInterface } = await import("node:readline/promises");
114
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
115
+ try {
116
+ return (await rl.question(question)).trim();
117
+ } finally {
118
+ rl.close();
119
+ }
120
+ }
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@vatio-ai/cli",
3
- "version": "0.46.0",
3
+ "version": "0.47.1",
4
4
  "description": "Vatio CLI — deploy and manage Vatio agent workspaces",
5
- "keywords": ["vatio", "agent", "ai", "cli", "deploy"],
5
+ "keywords": [
6
+ "vatio",
7
+ "agent",
8
+ "ai",
9
+ "cli",
10
+ "deploy"
11
+ ],
6
12
  "homepage": "https://vatio.ai",
7
13
  "bugs": "https://docs.vatio.ai",
8
14
  "license": "SEE LICENSE IN LICENSE.txt",