@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 +57 -15
- package/lib/commands/auth.mjs +17 -9
- package/lib/commands/deploy.mjs +15 -1
- package/lib/commands/misc.mjs +10 -15
- package/lib/device-auth.mjs +6 -2
- package/lib/support.mjs +14 -0
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,44 +1,86 @@
|
|
|
1
1
|
# @vatio-ai/cli
|
|
2
2
|
|
|
3
|
-
The CLI for [Vatio](https://vatio.ai) —
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
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 —
|
|
23
|
-
repository of the backend it calls, and every
|
|
24
|
-
walking up from the current directory. There
|
|
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]
|
|
30
|
-
login
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
package/lib/commands/auth.mjs
CHANGED
|
@@ -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
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
35
|
+
console.log("Waiting…");
|
|
28
36
|
|
|
29
37
|
const deadline = Date.now() + expiresIn * 1000;
|
|
30
38
|
for (;;) {
|
package/lib/commands/deploy.mjs
CHANGED
|
@@ -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.
|
|
138
|
+
const hint = payload.first_push_hint;
|
|
125
139
|
if (hint) {
|
|
126
140
|
console.log("");
|
|
127
141
|
console.log(hint.message);
|
package/lib/commands/misc.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
const
|
|
108
|
-
|
|
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\``);
|
package/lib/device-auth.mjs
CHANGED
|
@@ -15,8 +15,12 @@ export class DeviceAuthClient {
|
|
|
15
15
|
this.baseUrl = String(baseUrl ?? "").replace(/\/$/, "");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
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.
|
|
3
|
+
"version": "0.47.1",
|
|
4
4
|
"description": "Vatio CLI — deploy and manage Vatio agent workspaces",
|
|
5
|
-
"keywords": [
|
|
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",
|