@vatio-ai/cli 0.45.0 → 0.47.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
@@ -45,7 +45,7 @@ doctor Node, config, workspace and token status
45
45
  `pr-42`.
46
46
 
47
47
  Run `npx @vatio-ai/cli help` for the whole surface, and see
48
- [vatio.ai/docs](https://vatio.ai/docs) for the platform.
48
+ [docs.vatio.ai](https://docs.vatio.ai) for the platform.
49
49
 
50
50
  ## Where your credentials live
51
51
 
@@ -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);
@@ -81,7 +81,7 @@ export async function auth(config, args) {
81
81
  console.log(" }");
82
82
  console.log("");
83
83
  console.log("Put it on the widget as data-visitor-token, and push.");
84
- console.log("Full examples: https://vatio.ai/docs#auth");
84
+ console.log("Full examples: https://docs.vatio.ai/authentication/");
85
85
  }
86
86
 
87
87
  function ignorePrivateKey(root) {
@@ -8,10 +8,9 @@ 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
- export const DOCS_URL = "https://vatio.ai/docs";
13
+ export const DOCS_URL = "https://docs.vatio.ai";
15
14
 
16
15
  export function version() {
17
16
  console.log(`Vatio CLI ${VERSION} (node)`);
@@ -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\``);
package/lib/config.mjs CHANGED
@@ -186,7 +186,7 @@ export class Config {
186
186
  const normalized = String(key ?? "").trim();
187
187
  if (normalized === "assistants") {
188
188
  throw new ConfigError(
189
- 'config key "assistants" was removed — Vatio no longer installs local skills; see https://vatio.ai/docs'
189
+ 'config key "assistants" was removed — Vatio no longer installs local skills; see https://docs.vatio.ai'
190
190
  );
191
191
  }
192
192
  if (normalized === "workspace") {
@@ -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/help.mjs CHANGED
@@ -68,6 +68,6 @@ flag, the directory decides.
68
68
 
69
69
  Environment: VATIO_BASE_URL, VATIO_TOKEN, VATIO_HOME
70
70
 
71
- Docs: https://vatio.ai/docs
71
+ Docs: https://docs.vatio.ai
72
72
  `;
73
73
  }
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,10 +1,10 @@
1
1
  {
2
2
  "name": "@vatio-ai/cli",
3
- "version": "0.45.0",
3
+ "version": "0.47.0",
4
4
  "description": "Vatio CLI — deploy and manage Vatio agent workspaces",
5
5
  "keywords": ["vatio", "agent", "ai", "cli", "deploy"],
6
6
  "homepage": "https://vatio.ai",
7
- "bugs": "https://vatio.ai/docs",
7
+ "bugs": "https://docs.vatio.ai",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Vatio",
10
10
  "type": "module",