@vatio-ai/cli 0.53.0 → 0.54.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/bin/vatio.mjs +3 -0
- package/lib/api-client.mjs +79 -33
- package/lib/branch.mjs +51 -0
- package/lib/commands/channels.mjs +39 -20
- package/lib/commands/chat.mjs +1 -1
- package/lib/commands/deploy.mjs +15 -10
- package/lib/commands/env.mjs +90 -0
- package/lib/commands/knowledge.mjs +99 -81
- package/lib/commands/mcp.mjs +36 -18
- package/lib/commands/workspace-admin.mjs +29 -21
- package/lib/help.mjs +30 -16
- package/lib/http.mjs +4 -1
- package/lib/support.mjs +23 -10
- package/package.json +1 -1
package/bin/vatio.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { helpText } from "../lib/help.mjs";
|
|
|
17
17
|
import { login, logout } from "../lib/commands/auth.mjs";
|
|
18
18
|
import { configCommand, doctor, init, version } from "../lib/commands/misc.mjs";
|
|
19
19
|
import { diff, publish, push, rollback, status, toolsCheck } from "../lib/commands/deploy.mjs";
|
|
20
|
+
import { env } from "../lib/commands/env.mjs";
|
|
20
21
|
import { secrets, tokens, widget } from "../lib/commands/workspace-admin.mjs";
|
|
21
22
|
import { auth } from "../lib/commands/identity-key.mjs";
|
|
22
23
|
import { chat } from "../lib/commands/chat.mjs";
|
|
@@ -70,6 +71,8 @@ async function run(command, args) {
|
|
|
70
71
|
return await rollback(config);
|
|
71
72
|
case "status":
|
|
72
73
|
return await status(config);
|
|
74
|
+
case "env":
|
|
75
|
+
return await env(config, args);
|
|
73
76
|
case "diff":
|
|
74
77
|
return await diff(config, args);
|
|
75
78
|
case "tools":
|
package/lib/api-client.mjs
CHANGED
|
@@ -29,9 +29,10 @@ export class ApiClient extends HttpClient {
|
|
|
29
29
|
return this.post("/deploy/check", body);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
pushPreview({ manifest, gitSha = null, createdBy = null, environment = null }) {
|
|
32
|
+
pushPreview({ manifest, gitSha = null, gitBranch = null, createdBy = null, environment = null }) {
|
|
33
33
|
const body = { manifest };
|
|
34
34
|
if (gitSha) body.git_sha = gitSha;
|
|
35
|
+
if (gitBranch) body.git_branch = gitBranch;
|
|
35
36
|
if (createdBy) body.created_by = createdBy;
|
|
36
37
|
if (environment) body.environment = environment;
|
|
37
38
|
return this.put("/deploy/preview", body);
|
|
@@ -58,18 +59,36 @@ export class ApiClient extends HttpClient {
|
|
|
58
59
|
return this.get(`/deploy/revisions/${encode(id)}`);
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// --- environments ---------------------------------------------------
|
|
63
|
+
|
|
64
|
+
environments() {
|
|
65
|
+
return this.get("/deploy/environments");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
environment(name) {
|
|
69
|
+
return this.get(`/deploy/environments/${encode(name)}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
deleteEnvironment(name) {
|
|
73
|
+
return this.delete(`/deploy/environments/${encode(name)}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
// --- secrets --------------------------------------------------------
|
|
62
77
|
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
// Without an environment, the value everywhere; with one, the value only
|
|
79
|
+
// that environment uses instead.
|
|
80
|
+
listSecrets({ environment = null } = {}) {
|
|
81
|
+
return this.get("/deploy/secrets", { environment });
|
|
65
82
|
}
|
|
66
83
|
|
|
67
|
-
upsertSecret({ key, value }) {
|
|
68
|
-
|
|
84
|
+
upsertSecret({ key, value, environment = null }) {
|
|
85
|
+
const body = { value };
|
|
86
|
+
if (environment) body.environment = environment;
|
|
87
|
+
return this.put(`/deploy/secrets/${encode(key)}`, body);
|
|
69
88
|
}
|
|
70
89
|
|
|
71
|
-
deleteSecret({ key }) {
|
|
72
|
-
return this.delete(`/deploy/secrets/${encode(key)}`);
|
|
90
|
+
deleteSecret({ key, environment = null }) {
|
|
91
|
+
return this.delete(`/deploy/secrets/${encode(key)}${query({ environment })}`);
|
|
73
92
|
}
|
|
74
93
|
|
|
75
94
|
// --- knowledge bases ------------------------------------------------
|
|
@@ -77,12 +96,16 @@ export class ApiClient extends HttpClient {
|
|
|
77
96
|
// A base holds entries (markdown) and reads sites (a URL or a pattern). Two
|
|
78
97
|
// nouns, and every call below is one of them.
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
|
|
99
|
+
// Every read and write below is as one environment sees the base: a
|
|
100
|
+
// branch's, which keeps its changes on top of live, or `live` itself.
|
|
101
|
+
// Without one the platform means `preview`.
|
|
102
|
+
|
|
103
|
+
knowledgeBases({ environment = null } = {}) {
|
|
104
|
+
return this.get("/knowledge_bases", { environment });
|
|
82
105
|
}
|
|
83
106
|
|
|
84
|
-
knowledgeBase(name) {
|
|
85
|
-
return this.get(`/knowledge_bases/${encode(name)}
|
|
107
|
+
knowledgeBase(name, { environment = null } = {}) {
|
|
108
|
+
return this.get(`/knowledge_bases/${encode(name)}`, { environment });
|
|
86
109
|
}
|
|
87
110
|
|
|
88
111
|
createKnowledgeBase(name) {
|
|
@@ -97,19 +120,21 @@ export class ApiClient extends HttpClient {
|
|
|
97
120
|
return this.post(`/knowledge_bases/${encode(name)}/refresh`, {});
|
|
98
121
|
}
|
|
99
122
|
|
|
100
|
-
knowledgeEntry({ base, name }) {
|
|
101
|
-
return this.get(`/knowledge_bases/${encode(base)}/entries/${encode(name)}
|
|
123
|
+
knowledgeEntry({ base, name, environment = null, version = null }) {
|
|
124
|
+
return this.get(`/knowledge_bases/${encode(base)}/entries/${encode(name)}`, { environment, version });
|
|
102
125
|
}
|
|
103
126
|
|
|
104
127
|
// Put this markdown under this name, whether or not the entry exists yet --
|
|
105
128
|
// one call for "write" and "rewrite", because from the terminal they are the
|
|
106
129
|
// same sentence.
|
|
107
|
-
writeKnowledgeEntry({ base, name, content }) {
|
|
108
|
-
|
|
130
|
+
writeKnowledgeEntry({ base, name, content, environment = null }) {
|
|
131
|
+
const body = { content };
|
|
132
|
+
if (environment) body.environment = environment;
|
|
133
|
+
return this.patch(`/knowledge_bases/${encode(base)}/entries/${encode(name)}`, body);
|
|
109
134
|
}
|
|
110
135
|
|
|
111
|
-
deleteKnowledgeEntry({ base, name }) {
|
|
112
|
-
return this.delete(`/knowledge_bases/${encode(base)}/entries/${encode(name)}`);
|
|
136
|
+
deleteKnowledgeEntry({ base, name, environment = null }) {
|
|
137
|
+
return this.delete(`/knowledge_bases/${encode(base)}/entries/${encode(name)}${query({ environment })}`);
|
|
113
138
|
}
|
|
114
139
|
|
|
115
140
|
followKnowledgeSite({ base, url }) {
|
|
@@ -127,22 +152,19 @@ export class ApiClient extends HttpClient {
|
|
|
127
152
|
return this.post(`/knowledge_bases/${encode(base)}/sites/refresh`, { url });
|
|
128
153
|
}
|
|
129
154
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return this.get(`/knowledge_bases/${encode(base)}/changes`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
publishKnowledgeBase({ base, note }) {
|
|
137
|
-
return this.post(`/knowledge_bases/${encode(base)}/publish`, { note });
|
|
155
|
+
// What the environment changed that live does not have yet.
|
|
156
|
+
knowledgeChanges(base, { environment = null } = {}) {
|
|
157
|
+
return this.get(`/knowledge_bases/${encode(base)}/changes`, { environment });
|
|
138
158
|
}
|
|
139
159
|
|
|
140
|
-
|
|
141
|
-
|
|
160
|
+
publishKnowledgeBase({ base, environment = null }) {
|
|
161
|
+
const body = {};
|
|
162
|
+
if (environment) body.environment = environment;
|
|
163
|
+
return this.post(`/knowledge_bases/${encode(base)}/publish`, body);
|
|
142
164
|
}
|
|
143
165
|
|
|
144
|
-
|
|
145
|
-
return this.
|
|
166
|
+
knowledgeHistory(base) {
|
|
167
|
+
return this.get(`/knowledge_bases/${encode(base)}/history`);
|
|
146
168
|
}
|
|
147
169
|
|
|
148
170
|
// --- publishable tokens and the widget ------------------------------
|
|
@@ -176,8 +198,15 @@ export class ApiClient extends HttpClient {
|
|
|
176
198
|
return this.get("/test_phone_numbers");
|
|
177
199
|
}
|
|
178
200
|
|
|
179
|
-
addTestPhoneNumber({ phoneNumber }) {
|
|
180
|
-
|
|
201
|
+
addTestPhoneNumber({ phoneNumber, environment = null }) {
|
|
202
|
+
const body = { phone_number: phoneNumber };
|
|
203
|
+
if (environment) body.environment = environment;
|
|
204
|
+
return this.post("/test_phone_numbers", body);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Which environment the phone reaches on the shared number.
|
|
208
|
+
pointTestPhoneNumber({ phoneNumber, environment }) {
|
|
209
|
+
return this.patch(`/test_phone_numbers/${waId(phoneNumber)}`, { environment });
|
|
181
210
|
}
|
|
182
211
|
|
|
183
212
|
verifyTestPhoneNumber({ phoneNumber, code }) {
|
|
@@ -245,8 +274,15 @@ export class ApiClient extends HttpClient {
|
|
|
245
274
|
// Declares which Instagram account you mean. The code comes later, by DM,
|
|
246
275
|
// once that account writes to the shared preview -- Instagram will not let
|
|
247
276
|
// Vatio message an account that has not messaged it first.
|
|
248
|
-
addTestInstagramAccount({ username }) {
|
|
249
|
-
|
|
277
|
+
addTestInstagramAccount({ username, environment = null }) {
|
|
278
|
+
const body = { username };
|
|
279
|
+
if (environment) body.environment = environment;
|
|
280
|
+
return this.post("/test_instagram_accounts", body);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Which environment the account reaches on the shared one.
|
|
284
|
+
pointTestInstagramAccount({ id, environment }) {
|
|
285
|
+
return this.patch(`/test_instagram_accounts/${encode(id)}`, { environment });
|
|
250
286
|
}
|
|
251
287
|
|
|
252
288
|
// Addressed by the code alone: the workspace is already in the path and the
|
|
@@ -319,6 +355,16 @@ export class CliClient extends HttpClient {
|
|
|
319
355
|
}
|
|
320
356
|
}
|
|
321
357
|
|
|
358
|
+
// A DELETE carries no body, so what it needs beyond the path rides as a query.
|
|
359
|
+
function query(params) {
|
|
360
|
+
const search = new URLSearchParams();
|
|
361
|
+
for (const [key, value] of Object.entries(params)) {
|
|
362
|
+
if (value !== undefined && value !== null && value !== "") search.set(key, String(value));
|
|
363
|
+
}
|
|
364
|
+
const text = search.toString();
|
|
365
|
+
return text ? `?${text}` : "";
|
|
366
|
+
}
|
|
367
|
+
|
|
322
368
|
// The path carries the digits: "+" and spaces do not survive a URL, and the
|
|
323
369
|
// digits are what the API stores as `wa_id`.
|
|
324
370
|
function waId(phoneNumber) {
|
package/lib/branch.mjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The environment a git branch gets, so that working on a branch is working in
|
|
2
|
+
// its own environment without saying so: `vatio push` from `fix/pagos` lands on
|
|
3
|
+
// `fix-pagos`, and so do `vatio kb write`, `vatio secrets set` and `vatio chat`.
|
|
4
|
+
// The pull request opened from that branch deploys to the same one.
|
|
5
|
+
//
|
|
6
|
+
// The default branch is not a branch environment: there, and outside a
|
|
7
|
+
// repository, every command keeps the default it always had.
|
|
8
|
+
//
|
|
9
|
+
// The name is derived exactly as the platform derives it for a pull request
|
|
10
|
+
// (Vatio::Environment.for_branch, backend/app/vatio/environment.rb). Kept in
|
|
11
|
+
// step by hand; both are short.
|
|
12
|
+
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
|
|
16
|
+
const RESERVED = new Set(["live", "preview", "staging", "sandbox", "main"]);
|
|
17
|
+
const MAX_LENGTH = 40;
|
|
18
|
+
|
|
19
|
+
export function environmentForBranch(ref) {
|
|
20
|
+
const raw = String(ref ?? "").replace(/^refs\/heads\//, "");
|
|
21
|
+
let slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
22
|
+
if (slug === "") return null;
|
|
23
|
+
|
|
24
|
+
if (RESERVED.has(slug)) slug = `branch-${slug}`;
|
|
25
|
+
if (slug.length <= MAX_LENGTH) return slug;
|
|
26
|
+
|
|
27
|
+
const hash = createHash("sha1").update(String(ref)).digest("hex").slice(0, 6);
|
|
28
|
+
return `${slug.slice(0, 33).replace(/-+$/, "")}-${hash}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// { branch, environment } on a branch other than the default one; null on the
|
|
32
|
+
// default branch, outside a repository, or with HEAD detached.
|
|
33
|
+
export function branchEnvironment(cwd) {
|
|
34
|
+
const branch = git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]);
|
|
35
|
+
if (!branch) return null;
|
|
36
|
+
|
|
37
|
+
const remoteHead = git(cwd, ["symbolic-ref", "--short", "-q", "refs/remotes/origin/HEAD"]);
|
|
38
|
+
const defaultBranch = remoteHead ? remoteHead.replace(/^[^/]+\//, "") : null;
|
|
39
|
+
if (branch === defaultBranch || (!defaultBranch && (branch === "main" || branch === "master"))) return null;
|
|
40
|
+
|
|
41
|
+
const environment = environmentForBranch(branch);
|
|
42
|
+
return environment ? { branch, environment } : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function git(cwd, args) {
|
|
46
|
+
try {
|
|
47
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// would say nothing about which one.
|
|
8
8
|
|
|
9
9
|
import { deployClient } from "./deploy.mjs";
|
|
10
|
-
import { fail, openBrowser } from "../support.mjs";
|
|
10
|
+
import { fail, openBrowser, takeEnv } from "../support.mjs";
|
|
11
11
|
|
|
12
12
|
export async function whatsapp(config, args) {
|
|
13
13
|
const workspace = config.resolveWorkspaceRequired();
|
|
@@ -33,18 +33,20 @@ export async function whatsapp(config, args) {
|
|
|
33
33
|
console.log(`Disconnected WhatsApp from workspace ${workspace}. Vatio stops receiving its messages.`);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
case "numbers": return await numbers(client, workspace, args);
|
|
36
|
+
case "numbers": return await numbers(client, workspace, args, config.workspaceRoot);
|
|
37
37
|
default: fail(whatsappUsage());
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function whatsappUsage() {
|
|
42
42
|
return `Usage: vatio whatsapp status|connect|check|activate|deactivate|disconnect
|
|
43
|
-
vatio whatsapp numbers list|add PHONE|verify PHONE CODE|resend PHONE|remove PHONE
|
|
43
|
+
vatio whatsapp numbers list|add PHONE|point PHONE|verify PHONE CODE|resend PHONE|remove PHONE [--env NAME]
|
|
44
44
|
|
|
45
45
|
The bare commands drive your own WhatsApp number, which serves live.
|
|
46
|
-
\`numbers\` registers test phones on the shared WhatsApp preview
|
|
47
|
-
|
|
46
|
+
\`numbers\` registers test phones on the shared WhatsApp preview. Each reaches
|
|
47
|
+
the environment it points at: preview, or a branch's (\`add\` and \`point\`
|
|
48
|
+
take --env, and default to the branch you are on) — the counterpart of
|
|
49
|
+
\`vatio instagram accounts\`.
|
|
48
50
|
|
|
49
51
|
connect
|
|
50
52
|
Prints a link to finish in a browser. Connecting a number is Meta's
|
|
@@ -114,9 +116,10 @@ async function connectWhatsapp(client, workspace) {
|
|
|
114
116
|
openBrowser(url);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
async function numbers(client, workspace, args) {
|
|
119
|
+
async function numbers(client, workspace, args, cwd) {
|
|
118
120
|
const sub = args.shift() ?? "list";
|
|
119
|
-
const usage = "Usage: vatio whatsapp numbers list|add PHONE|verify PHONE CODE|resend PHONE|remove PHONE";
|
|
121
|
+
const usage = "Usage: vatio whatsapp numbers list|add PHONE|point PHONE|verify PHONE CODE|resend PHONE|remove PHONE [--env NAME]";
|
|
122
|
+
const environment = ["add", "point"].includes(sub) ? takeEnv(args, { fallback: null, cwd }) : null;
|
|
120
123
|
|
|
121
124
|
if (sub === "list") {
|
|
122
125
|
const list = asArray((await client.listTestPhoneNumbers()).test_phone_numbers);
|
|
@@ -128,15 +131,22 @@ async function numbers(client, workspace, args) {
|
|
|
128
131
|
console.log(`Test phone numbers on workspace ${workspace} (${list.length}):`);
|
|
129
132
|
for (const number of list) {
|
|
130
133
|
const suffix = number.status === "verified" ? `verified ${number.verified_at}` : "pending verification";
|
|
131
|
-
console.log(` ${number.phone_number} ${suffix}`);
|
|
134
|
+
console.log(` ${number.phone_number} → ${number.environment} ${suffix}`);
|
|
132
135
|
}
|
|
133
136
|
console.log("");
|
|
134
|
-
console.log("
|
|
137
|
+
console.log("Each reaches its environment on the shared WhatsApp preview number, never live.");
|
|
138
|
+
console.log("Point one elsewhere: vatio whatsapp numbers point PHONE --env NAME");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (sub === "point") {
|
|
142
|
+
const phoneNumber = requireArg(args, "vatio whatsapp numbers point +56912345678 [--env NAME]");
|
|
143
|
+
const number = await client.pointTestPhoneNumber({ phoneNumber, environment: environment ?? "preview" });
|
|
144
|
+
console.log(`${number.phone_number} now reaches ${number.environment} on workspace ${workspace}.`);
|
|
135
145
|
return;
|
|
136
146
|
}
|
|
137
147
|
if (sub === "add") {
|
|
138
148
|
const phoneNumber = requireArg(args, "vatio whatsapp numbers add +56912345678");
|
|
139
|
-
const number = await client.addTestPhoneNumber({ phoneNumber });
|
|
149
|
+
const number = await client.addTestPhoneNumber({ phoneNumber, environment });
|
|
140
150
|
if (number.already_verified) {
|
|
141
151
|
console.log(`${number.phone_number} is already verified on workspace ${workspace} — nothing to do.`);
|
|
142
152
|
} else if (number.moved_from) {
|
|
@@ -159,7 +169,7 @@ async function numbers(client, workspace, args) {
|
|
|
159
169
|
? `${number.phone_number} was already verified on workspace ${workspace}.`
|
|
160
170
|
: `Verified ${number.phone_number} on workspace ${workspace}.`
|
|
161
171
|
);
|
|
162
|
-
console.log(
|
|
172
|
+
console.log(`Anything you send from that phone now reaches ${number.environment}.`);
|
|
163
173
|
return;
|
|
164
174
|
}
|
|
165
175
|
if (sub === "resend") {
|
|
@@ -196,18 +206,20 @@ export async function instagram(config, args) {
|
|
|
196
206
|
console.log(`Disconnected Instagram from workspace ${workspace}. Vatio stops receiving its DMs.`);
|
|
197
207
|
return;
|
|
198
208
|
}
|
|
199
|
-
case "accounts": return await instagramAccounts(client, workspace, args);
|
|
209
|
+
case "accounts": return await instagramAccounts(client, workspace, args, config.workspaceRoot);
|
|
200
210
|
default: fail(instagramUsage());
|
|
201
211
|
}
|
|
202
212
|
}
|
|
203
213
|
|
|
204
214
|
function instagramUsage() {
|
|
205
215
|
return `Usage: vatio instagram status|connect|check|disconnect
|
|
206
|
-
vatio instagram accounts list|add @HANDLE|verify CODE|resend ID|remove ID
|
|
216
|
+
vatio instagram accounts list|add @HANDLE|point ID|verify CODE|resend ID|remove ID [--env NAME]
|
|
207
217
|
|
|
208
218
|
The bare commands drive your own Instagram account, which serves live.
|
|
209
|
-
\`accounts\` registers test accounts on the shared Instagram preview
|
|
210
|
-
|
|
219
|
+
\`accounts\` registers test accounts on the shared Instagram preview. Each
|
|
220
|
+
reaches the environment it points at: preview, or a branch's (\`add\` and
|
|
221
|
+
\`point\` take --env, and default to the branch you are on) — the counterpart
|
|
222
|
+
of \`vatio whatsapp numbers\`.`;
|
|
211
223
|
}
|
|
212
224
|
|
|
213
225
|
function printInstagram(account, workspace) {
|
|
@@ -254,9 +266,10 @@ async function connectInstagram(client) {
|
|
|
254
266
|
openBrowser(url);
|
|
255
267
|
}
|
|
256
268
|
|
|
257
|
-
async function instagramAccounts(client, workspace, args) {
|
|
269
|
+
async function instagramAccounts(client, workspace, args, cwd) {
|
|
258
270
|
const sub = args.shift() ?? "list";
|
|
259
|
-
const usage = "Usage: vatio instagram accounts list|add @HANDLE|verify CODE|resend ID|remove ID";
|
|
271
|
+
const usage = "Usage: vatio instagram accounts list|add @HANDLE|point ID|verify CODE|resend ID|remove ID [--env NAME]";
|
|
272
|
+
const environment = ["add", "point"].includes(sub) ? takeEnv(args, { fallback: null, cwd }) : null;
|
|
260
273
|
|
|
261
274
|
if (sub === "list") {
|
|
262
275
|
const list = asArray((await client.listTestInstagramAccounts()).test_instagram_accounts);
|
|
@@ -271,13 +284,19 @@ async function instagramAccounts(client, workspace, args) {
|
|
|
271
284
|
account.status === "verified" ? `verified ${account.verified_at}`
|
|
272
285
|
: account.status === "awaiting_code" ? "code sent — confirm it: vatio instagram accounts verify CODE"
|
|
273
286
|
: `declared — DM @${account.dm_to} from that account to get a code`;
|
|
274
|
-
console.log(` [${account.id}] @${account.username} ${suffix}`);
|
|
287
|
+
console.log(` [${account.id}] @${account.username} → ${account.environment} ${suffix}`);
|
|
275
288
|
}
|
|
276
289
|
return;
|
|
277
290
|
}
|
|
291
|
+
if (sub === "point") {
|
|
292
|
+
const id = requireArg(args, "vatio instagram accounts point ID [--env NAME]");
|
|
293
|
+
const account = await client.pointTestInstagramAccount({ id, environment: environment ?? "preview" });
|
|
294
|
+
console.log(`@${account.username} now reaches ${account.environment} on workspace ${workspace}.`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
278
297
|
if (sub === "add") {
|
|
279
298
|
const username = requireArg(args, "vatio instagram accounts add @yourhandle").replace(/^@/, "");
|
|
280
|
-
const account = await client.addTestInstagramAccount({ username });
|
|
299
|
+
const account = await client.addTestInstagramAccount({ username, environment });
|
|
281
300
|
console.log(`Declared @${account.username} on workspace ${workspace}.`);
|
|
282
301
|
console.log("");
|
|
283
302
|
// Meta will not let an app DM an account that has not written first, which
|
|
@@ -292,7 +311,7 @@ async function instagramAccounts(client, workspace, args) {
|
|
|
292
311
|
const code = requireArg(args, "vatio instagram accounts verify 123456");
|
|
293
312
|
const account = await client.verifyTestInstagramAccount({ code });
|
|
294
313
|
console.log(`Verified @${account.username} on workspace ${workspace}.`);
|
|
295
|
-
console.log(
|
|
314
|
+
console.log(`DMs from that account now reach ${account.environment}.`);
|
|
296
315
|
return;
|
|
297
316
|
}
|
|
298
317
|
if (sub === "resend") {
|
package/lib/commands/chat.mjs
CHANGED
|
@@ -28,7 +28,7 @@ export async function chat(config, args) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const environment = takeEnv(args, { fallback: "preview" });
|
|
31
|
+
const environment = takeEnv(args, { fallback: "preview", cwd: config.workspaceRoot });
|
|
32
32
|
const timeout = Number(takeValue(args, "--timeout") ?? 120);
|
|
33
33
|
const lastValue = takeValue(args, "--last");
|
|
34
34
|
const last = Number(lastValue ?? 10);
|
package/lib/commands/deploy.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { buildBundle, BundleError } from "../bundle.mjs";
|
|
|
18
18
|
import { ApiClient, CliClient } from "../api-client.mjs";
|
|
19
19
|
import { NotFoundError } from "../http.mjs";
|
|
20
20
|
import { deviceLogin } from "./auth.mjs";
|
|
21
|
+
import { branchEnvironment } from "../branch.mjs";
|
|
21
22
|
import { detectGitSha, fail, paint, takeEnv, takeFlag, takeValue } from "../support.mjs";
|
|
22
23
|
|
|
23
24
|
// The first command that makes something exist on a server, so the first one
|
|
@@ -99,8 +100,9 @@ export async function toolsCheck(config, args) {
|
|
|
99
100
|
export async function push(config, args) {
|
|
100
101
|
// Null, not "preview": the platform owns that default, and sending it
|
|
101
102
|
// explicitly would make a bare `vatio push` and `vatio push --env preview`
|
|
102
|
-
// two different requests that only happen to agree today.
|
|
103
|
-
|
|
103
|
+
// two different requests that only happen to agree today. On a branch, the
|
|
104
|
+
// branch's own environment.
|
|
105
|
+
const environment = takeEnv(args, { fallback: null, cwd: config.workspaceRoot });
|
|
104
106
|
const workspace = config.resolveWorkspaceRequired();
|
|
105
107
|
|
|
106
108
|
await ensureAuthorized(config);
|
|
@@ -123,15 +125,15 @@ export async function push(config, args) {
|
|
|
123
125
|
const payload = await client.pushPreview({
|
|
124
126
|
manifest: check.manifest,
|
|
125
127
|
gitSha: detectGitSha(config.workspaceRootRequired()),
|
|
128
|
+
gitBranch: branchEnvironment(config.workspaceRootRequired())?.branch ?? null,
|
|
126
129
|
createdBy: process.env.USER ?? null,
|
|
127
130
|
environment
|
|
128
131
|
});
|
|
129
132
|
|
|
130
133
|
const landedOn = payload.environment ?? "preview";
|
|
131
134
|
console.log(`Pushed deployment #${payload.deployment_id} to ${landedOn} (git_sha=${JSON.stringify(payload.git_sha ?? null)})`);
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
if (payload.share_url) console.log(`Open it: ${payload.share_url}`);
|
|
135
|
+
// Where to try it: the console's test screen for that environment.
|
|
136
|
+
if (payload.share_url) console.log(`Try it: ${payload.share_url}`);
|
|
135
137
|
for (const warning of asArray(payload.warnings)) console.log(` warning: ${warning}`);
|
|
136
138
|
if (payload.preview_url) console.log(`Preview: ${payload.preview_url}`);
|
|
137
139
|
|
|
@@ -154,10 +156,11 @@ function printNotices(payload) {
|
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
export async function publish(config, args) {
|
|
157
|
-
// Here `--env` names the
|
|
158
|
-
// destination of a publish is always live.
|
|
159
|
-
// the most recent push, whichever preview it
|
|
160
|
-
|
|
159
|
+
// Here `--env` names the environment being promoted, not a destination: the
|
|
160
|
+
// destination of a publish is always live. On a branch it is the branch's;
|
|
161
|
+
// otherwise the platform promotes the most recent push, whichever preview it
|
|
162
|
+
// landed on. Its knowledge changes go live with it.
|
|
163
|
+
const environment = takeEnv(args, { fallback: null, cwd: config.workspaceRoot });
|
|
161
164
|
const workspace = config.resolveWorkspaceRequired();
|
|
162
165
|
const payload = await deployClient(config, workspace).publish({
|
|
163
166
|
createdBy: process.env.USER ?? null,
|
|
@@ -166,6 +169,8 @@ export async function publish(config, args) {
|
|
|
166
169
|
const from = asArray(payload.published_from);
|
|
167
170
|
const source = from.length > 0 ? ` from ${from.join(", ")}` : "";
|
|
168
171
|
console.log(`Published live deployment #${payload.deployment_id}${source} (git_sha=${JSON.stringify(payload.git_sha ?? null)})`);
|
|
172
|
+
const knowledge = asArray(payload.knowledge);
|
|
173
|
+
if (knowledge.length > 0) console.log(`Knowledge now live: ${knowledge.join(", ")}`);
|
|
169
174
|
printNotices(payload);
|
|
170
175
|
}
|
|
171
176
|
|
|
@@ -193,7 +198,7 @@ export async function status(config) {
|
|
|
193
198
|
}
|
|
194
199
|
|
|
195
200
|
export async function diff(config, args) {
|
|
196
|
-
const environment = takeEnv(args, { fallback: "preview" });
|
|
201
|
+
const environment = takeEnv(args, { fallback: "preview", cwd: config.workspaceRoot });
|
|
197
202
|
const full = takeFlag(args, "--full");
|
|
198
203
|
const stat = takeFlag(args, "--stat");
|
|
199
204
|
const nameOnly = takeFlag(args, "--name-only");
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// `vatio env` — where you are working, and how to point things at it.
|
|
2
|
+
//
|
|
3
|
+
// On a branch other than the default one, every command works in the branch's
|
|
4
|
+
// own environment (see branch.mjs): its agent, its knowledge changes on top of
|
|
5
|
+
// live, its secret values. This is the command that says which one that is and
|
|
6
|
+
// hands over what an app needs to talk to it -- the publishable token and the
|
|
7
|
+
// widget snippet -- and the link to try it in the console.
|
|
8
|
+
|
|
9
|
+
import { deployClient } from "./deploy.mjs";
|
|
10
|
+
import { fail, takeEnv } from "../support.mjs";
|
|
11
|
+
|
|
12
|
+
export async function env(config, args) {
|
|
13
|
+
const workspace = config.resolveWorkspaceRequired();
|
|
14
|
+
const client = deployClient(config, workspace);
|
|
15
|
+
const sub = ["list", "show", "rm"].includes(args[0]) ? args.shift() : "show";
|
|
16
|
+
|
|
17
|
+
if (sub === "list") return await list(client, workspace);
|
|
18
|
+
|
|
19
|
+
const named = args.find((arg) => !arg.startsWith("-"));
|
|
20
|
+
if (named) args.splice(args.indexOf(named), 1);
|
|
21
|
+
const name = named ?? takeEnv(args, { fallback: "preview", cwd: config.workspaceRoot });
|
|
22
|
+
|
|
23
|
+
if (sub === "rm") return await remove(client, workspace, name);
|
|
24
|
+
return await show(client, workspace, name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function list(client, workspace) {
|
|
28
|
+
const rows = asArray((await client.environments()).data);
|
|
29
|
+
console.log(`Environments on workspace ${workspace} (${rows.length}):`);
|
|
30
|
+
for (const row of rows) {
|
|
31
|
+
const branch = row.git_branch ? ` branch=${row.git_branch}` : "";
|
|
32
|
+
const deployed = row.deployment_id ? ` deployment=#${row.deployment_id}` : " (nothing pushed)";
|
|
33
|
+
const knowledge = row.knowledge_changes_count ? ` knowledge_changes=${row.knowledge_changes_count}` : "";
|
|
34
|
+
const active = row.last_activity_at ? ` last_activity=${row.last_activity_at}` : "";
|
|
35
|
+
console.log(` ${row.name}${branch}${deployed}${knowledge}${active}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function show(client, workspace, name) {
|
|
40
|
+
let row;
|
|
41
|
+
try {
|
|
42
|
+
row = await client.environment(name);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error?.status !== 404) throw error;
|
|
45
|
+
console.log(`${name} on workspace ${workspace} does not exist yet — \`vatio push\` creates it.`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`${row.name} on workspace ${workspace}`);
|
|
50
|
+
if (row.git_branch) console.log(` branch: ${row.git_branch}`);
|
|
51
|
+
console.log(row.deployment_id
|
|
52
|
+
? ` deployment: #${row.deployment_id}${row.git_sha ? ` (${row.git_sha.slice(0, 7)})` : ""}`
|
|
53
|
+
: " deployment: none yet — `vatio push`");
|
|
54
|
+
if (row.share_url) console.log(` try it: ${row.share_url}`);
|
|
55
|
+
if (row.page_url) console.log(` page with the bubble: ${row.page_url}`);
|
|
56
|
+
if (row.knowledge_url) console.log(` knowledge diff: ${row.knowledge_url}`);
|
|
57
|
+
|
|
58
|
+
if (row.token) {
|
|
59
|
+
console.log(` publishable token: ${row.token}`);
|
|
60
|
+
console.log("\n widget:");
|
|
61
|
+
console.log(` <script src="${row.widget_script_url}"`);
|
|
62
|
+
console.log(` data-workspace="${workspace}"`);
|
|
63
|
+
console.log(` data-token="${row.token}"></script>`);
|
|
64
|
+
console.log("\n SDK: new VatioChat({ workspace, token }) with the token above talks to this environment.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const changes = asArray(row.knowledge_changes);
|
|
68
|
+
if (changes.length > 0) {
|
|
69
|
+
console.log(`\n knowledge changes live does not have (${changes.length}):`);
|
|
70
|
+
for (const change of changes) {
|
|
71
|
+
const conflict = change.conflict ? " CONFLICT: live changed it after you did" : "";
|
|
72
|
+
console.log(` ${change.change.padEnd(8)} ${change.base}/${change.name} v${change.version}${conflict}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const secrets = asArray(row.secrets);
|
|
77
|
+
if (secrets.length > 0) console.log(`\n secrets with their own value here: ${secrets.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function remove(client, workspace, name) {
|
|
81
|
+
if (!name || name === "live") fail("usage: vatio env rm NAME (live cannot be removed)");
|
|
82
|
+
|
|
83
|
+
const result = await client.deleteEnvironment(name);
|
|
84
|
+
console.log(`Removed ${name} from workspace ${workspace} (${result.agents ?? 0} agents, ${result.chats ?? 0} chats)`);
|
|
85
|
+
console.log("Its unpublished knowledge changes and secret values went with it.");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function asArray(value) {
|
|
89
|
+
return Array.isArray(value) ? value : [];
|
|
90
|
+
}
|