@vendian/cli 0.0.2 → 0.0.5
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/vendian.cjs +2 -0
- package/cli-wrapper.cjs +1143 -0
- package/package.json +10 -3
- package/bin/vendian.js +0 -8
- package/src/auth.js +0 -265
- package/src/config.js +0 -40
- package/src/constants.js +0 -15
- package/src/doctor.js +0 -22
- package/src/forward.js +0 -47
- package/src/install.js +0 -105
- package/src/main.js +0 -86
- package/src/paths.js +0 -43
- package/src/process.js +0 -50
- package/src/prompt.js +0 -38
- package/src/python.js +0 -77
- package/src/secret-store.js +0 -94
- package/src/setup.js +0 -127
- package/src/tui.js +0 -119
package/cli-wrapper.cjs
ADDED
|
@@ -0,0 +1,1143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/doctor.js
|
|
27
|
+
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
28
|
+
|
|
29
|
+
// src/config.js
|
|
30
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
31
|
+
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
32
|
+
|
|
33
|
+
// src/constants.js
|
|
34
|
+
var DEFAULT_GITLAB_HOST = "gitlab.com";
|
|
35
|
+
var DEFAULT_SDK_PUBLIC_PROJECT_ID = "77150592";
|
|
36
|
+
var DEFAULT_SDK_RUNTIME_PROJECT_ID = "79410876";
|
|
37
|
+
var PUBLIC_PACKAGE = "vendian-agents[full]";
|
|
38
|
+
var RUNTIME_PACKAGE = "vendian-agents-runtime";
|
|
39
|
+
var CONFIG_VERSION = 1;
|
|
40
|
+
var BACKEND_TARGETS = {
|
|
41
|
+
local: "http://localhost:3000",
|
|
42
|
+
localhost: "http://localhost:3000",
|
|
43
|
+
dev: "https://api.dev.vendian.ai",
|
|
44
|
+
staging: "https://api.staging.vendian.ai",
|
|
45
|
+
prod: "https://api.vendian.ai",
|
|
46
|
+
production: "https://api.vendian.ai"
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/paths.js
|
|
50
|
+
var import_node_os = __toESM(require("node:os"), 1);
|
|
51
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
52
|
+
function pathApi(platform) {
|
|
53
|
+
return platform === "win32" ? import_node_path.default.win32 : import_node_path.default.posix;
|
|
54
|
+
}
|
|
55
|
+
function vendianHome(env = process.env, platform = process.platform) {
|
|
56
|
+
if (env.VENDIAN_CLI_HOME) {
|
|
57
|
+
return pathApi(platform).resolve(env.VENDIAN_CLI_HOME);
|
|
58
|
+
}
|
|
59
|
+
if (platform === "win32") {
|
|
60
|
+
const root = env.LOCALAPPDATA || import_node_path.default.join(import_node_os.default.homedir(), "AppData", "Local");
|
|
61
|
+
return import_node_path.default.win32.join(root, "Vendian", "cli");
|
|
62
|
+
}
|
|
63
|
+
return import_node_path.default.posix.join(import_node_os.default.homedir(), ".vendian", "cli");
|
|
64
|
+
}
|
|
65
|
+
function configPath(env = process.env, platform = process.platform) {
|
|
66
|
+
if (env.VENDIAN_CLI_CONFIG) {
|
|
67
|
+
return pathApi(platform).resolve(env.VENDIAN_CLI_CONFIG);
|
|
68
|
+
}
|
|
69
|
+
return pathApi(platform).join(vendianHome(env, platform), "config.json");
|
|
70
|
+
}
|
|
71
|
+
function managedVenvPath(env = process.env, platform = process.platform) {
|
|
72
|
+
if (env.VENDIAN_CLI_VENV) {
|
|
73
|
+
return pathApi(platform).resolve(env.VENDIAN_CLI_VENV);
|
|
74
|
+
}
|
|
75
|
+
return pathApi(platform).join(vendianHome(env, platform), ".venv");
|
|
76
|
+
}
|
|
77
|
+
function venvPython(venvPath, platform = process.platform) {
|
|
78
|
+
return platform === "win32" ? import_node_path.default.win32.join(venvPath, "Scripts", "python.exe") : import_node_path.default.posix.join(venvPath, "bin", "python");
|
|
79
|
+
}
|
|
80
|
+
function venvVendian(venvPath, platform = process.platform) {
|
|
81
|
+
return platform === "win32" ? import_node_path.default.win32.join(venvPath, "Scripts", "vendian.exe") : import_node_path.default.posix.join(venvPath, "bin", "vendian");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/config.js
|
|
85
|
+
function loadConfig(env = process.env, platform = process.platform) {
|
|
86
|
+
const file = configPath(env, platform);
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
|
|
89
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error && error.code === "ENOENT") {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function saveConfig(config, env = process.env, platform = process.platform) {
|
|
98
|
+
const file = configPath(env, platform);
|
|
99
|
+
import_node_fs.default.mkdirSync(import_node_path2.default.dirname(file), { recursive: true });
|
|
100
|
+
const next = { version: CONFIG_VERSION, ...config };
|
|
101
|
+
import_node_fs.default.writeFileSync(file, `${JSON.stringify(next, null, 2)}
|
|
102
|
+
`, { encoding: "utf8", mode: 384 });
|
|
103
|
+
try {
|
|
104
|
+
import_node_fs.default.chmodSync(file, 384);
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
return file;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/install.js
|
|
111
|
+
var import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
112
|
+
var import_node_path3 = __toESM(require("node:path"), 1);
|
|
113
|
+
|
|
114
|
+
// src/process.js
|
|
115
|
+
var import_node_child_process = require("node:child_process");
|
|
116
|
+
function commandExists(command) {
|
|
117
|
+
const result = (0, import_node_child_process.spawnSync)(command, ["--version"], { stdio: "ignore", shell: false });
|
|
118
|
+
return result.status === 0;
|
|
119
|
+
}
|
|
120
|
+
function runCapture(command, args, options = {}) {
|
|
121
|
+
const result = (0, import_node_child_process.spawnSync)(command, args, {
|
|
122
|
+
encoding: "utf8",
|
|
123
|
+
shell: false,
|
|
124
|
+
...options
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
ok: result.status === 0,
|
|
128
|
+
status: result.status ?? 1,
|
|
129
|
+
stdout: result.stdout || "",
|
|
130
|
+
stderr: result.stderr || ""
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function runInherit(command, args, options = {}) {
|
|
134
|
+
const result = (0, import_node_child_process.spawnSync)(command, args, {
|
|
135
|
+
stdio: "inherit",
|
|
136
|
+
shell: false,
|
|
137
|
+
...options
|
|
138
|
+
});
|
|
139
|
+
if (result.error) {
|
|
140
|
+
throw result.error;
|
|
141
|
+
}
|
|
142
|
+
return result.status ?? 1;
|
|
143
|
+
}
|
|
144
|
+
function spawnForward(command, args, options = {}) {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
const child = (0, import_node_child_process.spawn)(command, args, {
|
|
147
|
+
stdio: "inherit",
|
|
148
|
+
shell: false,
|
|
149
|
+
...options
|
|
150
|
+
});
|
|
151
|
+
child.on("error", reject);
|
|
152
|
+
child.on("exit", (code, signal) => {
|
|
153
|
+
if (signal) {
|
|
154
|
+
reject(new Error(`command terminated by ${signal}`));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
resolve(code ?? 1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/python.js
|
|
163
|
+
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
164
|
+
function findPython(platform = process.platform) {
|
|
165
|
+
const candidates = platform === "win32" ? [
|
|
166
|
+
{ command: "py", args: ["-3.11"] },
|
|
167
|
+
{ command: "python", args: [] },
|
|
168
|
+
{ command: "python3", args: [] }
|
|
169
|
+
] : [
|
|
170
|
+
{ command: "python3.12", args: [] },
|
|
171
|
+
{ command: "python3.11", args: [] },
|
|
172
|
+
{ command: "python3", args: [] },
|
|
173
|
+
{ command: "python", args: [] }
|
|
174
|
+
];
|
|
175
|
+
for (const candidate of candidates) {
|
|
176
|
+
const version = runCapture(candidate.command, [
|
|
177
|
+
...candidate.args,
|
|
178
|
+
"-c",
|
|
179
|
+
'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")'
|
|
180
|
+
]);
|
|
181
|
+
if (!version.ok) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const match = version.stdout.trim().match(/^(\d+)\.(\d+)\./);
|
|
185
|
+
if (!match) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const major = Number(match[1]);
|
|
189
|
+
const minor = Number(match[2]);
|
|
190
|
+
if (major > 3 || major === 3 && minor >= 11) {
|
|
191
|
+
return candidate;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
function ensureVenv(venvPath, pythonCandidate, platform = process.platform) {
|
|
197
|
+
const pythonPath = venvPython(venvPath, platform);
|
|
198
|
+
if (import_node_fs2.default.existsSync(pythonPath)) {
|
|
199
|
+
return pythonPath;
|
|
200
|
+
}
|
|
201
|
+
import_node_fs2.default.mkdirSync(venvPath, { recursive: true });
|
|
202
|
+
const status = runInherit(pythonCandidate.command, [
|
|
203
|
+
...pythonCandidate.args,
|
|
204
|
+
"-m",
|
|
205
|
+
"venv",
|
|
206
|
+
venvPath
|
|
207
|
+
]);
|
|
208
|
+
if (status !== 0) {
|
|
209
|
+
throw new Error("Could not create the managed Vendian Python environment.");
|
|
210
|
+
}
|
|
211
|
+
return pythonPath;
|
|
212
|
+
}
|
|
213
|
+
function hasUv() {
|
|
214
|
+
return commandExists("uv");
|
|
215
|
+
}
|
|
216
|
+
function pythonVersion(pythonPath) {
|
|
217
|
+
const result = runCapture(pythonPath, [
|
|
218
|
+
"-c",
|
|
219
|
+
'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")'
|
|
220
|
+
]);
|
|
221
|
+
return result.ok ? result.stdout.trim() : null;
|
|
222
|
+
}
|
|
223
|
+
function verifyVendianImports(pythonPath) {
|
|
224
|
+
const result = runCapture(pythonPath, [
|
|
225
|
+
"-c",
|
|
226
|
+
'import vendian_agents, vendian_agents_runtime; print("ok")'
|
|
227
|
+
]);
|
|
228
|
+
return result.ok;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/secret-store.js
|
|
232
|
+
var import_node_child_process2 = require("node:child_process");
|
|
233
|
+
var SERVICE = "Vendian CLI";
|
|
234
|
+
var PACKAGE_TOKEN_ACCOUNT = "gitlab-package-token";
|
|
235
|
+
function run(command, args, options = {}) {
|
|
236
|
+
return (0, import_node_child_process2.spawnSync)(command, args, {
|
|
237
|
+
encoding: "utf8",
|
|
238
|
+
shell: false,
|
|
239
|
+
...options
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function savePackageTokenSecret(token, config = {}, platform = process.platform) {
|
|
243
|
+
if (!token) {
|
|
244
|
+
return { ok: false };
|
|
245
|
+
}
|
|
246
|
+
if (platform === "darwin") {
|
|
247
|
+
const result = run("security", [
|
|
248
|
+
"add-generic-password",
|
|
249
|
+
"-a",
|
|
250
|
+
PACKAGE_TOKEN_ACCOUNT,
|
|
251
|
+
"-s",
|
|
252
|
+
SERVICE,
|
|
253
|
+
"-w",
|
|
254
|
+
token,
|
|
255
|
+
"-U"
|
|
256
|
+
]);
|
|
257
|
+
return { ok: result.status === 0, provider: "macos-keychain" };
|
|
258
|
+
}
|
|
259
|
+
if (platform === "win32") {
|
|
260
|
+
const encrypted = run(
|
|
261
|
+
"powershell.exe",
|
|
262
|
+
[
|
|
263
|
+
"-NoProfile",
|
|
264
|
+
"-NonInteractive",
|
|
265
|
+
"-Command",
|
|
266
|
+
"$secure = ConvertTo-SecureString -String $env:VENDIAN_SECRET_VALUE -AsPlainText -Force; $secure | ConvertFrom-SecureString"
|
|
267
|
+
],
|
|
268
|
+
{ env: { ...process.env, VENDIAN_SECRET_VALUE: token } }
|
|
269
|
+
);
|
|
270
|
+
if (encrypted.status !== 0 || !encrypted.stdout.trim()) {
|
|
271
|
+
return { ok: false, provider: "windows-dpapi" };
|
|
272
|
+
}
|
|
273
|
+
const nextConfig = {
|
|
274
|
+
...config,
|
|
275
|
+
secretStore: {
|
|
276
|
+
...config.secretStore || {},
|
|
277
|
+
[PACKAGE_TOKEN_ACCOUNT]: {
|
|
278
|
+
provider: "windows-dpapi",
|
|
279
|
+
value: encrypted.stdout.trim()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
return { ok: true, provider: "windows-dpapi", config: nextConfig };
|
|
284
|
+
}
|
|
285
|
+
return { ok: false };
|
|
286
|
+
}
|
|
287
|
+
function loadPackageTokenSecret(config = {}, platform = process.platform) {
|
|
288
|
+
if (platform === "darwin") {
|
|
289
|
+
const result = run("security", [
|
|
290
|
+
"find-generic-password",
|
|
291
|
+
"-a",
|
|
292
|
+
PACKAGE_TOKEN_ACCOUNT,
|
|
293
|
+
"-s",
|
|
294
|
+
SERVICE,
|
|
295
|
+
"-w"
|
|
296
|
+
]);
|
|
297
|
+
return result.status === 0 ? result.stdout.trim() : "";
|
|
298
|
+
}
|
|
299
|
+
if (platform === "win32") {
|
|
300
|
+
const entry = config.secretStore?.[PACKAGE_TOKEN_ACCOUNT];
|
|
301
|
+
if (!entry || entry.provider !== "windows-dpapi" || !entry.value) {
|
|
302
|
+
return "";
|
|
303
|
+
}
|
|
304
|
+
const result = run(
|
|
305
|
+
"powershell.exe",
|
|
306
|
+
[
|
|
307
|
+
"-NoProfile",
|
|
308
|
+
"-NonInteractive",
|
|
309
|
+
"-Command",
|
|
310
|
+
"$secure = ConvertTo-SecureString -String $env:VENDIAN_SECRET_BLOB; $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure); try { [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }"
|
|
311
|
+
],
|
|
312
|
+
{ env: { ...process.env, VENDIAN_SECRET_BLOB: entry.value } }
|
|
313
|
+
);
|
|
314
|
+
return result.status === 0 ? result.stdout.trim() : "";
|
|
315
|
+
}
|
|
316
|
+
return "";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/install.js
|
|
320
|
+
function registryConfig(config = {}, env = process.env, platform = process.platform) {
|
|
321
|
+
const gitlabHost = env.GITLAB_HOST || config.gitlabHost || DEFAULT_GITLAB_HOST;
|
|
322
|
+
const username = env.GITLAB_USERNAME || config.gitlabUsername || "__token__";
|
|
323
|
+
const sdkProjectId = env.SDK_PUBLIC_PROJECT_ID || config.sdkPublicProjectId || DEFAULT_SDK_PUBLIC_PROJECT_ID;
|
|
324
|
+
const runtimeProjectId = env.SDK_RUNTIME_PROJECT_ID || config.sdkRuntimeProjectId || DEFAULT_SDK_RUNTIME_PROJECT_ID;
|
|
325
|
+
const secretToken = loadPackageTokenSecret(config, platform);
|
|
326
|
+
const token = env.GITLAB_TOKEN || config.gitlabToken || secretToken || "";
|
|
327
|
+
const tokenSource = env.GITLAB_TOKEN ? "env" : config.gitlabToken ? "config" : secretToken ? "secret-store" : "missing";
|
|
328
|
+
const vendianAgentsVersion = env.VENDIAN_AGENTS_VERSION || config.vendianAgentsVersion || "";
|
|
329
|
+
const vendianRuntimeVersion = env.VENDIAN_AGENTS_RUNTIME_VERSION || config.vendianAgentsRuntimeVersion || "";
|
|
330
|
+
return {
|
|
331
|
+
gitlabHost,
|
|
332
|
+
username,
|
|
333
|
+
sdkProjectId,
|
|
334
|
+
runtimeProjectId,
|
|
335
|
+
token,
|
|
336
|
+
tokenSource,
|
|
337
|
+
vendianAgentsVersion,
|
|
338
|
+
vendianRuntimeVersion
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function buildIndexUrls(registry) {
|
|
342
|
+
if (!registry.token) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const username = encodeURIComponent(registry.username || "__token__");
|
|
346
|
+
const token = encodeURIComponent(registry.token);
|
|
347
|
+
const sdkIndexUrl = `https://${username}:${token}@${registry.gitlabHost}/api/v4/projects/${registry.sdkProjectId}/packages/pypi/simple`;
|
|
348
|
+
const runtimeIndexUrl = `https://${username}:${token}@${registry.gitlabHost}/api/v4/projects/${registry.runtimeProjectId}/packages/pypi/simple`;
|
|
349
|
+
return { sdkIndexUrl, runtimeIndexUrl };
|
|
350
|
+
}
|
|
351
|
+
function packageSpecs(registry) {
|
|
352
|
+
const publicSpec = registry.vendianAgentsVersion ? `${PUBLIC_PACKAGE}==${registry.vendianAgentsVersion}` : PUBLIC_PACKAGE;
|
|
353
|
+
const runtimeSpec = registry.vendianRuntimeVersion ? `${RUNTIME_PACKAGE}==${registry.vendianRuntimeVersion}` : RUNTIME_PACKAGE;
|
|
354
|
+
return [publicSpec, runtimeSpec];
|
|
355
|
+
}
|
|
356
|
+
function installVendianPackages({ pythonPath, venvPath, config, env = process.env, platform = process.platform }) {
|
|
357
|
+
const registry = registryConfig(config, env, platform);
|
|
358
|
+
const indexes = buildIndexUrls(registry);
|
|
359
|
+
if (!indexes) {
|
|
360
|
+
throw new Error("Package access is missing. Run `vendian login`.");
|
|
361
|
+
}
|
|
362
|
+
const specs = packageSpecs(registry);
|
|
363
|
+
const installArgs = [
|
|
364
|
+
"install",
|
|
365
|
+
"--upgrade",
|
|
366
|
+
...specs,
|
|
367
|
+
"--index-url",
|
|
368
|
+
indexes.sdkIndexUrl,
|
|
369
|
+
"--extra-index-url",
|
|
370
|
+
indexes.runtimeIndexUrl,
|
|
371
|
+
"--extra-index-url",
|
|
372
|
+
"https://pypi.org/simple"
|
|
373
|
+
];
|
|
374
|
+
let status;
|
|
375
|
+
if (hasUv()) {
|
|
376
|
+
status = runInherit("uv", ["pip", ...installArgs, "--python", pythonPath]);
|
|
377
|
+
} else {
|
|
378
|
+
status = runInherit(pythonPath, ["-m", "pip", ...installArgs]);
|
|
379
|
+
}
|
|
380
|
+
if (status !== 0) {
|
|
381
|
+
throw new Error("Could not install Vendian runtime packages. Check package access and try again.");
|
|
382
|
+
}
|
|
383
|
+
writeInstallMarker(venvPath, {
|
|
384
|
+
gitlabHost: registry.gitlabHost,
|
|
385
|
+
gitlabUsername: registry.username,
|
|
386
|
+
sdkProjectId: registry.sdkProjectId,
|
|
387
|
+
runtimeProjectId: registry.runtimeProjectId,
|
|
388
|
+
vendianAgentsVersion: registry.vendianAgentsVersion || "latest",
|
|
389
|
+
vendianAgentsRuntimeVersion: registry.vendianRuntimeVersion || "latest"
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
function writeInstallMarker(venvPath, marker) {
|
|
393
|
+
import_node_fs3.default.mkdirSync(venvPath, { recursive: true });
|
|
394
|
+
import_node_fs3.default.writeFileSync(
|
|
395
|
+
import_node_path3.default.join(venvPath, ".vendian-bootstrap.json"),
|
|
396
|
+
`${JSON.stringify({ version: 1, ...marker }, null, 2)}
|
|
397
|
+
`,
|
|
398
|
+
"utf8"
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/doctor.js
|
|
403
|
+
function doctor({ env = process.env, platform = process.platform } = {}) {
|
|
404
|
+
const config = loadConfig(env, platform);
|
|
405
|
+
const venvPath = managedVenvPath(env, platform);
|
|
406
|
+
const pythonPath = venvPython(venvPath, platform);
|
|
407
|
+
const vendianPath = venvVendian(venvPath, platform);
|
|
408
|
+
const registry = registryConfig(config, env, platform);
|
|
409
|
+
console.log("Vendian doctor");
|
|
410
|
+
console.log(`Home: ${venvPath}`);
|
|
411
|
+
console.log(`System Python 3.11+: ${findPython(platform) ? "yes" : "no"}`);
|
|
412
|
+
console.log(`uv: ${hasUv() ? "yes" : "no, will use pip fallback"}`);
|
|
413
|
+
console.log(`Managed Python: ${import_node_fs4.default.existsSync(pythonPath) ? pythonVersion(pythonPath) || "present" : "missing"}`);
|
|
414
|
+
console.log(`Vendian executable: ${import_node_fs4.default.existsSync(vendianPath) ? vendianPath : "missing"}`);
|
|
415
|
+
console.log(`Vendian imports: ${import_node_fs4.default.existsSync(pythonPath) && verifyVendianImports(pythonPath) ? "ok" : "missing"}`);
|
|
416
|
+
console.log(`Package access: ${registry.token ? `configured (${registry.tokenSource})` : "missing"}`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/forward.js
|
|
420
|
+
var import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
421
|
+
var AUTO_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
422
|
+
async function forwardToPythonVendian(args, { env = process.env, platform = process.platform } = {}) {
|
|
423
|
+
const venvPath = managedVenvPath(env, platform);
|
|
424
|
+
const vendianPath = venvVendian(venvPath, platform);
|
|
425
|
+
if (!import_node_fs5.default.existsSync(vendianPath)) {
|
|
426
|
+
throw new Error("Vendian is not set up yet. Run `vendian login` first.");
|
|
427
|
+
}
|
|
428
|
+
maybeAutoUpdateManagedEnv({ env, platform, venvPath });
|
|
429
|
+
const code = await spawnForward(vendianPath, args, { env });
|
|
430
|
+
process.exitCode = code;
|
|
431
|
+
}
|
|
432
|
+
function maybeAutoUpdateManagedEnv({ env = process.env, platform = process.platform, venvPath = managedVenvPath(env, platform) } = {}) {
|
|
433
|
+
if (env.VENDIAN_SKIP_AUTO_UPDATE === "1") {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const config = loadConfig(env, platform);
|
|
437
|
+
const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
|
|
438
|
+
if (Number.isFinite(lastUpdate) && Date.now() - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
const registry = registryConfig(config, env);
|
|
442
|
+
if (!registry.token) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
const pythonPath = venvPython(venvPath, platform);
|
|
446
|
+
if (!import_node_fs5.default.existsSync(pythonPath)) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
console.error("[vendian] Checking managed CLI/runtime updates...");
|
|
451
|
+
installVendianPackages({ pythonPath, venvPath, config, env });
|
|
452
|
+
saveConfig({ ...config, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() }, env, platform);
|
|
453
|
+
return true;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
const message = error && typeof error.message === "string" ? error.message : String(error);
|
|
456
|
+
console.error(`[vendian] Update check failed; continuing with installed CLI. ${message}`);
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/setup.js
|
|
462
|
+
var import_node_fs7 = __toESM(require("node:fs"), 1);
|
|
463
|
+
|
|
464
|
+
// src/auth.js
|
|
465
|
+
var import_node_crypto = __toESM(require("node:crypto"), 1);
|
|
466
|
+
var import_node_http = __toESM(require("node:http"), 1);
|
|
467
|
+
var import_node_fs6 = __toESM(require("node:fs"), 1);
|
|
468
|
+
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
469
|
+
var import_node_child_process3 = require("node:child_process");
|
|
470
|
+
var DEFAULT_OAUTH_REDIRECT_PORT = 8765;
|
|
471
|
+
function resolveApiUrl({ backend, apiUrl, env = process.env } = {}) {
|
|
472
|
+
if (apiUrl) {
|
|
473
|
+
return apiUrl.replace(/\/$/, "");
|
|
474
|
+
}
|
|
475
|
+
if (env.VENDIAN_API_URL) {
|
|
476
|
+
return env.VENDIAN_API_URL.replace(/\/$/, "");
|
|
477
|
+
}
|
|
478
|
+
const target = backend || "prod";
|
|
479
|
+
const resolved = BACKEND_TARGETS[target];
|
|
480
|
+
if (!resolved) {
|
|
481
|
+
throw new Error(`Unknown Vendian backend '${target}'. Use local, dev, staging, prod, or --api-url.`);
|
|
482
|
+
}
|
|
483
|
+
return resolved;
|
|
484
|
+
}
|
|
485
|
+
async function loginWithVendianOAuth({ backend, apiUrl, noBrowser = false, env = process.env } = {}) {
|
|
486
|
+
const resolvedApiUrl = resolveApiUrl({ backend, apiUrl, env });
|
|
487
|
+
const callback = await createCallbackServer(env);
|
|
488
|
+
try {
|
|
489
|
+
const config = await getOAuthConfig(resolvedApiUrl, callback.redirectUri);
|
|
490
|
+
const codeVerifier = import_node_crypto.default.randomBytes(48).toString("base64url");
|
|
491
|
+
const codeChallenge = import_node_crypto.default.createHash("sha256").update(codeVerifier).digest("base64url");
|
|
492
|
+
const state = import_node_crypto.default.randomBytes(18).toString("base64url");
|
|
493
|
+
const authorizationUrl = authorizationUrlFor(config, { state, codeChallenge });
|
|
494
|
+
if (noBrowser) {
|
|
495
|
+
console.error(`Open this URL to authenticate the CLI: ${authorizationUrl}`);
|
|
496
|
+
} else {
|
|
497
|
+
openBrowser(authorizationUrl);
|
|
498
|
+
}
|
|
499
|
+
const callbackResult = await callback.wait();
|
|
500
|
+
if (callbackResult.state !== state) {
|
|
501
|
+
throw new Error("OAuth state mismatch");
|
|
502
|
+
}
|
|
503
|
+
const clerkToken = await exchangeCodeForClerkToken(config, callbackResult.code, codeVerifier, callback.redirectUri);
|
|
504
|
+
const exchange = await postJson(`${resolvedApiUrl}/api/v1/cli/auth/oauth/exchange`, {
|
|
505
|
+
clerkToken
|
|
506
|
+
});
|
|
507
|
+
return { apiUrl: resolvedApiUrl, ...exchange };
|
|
508
|
+
} finally {
|
|
509
|
+
await callback.close();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function loadCloudConfig(env = process.env, platform = process.platform) {
|
|
513
|
+
const file = cloudConfigPath(env, platform);
|
|
514
|
+
try {
|
|
515
|
+
const parsed = JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
|
|
516
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
517
|
+
} catch {
|
|
518
|
+
return {};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function cloudAuthStatus({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
|
|
522
|
+
const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
|
|
523
|
+
const config = loadCloudConfig(env, platform);
|
|
524
|
+
const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
|
|
525
|
+
const profile = profiles[targetApiUrl];
|
|
526
|
+
return {
|
|
527
|
+
apiUrl: targetApiUrl,
|
|
528
|
+
activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
|
|
529
|
+
profile: profile && typeof profile === "object" ? profile : void 0,
|
|
530
|
+
authenticated: Boolean(profile?.access_token),
|
|
531
|
+
email: typeof profile?.email === "string" ? profile.email : void 0,
|
|
532
|
+
expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
|
|
533
|
+
profiles
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
async function getOAuthConfig(apiUrl, redirectUri) {
|
|
537
|
+
const url = new URL(`${apiUrl}/api/v1/cli/auth/oauth/config`);
|
|
538
|
+
url.searchParams.set("redirectUri", redirectUri);
|
|
539
|
+
return getJson(url);
|
|
540
|
+
}
|
|
541
|
+
function authorizationUrlFor(config, { state, codeChallenge }) {
|
|
542
|
+
const url = new URL(String(config.authorizationUrl));
|
|
543
|
+
url.searchParams.set("response_type", "code");
|
|
544
|
+
url.searchParams.set("client_id", String(config.clientId));
|
|
545
|
+
url.searchParams.set("redirect_uri", String(config.redirectUri));
|
|
546
|
+
url.searchParams.set("scope", String(config.scopes || "email profile"));
|
|
547
|
+
url.searchParams.set("state", state);
|
|
548
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
549
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
550
|
+
return url.toString();
|
|
551
|
+
}
|
|
552
|
+
async function exchangeCodeForClerkToken(config, code, codeVerifier, redirectUri) {
|
|
553
|
+
const body = new URLSearchParams({
|
|
554
|
+
grant_type: "authorization_code",
|
|
555
|
+
client_id: String(config.clientId),
|
|
556
|
+
code,
|
|
557
|
+
redirect_uri: redirectUri,
|
|
558
|
+
code_verifier: codeVerifier
|
|
559
|
+
});
|
|
560
|
+
const payload = await postForm(String(config.tokenUrl), body);
|
|
561
|
+
const token = payload.access_token || payload.id_token;
|
|
562
|
+
if (!token) {
|
|
563
|
+
throw new Error("OAuth token exchange did not return a Clerk token");
|
|
564
|
+
}
|
|
565
|
+
return String(token);
|
|
566
|
+
}
|
|
567
|
+
async function getJson(url) {
|
|
568
|
+
const response = await fetch(url, { headers: { Accept: "application/json" } });
|
|
569
|
+
return decodeResponse(response);
|
|
570
|
+
}
|
|
571
|
+
async function postJson(url, body) {
|
|
572
|
+
const response = await fetch(url, {
|
|
573
|
+
method: "POST",
|
|
574
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
575
|
+
body: JSON.stringify(body)
|
|
576
|
+
});
|
|
577
|
+
return decodeResponse(response);
|
|
578
|
+
}
|
|
579
|
+
async function postForm(url, body) {
|
|
580
|
+
const response = await fetch(url, {
|
|
581
|
+
method: "POST",
|
|
582
|
+
headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
|
|
583
|
+
body
|
|
584
|
+
});
|
|
585
|
+
return decodeResponse(response);
|
|
586
|
+
}
|
|
587
|
+
async function decodeResponse(response) {
|
|
588
|
+
const text = await response.text();
|
|
589
|
+
let payload = {};
|
|
590
|
+
if (text) {
|
|
591
|
+
try {
|
|
592
|
+
payload = JSON.parse(text);
|
|
593
|
+
} catch {
|
|
594
|
+
payload = { error: text };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (!response.ok) {
|
|
598
|
+
throw new Error(payload.message || payload.error || `HTTP ${response.status}`);
|
|
599
|
+
}
|
|
600
|
+
return payload;
|
|
601
|
+
}
|
|
602
|
+
async function createCallbackServer(env) {
|
|
603
|
+
const port = Number(env.VENDIAN_CLI_OAUTH_REDIRECT_PORT || DEFAULT_OAUTH_REDIRECT_PORT);
|
|
604
|
+
let server;
|
|
605
|
+
let settled = false;
|
|
606
|
+
let timeout;
|
|
607
|
+
const waitPromise = new Promise((resolve, reject) => {
|
|
608
|
+
timeout = setTimeout(() => {
|
|
609
|
+
settled = true;
|
|
610
|
+
reject(new Error("OAuth login timed out before callback completed"));
|
|
611
|
+
}, 3e5);
|
|
612
|
+
server = import_node_http.default.createServer((req, res) => {
|
|
613
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
|
|
614
|
+
const code = url.searchParams.get("code");
|
|
615
|
+
const state = url.searchParams.get("state");
|
|
616
|
+
const error = url.searchParams.get("error");
|
|
617
|
+
const body = code ? "Vendian CLI authentication complete. You can close this window." : "Vendian CLI authentication failed. Return to the terminal.";
|
|
618
|
+
res.writeHead(code ? 200 : 400, {
|
|
619
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
620
|
+
"Content-Length": Buffer.byteLength(body)
|
|
621
|
+
});
|
|
622
|
+
res.end(body);
|
|
623
|
+
clearTimeout(timeout);
|
|
624
|
+
if (settled) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
settled = true;
|
|
628
|
+
if (error) {
|
|
629
|
+
reject(new Error(`OAuth login failed: ${error}`));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (!code) {
|
|
633
|
+
reject(new Error("OAuth callback did not include an authorization code"));
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
resolve({ code, state });
|
|
637
|
+
});
|
|
638
|
+
server.on("error", reject);
|
|
639
|
+
server.listen(port, "127.0.0.1");
|
|
640
|
+
});
|
|
641
|
+
await new Promise((resolve, reject) => {
|
|
642
|
+
server.once("listening", resolve);
|
|
643
|
+
server.once("error", reject);
|
|
644
|
+
});
|
|
645
|
+
return {
|
|
646
|
+
redirectUri: `http://127.0.0.1:${server.address().port}/callback`,
|
|
647
|
+
wait: () => waitPromise,
|
|
648
|
+
close: () => new Promise((resolve) => server.close(resolve))
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function openBrowser(url) {
|
|
652
|
+
const platform = process.platform;
|
|
653
|
+
if (platform === "win32") {
|
|
654
|
+
(0, import_node_child_process3.spawnSync)(...buildWindowsOpenCommand(url), { stdio: "ignore", shell: false });
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (platform === "darwin") {
|
|
658
|
+
(0, import_node_child_process3.spawnSync)("open", [url], { stdio: "ignore", shell: false });
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
(0, import_node_child_process3.spawnSync)("xdg-open", [url], { stdio: "ignore", shell: false });
|
|
662
|
+
}
|
|
663
|
+
function buildWindowsOpenCommand(url) {
|
|
664
|
+
return ["rundll32.exe", ["url.dll,FileProtocolHandler", String(url)]];
|
|
665
|
+
}
|
|
666
|
+
function cloudConfigPath(env = process.env, platform = process.platform) {
|
|
667
|
+
if (env.VENDIAN_CLOUD_CONFIG) {
|
|
668
|
+
return import_node_path4.default.resolve(env.VENDIAN_CLOUD_CONFIG);
|
|
669
|
+
}
|
|
670
|
+
if (platform === "win32") {
|
|
671
|
+
const root2 = env.APPDATA || import_node_path4.default.join(process.env.USERPROFILE || "", "AppData", "Roaming");
|
|
672
|
+
return import_node_path4.default.win32.join(root2, "Vendian", "cloud-auth.json");
|
|
673
|
+
}
|
|
674
|
+
const root = env.XDG_CONFIG_HOME || import_node_path4.default.join(process.env.HOME || "", ".config");
|
|
675
|
+
return import_node_path4.default.posix.join(root, "vendian", "cloud-auth.json");
|
|
676
|
+
}
|
|
677
|
+
function saveCloudToken(token, { env = process.env, platform = process.platform } = {}) {
|
|
678
|
+
const file = cloudConfigPath(env, platform);
|
|
679
|
+
let raw = { version: 2, profiles: {}, active_api_url: token.apiUrl };
|
|
680
|
+
try {
|
|
681
|
+
const existing = JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
|
|
682
|
+
if (existing && typeof existing === "object" && existing.profiles && typeof existing.profiles === "object") {
|
|
683
|
+
raw.profiles = existing.profiles;
|
|
684
|
+
}
|
|
685
|
+
} catch {
|
|
686
|
+
}
|
|
687
|
+
raw.active_api_url = token.apiUrl;
|
|
688
|
+
raw.profiles[token.apiUrl] = {
|
|
689
|
+
api_url: token.apiUrl,
|
|
690
|
+
access_token: token.accessToken,
|
|
691
|
+
user_id: token.userId,
|
|
692
|
+
email: token.email,
|
|
693
|
+
expires_at: token.expiresAt,
|
|
694
|
+
scopes: token.scopes,
|
|
695
|
+
tooling_eligible: token.toolingEligible
|
|
696
|
+
};
|
|
697
|
+
import_node_fs6.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
|
|
698
|
+
import_node_fs6.default.writeFileSync(file, `${JSON.stringify(raw, null, 2)}
|
|
699
|
+
`, { encoding: "utf8", mode: 384 });
|
|
700
|
+
try {
|
|
701
|
+
import_node_fs6.default.chmodSync(file, 384);
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
return file;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/setup.js
|
|
708
|
+
async function setup({
|
|
709
|
+
nonInteractive = false,
|
|
710
|
+
backend = void 0,
|
|
711
|
+
apiUrl = void 0,
|
|
712
|
+
noBrowser = false,
|
|
713
|
+
forceAuth = false,
|
|
714
|
+
env = process.env,
|
|
715
|
+
platform = process.platform
|
|
716
|
+
} = {}) {
|
|
717
|
+
const existing = loadConfig(env, platform);
|
|
718
|
+
const registry = registryConfig(existing, env, platform);
|
|
719
|
+
const auth = cloudAuthStatus({ backend, apiUrl, env, platform });
|
|
720
|
+
const next = {
|
|
721
|
+
...existing,
|
|
722
|
+
gitlabHost: registry.gitlabHost || DEFAULT_GITLAB_HOST,
|
|
723
|
+
gitlabUsername: registry.username || "__token__",
|
|
724
|
+
sdkPublicProjectId: registry.sdkProjectId || DEFAULT_SDK_PUBLIC_PROJECT_ID,
|
|
725
|
+
sdkRuntimeProjectId: registry.runtimeProjectId || DEFAULT_SDK_RUNTIME_PROJECT_ID
|
|
726
|
+
};
|
|
727
|
+
let cloudAuthApiUrl = auth.authenticated ? auth.apiUrl : void 0;
|
|
728
|
+
console.log("Vendian login");
|
|
729
|
+
console.log("This signs in to Vendian and prepares the local runtime.");
|
|
730
|
+
console.log("Agent requirements.txt files stay reserved for agent-owned dependencies.");
|
|
731
|
+
console.log("");
|
|
732
|
+
if ((!auth.authenticated || forceAuth) && !nonInteractive) {
|
|
733
|
+
console.log(`Opening Vendian sign-in for ${auth.apiUrl}...`);
|
|
734
|
+
const login = await loginWithVendianOAuth({ backend, apiUrl, noBrowser, env });
|
|
735
|
+
saveCloudToken(login, { env, platform });
|
|
736
|
+
cloudAuthApiUrl = login.apiUrl;
|
|
737
|
+
const packageCredentials = login.packageCredentials;
|
|
738
|
+
if (!registry.token && !packageCredentials?.token) {
|
|
739
|
+
throw new Error("Vendian login succeeded, but the backend did not return package credentials. Configure CLI_PACKAGE_REGISTRY_TOKEN on the backend.");
|
|
740
|
+
}
|
|
741
|
+
if (packageCredentials) {
|
|
742
|
+
next.gitlabHost = packageCredentials.gitlabHost || next.gitlabHost;
|
|
743
|
+
next.gitlabUsername = packageCredentials.username || next.gitlabUsername;
|
|
744
|
+
next.sdkPublicProjectId = packageCredentials.sdkProjectId || next.sdkPublicProjectId;
|
|
745
|
+
next.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || next.sdkRuntimeProjectId;
|
|
746
|
+
next.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || next.vendianAgentsVersion;
|
|
747
|
+
next.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || next.vendianAgentsRuntimeVersion;
|
|
748
|
+
const secret = savePackageTokenSecret(packageCredentials.token, next, platform);
|
|
749
|
+
if (secret.ok) {
|
|
750
|
+
Object.assign(next, secret.config || {});
|
|
751
|
+
delete next.gitlabToken;
|
|
752
|
+
console.log("Package access saved.");
|
|
753
|
+
} else {
|
|
754
|
+
next.gitlabToken = packageCredentials.token;
|
|
755
|
+
console.log("Package access saved in the local Vendian CLI config file.");
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
} else if (auth.authenticated) {
|
|
759
|
+
console.log(`Cloud authentication already saved for ${auth.apiUrl}${auth.email ? ` (${auth.email})` : ""}.`);
|
|
760
|
+
} else if (registry.token) {
|
|
761
|
+
if (registry.tokenSource !== "secret-store") {
|
|
762
|
+
next.gitlabToken = registry.token;
|
|
763
|
+
}
|
|
764
|
+
console.log("Using saved package access.");
|
|
765
|
+
}
|
|
766
|
+
const installRegistry = registryConfig(next, env, platform);
|
|
767
|
+
if (!installRegistry.token) {
|
|
768
|
+
throw new Error("Package access is missing. Run interactive `vendian login` to sign in.");
|
|
769
|
+
}
|
|
770
|
+
const python = findPython(platform);
|
|
771
|
+
if (!python) {
|
|
772
|
+
throw new Error("Python 3.11+ was not found. Install Python 3.11 or newer, then rerun `vendian login`.");
|
|
773
|
+
}
|
|
774
|
+
const venvPath = managedVenvPath(env, platform);
|
|
775
|
+
console.log(`Managed environment: ${venvPath}`);
|
|
776
|
+
const pythonPath = ensureVenv(venvPath, python, platform);
|
|
777
|
+
console.log(`Python: ${pythonVersion(pythonPath) || pythonPath}`);
|
|
778
|
+
saveConfig(next, env, platform);
|
|
779
|
+
installVendianPackages({ pythonPath, venvPath, config: next, env, platform });
|
|
780
|
+
saveConfig({ ...next, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() }, env, platform);
|
|
781
|
+
if (!verifyVendianImports(pythonPath)) {
|
|
782
|
+
throw new Error("Vendian packages installed, but import verification failed.");
|
|
783
|
+
}
|
|
784
|
+
const vendianPath = venvVendian(venvPath, platform);
|
|
785
|
+
if (!import_node_fs7.default.existsSync(vendianPath)) {
|
|
786
|
+
throw new Error("Vendian executable was not found after install.");
|
|
787
|
+
}
|
|
788
|
+
console.log("");
|
|
789
|
+
for (const line of setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl })) {
|
|
790
|
+
console.log(line);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl } = {}) {
|
|
794
|
+
const lines = ["Vendian login complete."];
|
|
795
|
+
if (cloudAuthApiUrl) {
|
|
796
|
+
lines.push(`Connected to ${cloudAuthApiUrl}.`);
|
|
797
|
+
lines.push("Next: vendian cloud local serve --agents-dir ./agents");
|
|
798
|
+
return lines;
|
|
799
|
+
}
|
|
800
|
+
lines.push(`Next: ${cloudAuthLoginCommand({ backend, apiUrl })}`);
|
|
801
|
+
lines.push("Then: vendian cloud local serve --agents-dir ./agents");
|
|
802
|
+
return lines;
|
|
803
|
+
}
|
|
804
|
+
function cloudAuthLoginCommand({ backend, apiUrl } = {}) {
|
|
805
|
+
if (apiUrl) {
|
|
806
|
+
return `vendian cloud auth login --api-url ${apiUrl}`;
|
|
807
|
+
}
|
|
808
|
+
if (backend) {
|
|
809
|
+
return `vendian cloud auth login --backend ${backend}`;
|
|
810
|
+
}
|
|
811
|
+
return "vendian cloud auth login";
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// src/tui.js
|
|
815
|
+
var import_node_fs8 = __toESM(require("node:fs"), 1);
|
|
816
|
+
var import_node_readline = __toESM(require("node:readline"), 1);
|
|
817
|
+
var import_promises = __toESM(require("node:readline/promises"), 1);
|
|
818
|
+
|
|
819
|
+
// src/version.js
|
|
820
|
+
var CLI_VERSION = true ? "0.0.5" : process.env.npm_package_version || "0.0.0-dev";
|
|
821
|
+
|
|
822
|
+
// src/tui.js
|
|
823
|
+
var RESET = "\x1B[0m";
|
|
824
|
+
var BOLD = "\x1B[1m";
|
|
825
|
+
var DIM = "\x1B[2m";
|
|
826
|
+
var CYAN = "\x1B[36m";
|
|
827
|
+
var GREEN = "\x1B[32m";
|
|
828
|
+
var YELLOW = "\x1B[33m";
|
|
829
|
+
var RED = "\x1B[31m";
|
|
830
|
+
var INVERT = "\x1B[7m";
|
|
831
|
+
var CLEAR = "\x1B[2J\x1B[H";
|
|
832
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
833
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
834
|
+
var ENDPOINTS = [
|
|
835
|
+
{ key: "local", label: "Local" },
|
|
836
|
+
{ key: "dev", label: "Dev" },
|
|
837
|
+
{ key: "staging", label: "Staging" },
|
|
838
|
+
{ key: "prod", label: "Production" }
|
|
839
|
+
];
|
|
840
|
+
var ACTIONS = [
|
|
841
|
+
{ id: "connect", label: "Connect / switch endpoint", detail: "Sign in to local, dev, staging, production, or a custom API" },
|
|
842
|
+
{ id: "serve", label: "Start local agent server", detail: "Run agents from ./agents through the managed runtime" },
|
|
843
|
+
{ id: "doctor", label: "Run doctor", detail: "Check Python, runtime, package access, and local paths" },
|
|
844
|
+
{ id: "update", label: "Update managed runtime", detail: "Refresh the Vendian runtime packages" },
|
|
845
|
+
{ id: "help", label: "Show commands", detail: "Print common command-line equivalents" },
|
|
846
|
+
{ id: "exit", label: "Exit", detail: "Close this session" }
|
|
847
|
+
];
|
|
848
|
+
async function runTui({ env = process.env, platform = process.platform, input = process.stdin, output = process.stdout } = {}) {
|
|
849
|
+
let selected = 0;
|
|
850
|
+
const isRawCapable = Boolean(input.isTTY && output.isTTY && input.setRawMode);
|
|
851
|
+
if (!isRawCapable) {
|
|
852
|
+
output.write(`${renderHome({ env, platform, selected: 0, color: false })}
|
|
853
|
+
`);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
import_node_readline.default.emitKeypressEvents(input);
|
|
857
|
+
input.setRawMode(true);
|
|
858
|
+
output.write(HIDE_CURSOR);
|
|
859
|
+
try {
|
|
860
|
+
while (true) {
|
|
861
|
+
output.write(renderHome({ env, platform, selected }));
|
|
862
|
+
const key = await readKey(input);
|
|
863
|
+
if (key.name === "up") {
|
|
864
|
+
selected = (selected - 1 + ACTIONS.length) % ACTIONS.length;
|
|
865
|
+
} else if (key.name === "down") {
|
|
866
|
+
selected = (selected + 1) % ACTIONS.length;
|
|
867
|
+
} else if (key.name === "return") {
|
|
868
|
+
output.write(SHOW_CURSOR);
|
|
869
|
+
input.setRawMode(false);
|
|
870
|
+
output.write("\n");
|
|
871
|
+
const shouldExit = await runAction(ACTIONS[selected].id, { env, platform, input, output });
|
|
872
|
+
if (shouldExit) return;
|
|
873
|
+
input.setRawMode(true);
|
|
874
|
+
output.write(HIDE_CURSOR);
|
|
875
|
+
} else if (key.name === "q" || key.ctrl && key.name === "c") {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} finally {
|
|
880
|
+
output.write(SHOW_CURSOR);
|
|
881
|
+
input.setRawMode(false);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function renderHome({ env = process.env, platform = process.platform, selected = 0, color = true } = {}) {
|
|
885
|
+
const c = color ? palette : plainPalette;
|
|
886
|
+
const rows = endpointRows({ env, platform });
|
|
887
|
+
const runtime = runtimeSummary({ env, platform });
|
|
888
|
+
const active = rows.find((row) => row.active);
|
|
889
|
+
const width = 78;
|
|
890
|
+
return [
|
|
891
|
+
CLEAR,
|
|
892
|
+
c.dim("Vendian Cloud"),
|
|
893
|
+
`${c.brand("VENDIAN")} ${c.dim(`CLI ${CLI_VERSION}`)}`,
|
|
894
|
+
c.rule(width),
|
|
895
|
+
`${c.label("Connected")} ${active ? `${c.good(active.label)} ${c.dim(active.detail)}` : c.warn("No active endpoint")}`,
|
|
896
|
+
`${c.label("Runtime")} ${runtime.installed ? c.good("ready") : c.bad("not installed")} ${c.dim(runtime.detail)}`,
|
|
897
|
+
runtime.updateHint ? `${c.label("Update")} ${c.warn(runtime.updateHint)}` : `${c.label("Update")} ${c.good("up to date")}`,
|
|
898
|
+
"",
|
|
899
|
+
c.section("Connections:"),
|
|
900
|
+
...rows.map((row) => renderEndpointRow(row, c)),
|
|
901
|
+
"",
|
|
902
|
+
c.section("Actions:"),
|
|
903
|
+
...ACTIONS.map((action, index) => renderAction(action, index === selected, c)),
|
|
904
|
+
"",
|
|
905
|
+
c.dim("Use Up/Down to move, Enter to select, q to exit.")
|
|
906
|
+
].join("\n");
|
|
907
|
+
}
|
|
908
|
+
function endpointRows({ env = process.env, platform = process.platform } = {}) {
|
|
909
|
+
return ENDPOINTS.map((endpoint) => {
|
|
910
|
+
const status = cloudAuthStatus({ backend: endpoint.key, env, platform });
|
|
911
|
+
const active = status.activeApiUrl === status.apiUrl;
|
|
912
|
+
return {
|
|
913
|
+
key: endpoint.key,
|
|
914
|
+
label: endpoint.label,
|
|
915
|
+
apiUrl: status.apiUrl,
|
|
916
|
+
active,
|
|
917
|
+
status: status.authenticated ? active ? "connected" : "signed in" : "not signed in",
|
|
918
|
+
detail: status.authenticated ? status.email || status.apiUrl : status.apiUrl
|
|
919
|
+
};
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
function runtimeSummary({ env = process.env, platform = process.platform, now = Date.now() } = {}) {
|
|
923
|
+
const config = loadConfig(env, platform);
|
|
924
|
+
const venvPath = managedVenvPath(env, platform);
|
|
925
|
+
const vendianPath = venvVendian(venvPath, platform);
|
|
926
|
+
const installed = import_node_fs8.default.existsSync(vendianPath);
|
|
927
|
+
const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
|
|
928
|
+
const stale = !Number.isFinite(lastUpdate) || now - lastUpdate > 24 * 60 * 60 * 1e3;
|
|
929
|
+
return {
|
|
930
|
+
installed,
|
|
931
|
+
detail: installed ? venvPath : "Run vendian login to prepare it",
|
|
932
|
+
updateHint: installed && stale ? "Run vendian update to refresh packages" : ""
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
async function runAction(action, { env, platform, input, output }) {
|
|
936
|
+
if (action === "connect") {
|
|
937
|
+
await connectEndpoint({ env, platform, input, output });
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
if (action === "serve") {
|
|
941
|
+
const agentsDir = await prompt(input, output, "Agents directory", "./agents");
|
|
942
|
+
await forwardToPythonVendian(["cloud", "local", "serve", "--agents-dir", agentsDir || "./agents"], { env, platform });
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
if (action === "doctor") {
|
|
946
|
+
doctor({ env, platform });
|
|
947
|
+
await prompt(input, output, "Press Enter to return");
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
if (action === "update") {
|
|
951
|
+
await setup({ nonInteractive: true, env, platform });
|
|
952
|
+
await prompt(input, output, "Press Enter to return");
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
if (action === "help") {
|
|
956
|
+
output.write(`${helpText()}
|
|
957
|
+
`);
|
|
958
|
+
await prompt(input, output, "Press Enter to return");
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
963
|
+
async function connectEndpoint({ env, platform, input, output }) {
|
|
964
|
+
output.write("\nConnect endpoint:\n");
|
|
965
|
+
ENDPOINTS.forEach((endpoint2, index) => {
|
|
966
|
+
output.write(` ${index + 1}. ${endpoint2.label.padEnd(10)} ${BACKEND_TARGETS[endpoint2.key]}
|
|
967
|
+
`);
|
|
968
|
+
});
|
|
969
|
+
output.write(" 5. Custom API URL\n");
|
|
970
|
+
const choice = await prompt(input, output, "Endpoint");
|
|
971
|
+
if (choice === "5") {
|
|
972
|
+
const apiUrl = await prompt(input, output, "API URL");
|
|
973
|
+
if (!apiUrl) {
|
|
974
|
+
output.write("API URL is required.\n");
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
await setup({ apiUrl, forceAuth: true, env, platform });
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const endpoint = ENDPOINTS[Number(choice) - 1];
|
|
981
|
+
if (!endpoint) {
|
|
982
|
+
output.write("Unknown endpoint.\n");
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
await setup({ backend: endpoint.key, forceAuth: true, env, platform });
|
|
986
|
+
}
|
|
987
|
+
function renderEndpointRow(row, c) {
|
|
988
|
+
const status = row.status === "connected" ? c.badge("CONNECTED", "good") : row.status === "signed in" ? c.badge("SIGNED IN", "warn") : c.badge("OFFLINE", "bad");
|
|
989
|
+
const active = row.active ? c.good("*") : " ";
|
|
990
|
+
return ` ${active} ${row.label.padEnd(10)} ${status} ${c.dim(row.detail)}`;
|
|
991
|
+
}
|
|
992
|
+
function renderAction(action, selected, c) {
|
|
993
|
+
const marker = selected ? c.selected(" > ") : " ";
|
|
994
|
+
const label = selected ? c.selected(` ${action.label} `) : action.label;
|
|
995
|
+
return `${marker}${label}
|
|
996
|
+
${c.dim(action.detail)}`;
|
|
997
|
+
}
|
|
998
|
+
function helpText() {
|
|
999
|
+
return [
|
|
1000
|
+
"",
|
|
1001
|
+
"Common commands:",
|
|
1002
|
+
" vendian",
|
|
1003
|
+
" vendian login",
|
|
1004
|
+
" vendian login --backend staging",
|
|
1005
|
+
" vendian login --api-url http://localhost:3000",
|
|
1006
|
+
" vendian cloud local serve --agents-dir ./agents",
|
|
1007
|
+
" vendian doctor",
|
|
1008
|
+
" vendian update"
|
|
1009
|
+
].join("\n");
|
|
1010
|
+
}
|
|
1011
|
+
function readKey(input) {
|
|
1012
|
+
return new Promise((resolve) => {
|
|
1013
|
+
const handler = (_chunk, key) => {
|
|
1014
|
+
input.off("keypress", handler);
|
|
1015
|
+
resolve(key || {});
|
|
1016
|
+
};
|
|
1017
|
+
input.on("keypress", handler);
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
async function prompt(input, output, question, defaultValue = "") {
|
|
1021
|
+
const rl = import_promises.default.createInterface({ input, output });
|
|
1022
|
+
try {
|
|
1023
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
1024
|
+
const answer = await rl.question(`${question}${suffix}: `);
|
|
1025
|
+
return answer.trim() || defaultValue;
|
|
1026
|
+
} finally {
|
|
1027
|
+
rl.close();
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
var palette = {
|
|
1031
|
+
brand: (value) => `${BOLD}${CYAN}${value}${RESET}`,
|
|
1032
|
+
dim: (value) => `${DIM}${value}${RESET}`,
|
|
1033
|
+
good: (value) => `${GREEN}${value}${RESET}`,
|
|
1034
|
+
warn: (value) => `${YELLOW}${value}${RESET}`,
|
|
1035
|
+
bad: (value) => `${RED}${value}${RESET}`,
|
|
1036
|
+
label: (value) => `${DIM}${value.padEnd(10)}${RESET}`,
|
|
1037
|
+
section: (value) => `${BOLD}${value}${RESET}`,
|
|
1038
|
+
selected: (value) => `${INVERT}${value}${RESET}`,
|
|
1039
|
+
rule: (width) => `${DIM}${"-".repeat(width)}${RESET}`,
|
|
1040
|
+
badge: (value, tone) => {
|
|
1041
|
+
const color = tone === "good" ? GREEN : tone === "warn" ? YELLOW : RED;
|
|
1042
|
+
return `${color}${value.padEnd(9)}${RESET}`;
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
var plainPalette = {
|
|
1046
|
+
brand: String,
|
|
1047
|
+
dim: String,
|
|
1048
|
+
good: String,
|
|
1049
|
+
warn: String,
|
|
1050
|
+
bad: String,
|
|
1051
|
+
label: (value) => value.padEnd(10),
|
|
1052
|
+
section: String,
|
|
1053
|
+
selected: String,
|
|
1054
|
+
rule: (width) => "-".repeat(width),
|
|
1055
|
+
badge: (value) => value.padEnd(9)
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
// src/main.js
|
|
1059
|
+
function printHelp() {
|
|
1060
|
+
console.log(`Vendian CLI
|
|
1061
|
+
|
|
1062
|
+
Usage:
|
|
1063
|
+
vendian Open the interactive menu
|
|
1064
|
+
vendian login Sign in and prepare the local runtime
|
|
1065
|
+
vendian setup Alias for vendian login
|
|
1066
|
+
vendian doctor Check local bootstrap health
|
|
1067
|
+
vendian update Update the managed Vendian CLI/runtime
|
|
1068
|
+
vendian <command> Run a Vendian cloud command
|
|
1069
|
+
|
|
1070
|
+
Examples:
|
|
1071
|
+
vendian login
|
|
1072
|
+
vendian login --backend staging
|
|
1073
|
+
vendian cloud local serve --agents-dir ./agents
|
|
1074
|
+
|
|
1075
|
+
Environment:
|
|
1076
|
+
VENDIAN_CLI_HOME Override managed CLI home
|
|
1077
|
+
VENDIAN_API_URL Override Vendian backend API URL
|
|
1078
|
+
`);
|
|
1079
|
+
}
|
|
1080
|
+
async function main(argv) {
|
|
1081
|
+
const [command, ...rest] = argv;
|
|
1082
|
+
if (!command) {
|
|
1083
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
1084
|
+
await runTui();
|
|
1085
|
+
} else {
|
|
1086
|
+
printHelp();
|
|
1087
|
+
}
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (command === "--help" || command === "-h") {
|
|
1091
|
+
printHelp();
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (command === "--version" || command === "version") {
|
|
1095
|
+
console.log(CLI_VERSION);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (isBootstrapCommand(command)) {
|
|
1099
|
+
await setup(parseSetupOptions(rest));
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (command === "doctor") {
|
|
1103
|
+
doctor();
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (command === "update") {
|
|
1107
|
+
await setup({ nonInteractive: true });
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
await forwardToPythonVendian(argv);
|
|
1111
|
+
}
|
|
1112
|
+
function isBootstrapCommand(command) {
|
|
1113
|
+
return command === "login" || command === "setup";
|
|
1114
|
+
}
|
|
1115
|
+
function parseSetupOptions(args) {
|
|
1116
|
+
const options = {
|
|
1117
|
+
nonInteractive: args.includes("--yes") || args.includes("--non-interactive"),
|
|
1118
|
+
noBrowser: args.includes("--no-browser"),
|
|
1119
|
+
forceAuth: args.includes("--force") || args.includes("--reauth")
|
|
1120
|
+
};
|
|
1121
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1122
|
+
const arg = args[index];
|
|
1123
|
+
if (arg === "--backend" || arg === "-b") {
|
|
1124
|
+
options.backend = args[index + 1];
|
|
1125
|
+
index += 1;
|
|
1126
|
+
} else if (arg.startsWith("--backend=")) {
|
|
1127
|
+
options.backend = arg.slice("--backend=".length);
|
|
1128
|
+
} else if (arg === "--api-url") {
|
|
1129
|
+
options.apiUrl = args[index + 1];
|
|
1130
|
+
index += 1;
|
|
1131
|
+
} else if (arg.startsWith("--api-url=")) {
|
|
1132
|
+
options.apiUrl = arg.slice("--api-url=".length);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return options;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/entry.js
|
|
1139
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
1140
|
+
const message = error && typeof error.message === "string" ? error.message : String(error);
|
|
1141
|
+
console.error(`[vendian] ${message}`);
|
|
1142
|
+
process.exitCode = 1;
|
|
1143
|
+
});
|