@vatio-ai/cli 0.37.2

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.
@@ -0,0 +1,106 @@
1
+ // Terminal plumbing shared by every command: how the CLI stops, and how it
2
+ // reads the flags that mean the same thing everywhere.
3
+
4
+ import { execFileSync } from "node:child_process";
5
+
6
+ export class UserError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "UserError";
10
+ }
11
+ }
12
+
13
+ // The Ruby CLI's `abort`: a sentence on stderr and exit 1. Thrown rather than
14
+ // exiting on the spot so one place at the top decides how a failure prints.
15
+ export function fail(message) {
16
+ throw new UserError(message);
17
+ }
18
+
19
+ // `--env NAME` is the one way to say which deployment a command is about:
20
+ // live, preview, or a named preview like pr-42.
21
+ //
22
+ // The default is the command's own and is passed in, because the sensible
23
+ // answer differs: push writes a preview, pull brings down whatever is actually
24
+ // running. `--as` was the spelling on push and publish and stays as an alias --
25
+ // it is sitting in CI jobs, and there is nothing wrong with what it does.
26
+ export function takeEnv(args, { fallback = null } = {}) {
27
+ let environment = fallback;
28
+ const rest = [];
29
+
30
+ for (let i = 0; i < args.length; i += 1) {
31
+ const arg = args[i];
32
+ const match = /^--(env|as)(?:=(.*))?$/.exec(arg);
33
+ if (!match) {
34
+ rest.push(arg);
35
+ continue;
36
+ }
37
+ if (match[2] !== undefined) {
38
+ environment = match[2];
39
+ continue;
40
+ }
41
+ i += 1;
42
+ if (i >= args.length) fail(`${match[0]} needs a deployment name (live, preview, or a preview name)`);
43
+ environment = args[i];
44
+ }
45
+
46
+ args.length = 0;
47
+ args.push(...rest);
48
+ return environment;
49
+ }
50
+
51
+ export function takeFlag(args, name) {
52
+ const index = args.indexOf(name);
53
+ if (index === -1) return false;
54
+ args.splice(index, 1);
55
+ return true;
56
+ }
57
+
58
+ export function takeValue(args, name) {
59
+ for (let i = 0; i < args.length; i += 1) {
60
+ if (args[i] === name) {
61
+ const value = args[i + 1];
62
+ if (value === undefined) fail(`${name} needs a value`);
63
+ args.splice(i, 2);
64
+ return value;
65
+ }
66
+ const prefix = `${name}=`;
67
+ if (args[i].startsWith(prefix)) {
68
+ const value = args[i].slice(prefix.length);
69
+ args.splice(i, 1);
70
+ return value;
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+
76
+ // Stamped on a deployment so a revision can be traced back to a commit. Absent
77
+ // outside a repository, which is fine -- a workspace does not have to be in one.
78
+ export function detectGitSha(cwd) {
79
+ try {
80
+ return execFileSync("git", ["rev-parse", "HEAD"], {
81
+ cwd,
82
+ encoding: "utf8",
83
+ stdio: ["ignore", "pipe", "ignore"]
84
+ }).trim() || null;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+
90
+ export function openBrowser(url) {
91
+ if (!url) return;
92
+ const command =
93
+ process.platform === "darwin" ? ["open", [url]]
94
+ : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]]
95
+ : ["xdg-open", [url]];
96
+ try {
97
+ execFileSync(command[0], command[1], { stdio: "ignore" });
98
+ } catch {
99
+ // A headless machine has no browser to open, and the URL is already
100
+ // printed above this call. Nothing to report.
101
+ }
102
+ }
103
+
104
+ export function sleep(ms) {
105
+ return new Promise((resolve) => setTimeout(resolve, ms));
106
+ }
@@ -0,0 +1,11 @@
1
+ // Read from package.json rather than written twice: `npx vatio` resolves a
2
+ // version from the registry, and a constant in here could disagree with the
3
+ // one the developer actually downloaded.
4
+
5
+ import { readFileSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import { dirname, join } from "node:path";
8
+
9
+ const here = dirname(fileURLToPath(import.meta.url));
10
+
11
+ export const VERSION = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8")).version;
@@ -0,0 +1,75 @@
1
+ // Where the workspace is, and which remote it deploys to. Addressing, not
2
+ // contract -- the port of cli/lib/workspace_root.rb, and deliberately just as
3
+ // small.
4
+ //
5
+ // What a `tools/*.js` declares, which blocks `vatio.yml` accepts and whether
6
+ // any of it is valid are answered by POST /api/v1/:slug/deploy/check. The one
7
+ // thing a client cannot ask the platform for is the address of that call: the
8
+ // slug is in its URL. So this reads `workspace:` and stops.
9
+
10
+ import { existsSync, readFileSync, statSync } from "node:fs";
11
+ import { dirname, join, parse as parsePath, resolve } from "node:path";
12
+ import { parse as parseYaml } from "yaml";
13
+
14
+ export const MANIFEST_FILE = "vatio.yml";
15
+ // Keep in sync with Workspace::SLUG_FORMAT in urcalab/vatio. A slug that gets
16
+ // past this is still the platform's to reject; matching here only turns a typo
17
+ // into a sentence instead of a 404.
18
+ export const SLUG_FORMAT = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
19
+ // A workspace has exactly one agent and it is the conversation entry point.
20
+ export const ENTRY_AGENT = "main";
21
+
22
+ // The manifest in a directory, or null when the directory is not a workspace
23
+ // root. This file is the only thing that makes one.
24
+ export function manifestPath(dir) {
25
+ const candidate = join(dir, MANIFEST_FILE);
26
+ try {
27
+ return statSync(candidate).isFile() ? candidate : null;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ // Any directory holding a manifest is a workspace root, so a vatio.yml can sit
34
+ // anywhere -- beside a backend, in a monorepo's agents/ folder, at the top of
35
+ // its own repo. The nearest one wins.
36
+ export function findWorkspaceRoot(startDir) {
37
+ let current = resolve(startDir);
38
+ for (;;) {
39
+ if (manifestPath(current)) return current;
40
+ const parent = dirname(current);
41
+ if (parent === current || parent === parsePath(current).root) {
42
+ return manifestPath(parent) ? parent : null;
43
+ }
44
+ current = parent;
45
+ }
46
+ }
47
+
48
+ // Which workspace this directory deploys to. Addressing, not content: it names
49
+ // the remote, so it never rides along in the deployed manifest. Null when the
50
+ // file does not declare one.
51
+ export function workspaceSlug(dir) {
52
+ const file = manifestPath(dir);
53
+ if (!file) return null;
54
+
55
+ const main = readManifest(file);
56
+ if (!main || typeof main !== "object" || Array.isArray(main)) return null;
57
+
58
+ const slug = String(main.workspace ?? "").trim().toLowerCase();
59
+ if (slug === "") return null;
60
+ if (!SLUG_FORMAT.test(slug)) {
61
+ throw new Error(
62
+ `${MANIFEST_FILE}: workspace "${slug}" is not a slug (lowercase letters, numbers, hyphens)`
63
+ );
64
+ }
65
+ return slug;
66
+ }
67
+
68
+ function readManifest(file) {
69
+ if (!existsSync(file)) return null;
70
+ try {
71
+ return parseYaml(readFileSync(file, "utf8"));
72
+ } catch (error) {
73
+ throw new Error(`${MANIFEST_FILE}: ${error.message}`);
74
+ }
75
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@vatio-ai/cli",
3
+ "version": "0.37.2",
4
+ "description": "Vatio CLI — deploy and manage Vatio agent workspaces",
5
+ "keywords": ["vatio", "agent", "ai", "cli", "deploy"],
6
+ "homepage": "https://vatio.ai",
7
+ "bugs": "https://vatio.ai/docs",
8
+ "license": "SEE LICENSE IN LICENSE.txt",
9
+ "author": "Vatio",
10
+ "type": "module",
11
+ "bin": {
12
+ "vatio": "bin/vatio.mjs"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "engines": {
18
+ "node": ">=20.0.0"
19
+ },
20
+ "files": [
21
+ "bin",
22
+ "lib",
23
+ "README.md",
24
+ "LICENSE.txt"
25
+ ],
26
+ "dependencies": {
27
+ "yaml": "^2.5.0"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/urcalab/vatio.git",
32
+ "directory": "cli"
33
+ }
34
+ }