@vatio-ai/cli 0.46.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/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 +1 -1
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
|
+
}
|