@vendian/cli 0.0.34 → 0.0.36

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.
Files changed (2) hide show
  1. package/cli-wrapper.mjs +1722 -1026
  2. package/package.json +1 -1
package/cli-wrapper.mjs CHANGED
@@ -2,152 +2,498 @@
2
2
  import { createRequire as __vendianCreateRequire } from 'node:module';
3
3
  const require = __vendianCreateRequire(import.meta.url);
4
4
 
5
-
6
- // src/doctor.js
7
- import fs4 from "node:fs";
8
-
9
- // src/config.js
10
- import fs from "node:fs";
11
- import path2 from "node:path";
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
12
14
 
13
15
  // src/constants.js
14
- var DEFAULT_GITLAB_HOST = "gitlab.com";
15
- var DEFAULT_SDK_PUBLIC_PROJECT_ID = "77150592";
16
- var DEFAULT_SDK_RUNTIME_PROJECT_ID = "79410876";
17
- var PUBLIC_PACKAGE = "vendian-agents[full]";
18
- var RUNTIME_PACKAGE = "vendian-agents-runtime";
19
- var CONFIG_VERSION = 1;
20
- var BACKEND_TARGETS = {
21
- local: "http://localhost:3000",
22
- localhost: "http://localhost:3000",
23
- dev: "https://api.dev.vendian.ai",
24
- staging: "https://api.staging.vendian.ai",
25
- prod: "https://api.vendian.ai",
26
- production: "https://api.vendian.ai"
27
- };
16
+ var DEFAULT_GITLAB_HOST, DEFAULT_SDK_PUBLIC_PROJECT_ID, DEFAULT_SDK_RUNTIME_PROJECT_ID, PUBLIC_PACKAGE, RUNTIME_PACKAGE, CONFIG_VERSION, BACKEND_TARGETS;
17
+ var init_constants = __esm({
18
+ "src/constants.js"() {
19
+ DEFAULT_GITLAB_HOST = "gitlab.com";
20
+ DEFAULT_SDK_PUBLIC_PROJECT_ID = "77150592";
21
+ DEFAULT_SDK_RUNTIME_PROJECT_ID = "79410876";
22
+ PUBLIC_PACKAGE = "vendian-agents[full]";
23
+ RUNTIME_PACKAGE = "vendian-agents-runtime";
24
+ CONFIG_VERSION = 1;
25
+ BACKEND_TARGETS = {
26
+ local: "http://localhost:3000",
27
+ localhost: "http://localhost:3000",
28
+ dev: "https://api.dev.vendian.ai",
29
+ staging: "https://api.staging.vendian.ai",
30
+ prod: "https://api.vendian.ai",
31
+ production: "https://api.vendian.ai"
32
+ };
33
+ }
34
+ });
28
35
 
29
- // src/paths.js
30
- import os from "node:os";
31
- import path from "node:path";
32
- function pathApi(platform) {
33
- return platform === "win32" ? path.win32 : path.posix;
34
- }
35
- function homeDir(env, platform) {
36
- if (platform === "win32") {
37
- return env.USERPROFILE || os.homedir();
36
+ // src/auth.js
37
+ var auth_exports = {};
38
+ __export(auth_exports, {
39
+ activateCloudProfile: () => activateCloudProfile,
40
+ activeCloudAuthStatus: () => activeCloudAuthStatus,
41
+ buildLinuxOpenCommand: () => buildLinuxOpenCommand,
42
+ buildMacOpenCommand: () => buildMacOpenCommand,
43
+ buildWindowsOpenCommand: () => buildWindowsOpenCommand,
44
+ cloudAuthStatus: () => cloudAuthStatus,
45
+ cloudConfigPath: () => cloudConfigPath,
46
+ fetchPackageCredentials: () => fetchPackageCredentials,
47
+ loadCloudConfig: () => loadCloudConfig,
48
+ loginWithVendianOAuth: () => loginWithVendianOAuth,
49
+ resolveApiUrl: () => resolveApiUrl,
50
+ saveCloudToken: () => saveCloudToken
51
+ });
52
+ import crypto from "node:crypto";
53
+ import http from "node:http";
54
+ import fs6 from "node:fs";
55
+ import path5 from "node:path";
56
+ import { spawnSync as spawnSync3 } from "node:child_process";
57
+ function resolveApiUrl({ backend, apiUrl, env = process.env } = {}) {
58
+ if (apiUrl) {
59
+ return apiUrl.replace(/\/$/, "");
38
60
  }
39
- return env.HOME || os.homedir();
40
- }
41
- function vendianHome(env = process.env, platform = process.platform) {
42
- if (env.VENDIAN_CLI_HOME) {
43
- return pathApi(platform).resolve(env.VENDIAN_CLI_HOME);
61
+ if (env.VENDIAN_API_URL) {
62
+ return env.VENDIAN_API_URL.replace(/\/$/, "");
44
63
  }
45
- if (platform === "win32") {
46
- const root = env.LOCALAPPDATA || path.win32.join(homeDir(env, platform), "AppData", "Local");
47
- return path.win32.join(root, "Vendian", "cli");
64
+ const target = backend || "prod";
65
+ const resolved = BACKEND_TARGETS[target];
66
+ if (!resolved) {
67
+ throw new Error(`Unknown Vendian backend '${target}'. Use local, dev, staging, prod, or --api-url.`);
48
68
  }
49
- return path.posix.join(homeDir(env, platform), ".vendian", "cli");
69
+ return resolved;
50
70
  }
51
- function configPath(env = process.env, platform = process.platform) {
52
- if (env.VENDIAN_CLI_CONFIG) {
53
- return pathApi(platform).resolve(env.VENDIAN_CLI_CONFIG);
71
+ async function loginWithVendianOAuth({ backend, apiUrl, noBrowser = false, env = process.env } = {}) {
72
+ const resolvedApiUrl = resolveApiUrl({ backend, apiUrl, env });
73
+ const callback = await createCallbackServer(env);
74
+ try {
75
+ const config = await getOAuthConfig(resolvedApiUrl, callback.redirectUri);
76
+ const codeVerifier = crypto.randomBytes(48).toString("base64url");
77
+ const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
78
+ const state = crypto.randomBytes(18).toString("base64url");
79
+ const authorizationUrl = authorizationUrlFor(config, { state, codeChallenge });
80
+ if (noBrowser) {
81
+ console.error(`Open this URL to authenticate the CLI: ${authorizationUrl}`);
82
+ } else {
83
+ openBrowser(authorizationUrl);
84
+ }
85
+ const callbackResult = await callback.wait();
86
+ if (callbackResult.state !== state) {
87
+ throw new Error("OAuth state mismatch");
88
+ }
89
+ const clerkToken = await exchangeCodeForClerkToken(config, callbackResult.code, codeVerifier, callback.redirectUri);
90
+ const exchange = await postJson(`${resolvedApiUrl}/api/v1/cli/auth/oauth/exchange`, {
91
+ clerkToken
92
+ });
93
+ return { apiUrl: resolvedApiUrl, ...exchange };
94
+ } finally {
95
+ await callback.close();
54
96
  }
55
- return pathApi(platform).join(vendianHome(env, platform), "config.json");
56
97
  }
57
- function managedVenvPath(env = process.env, platform = process.platform) {
58
- if (env.VENDIAN_CLI_VENV) {
59
- return pathApi(platform).resolve(env.VENDIAN_CLI_VENV);
98
+ async function fetchPackageCredentials({ apiUrl, accessToken }) {
99
+ if (!apiUrl || !accessToken) {
100
+ return void 0;
60
101
  }
61
- return pathApi(platform).join(vendianHome(env, platform), ".venv");
62
- }
63
- function venvPython(venvPath, platform = process.platform) {
64
- return platform === "win32" ? path.win32.join(venvPath, "Scripts", "python.exe") : path.posix.join(venvPath, "bin", "python");
65
- }
66
- function venvVendian(venvPath, platform = process.platform) {
67
- return platform === "win32" ? path.win32.join(venvPath, "Scripts", "vendian.exe") : path.posix.join(venvPath, "bin", "vendian");
102
+ const payload = await getJson(`${String(apiUrl).replace(/\/$/, "")}/api/v1/cli/package-credentials`, {
103
+ Authorization: `Bearer ${accessToken}`
104
+ });
105
+ return payload.packageCredentials;
68
106
  }
69
-
70
- // src/config.js
71
- function loadConfig(env = process.env, platform = process.platform) {
72
- const file = configPath(env, platform);
107
+ function loadCloudConfig(env = process.env, platform = process.platform) {
108
+ const file = cloudConfigPath(env, platform);
73
109
  try {
74
- const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
75
- return typeof parsed === "object" && parsed !== null ? parsed : {};
76
- } catch (error) {
77
- if (error && error.code === "ENOENT") {
78
- return {};
79
- }
110
+ const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
111
+ return parsed && typeof parsed === "object" ? parsed : {};
112
+ } catch {
80
113
  return {};
81
114
  }
82
115
  }
83
- function saveConfig(config, env = process.env, platform = process.platform) {
84
- const file = configPath(env, platform);
85
- fs.mkdirSync(path2.dirname(file), { recursive: true });
86
- const next = { version: CONFIG_VERSION, ...config };
87
- fs.writeFileSync(file, `${JSON.stringify(next, null, 2)}
88
- `, { encoding: "utf8", mode: 384 });
89
- try {
90
- fs.chmodSync(file, 384);
91
- } catch {
92
- }
93
- return file;
116
+ function cloudAuthStatus({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
117
+ const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
118
+ const config = loadCloudConfig(env, platform);
119
+ const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
120
+ const profile = profiles[targetApiUrl];
121
+ return {
122
+ apiUrl: targetApiUrl,
123
+ activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
124
+ profile: profile && typeof profile === "object" ? profile : void 0,
125
+ authenticated: Boolean(profile?.access_token),
126
+ email: typeof profile?.email === "string" ? profile.email : void 0,
127
+ expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
128
+ profiles
129
+ };
94
130
  }
95
-
96
- // src/install.js
97
- import fs3 from "node:fs";
98
- import path3 from "node:path";
99
-
100
- // src/process.js
101
- import { spawn, spawnSync } from "node:child_process";
102
- function commandExists(command) {
103
- const result = spawnSync(command, ["--version"], { stdio: "ignore", shell: false });
104
- return result.status === 0;
131
+ function activeCloudAuthStatus({ env = process.env, platform = process.platform } = {}) {
132
+ const config = loadCloudConfig(env, platform);
133
+ const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
134
+ const apiUrl = typeof config.active_api_url === "string" ? config.active_api_url : void 0;
135
+ const profile = apiUrl ? profiles[apiUrl] : void 0;
136
+ return {
137
+ apiUrl,
138
+ activeApiUrl: apiUrl,
139
+ profile: profile && typeof profile === "object" ? profile : void 0,
140
+ authenticated: Boolean(profile?.access_token),
141
+ email: typeof profile?.email === "string" ? profile.email : void 0,
142
+ expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
143
+ profiles
144
+ };
105
145
  }
106
- function runCapture(command, args, options = {}) {
107
- const result = spawnSync(command, args, {
108
- encoding: "utf8",
109
- shell: false,
110
- ...options
111
- });
112
- const errorMessage = result.error && typeof result.error.message === "string" ? result.error.message : "";
146
+ function activateCloudProfile({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
147
+ const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
148
+ const config = loadCloudConfig(env, platform);
149
+ const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
150
+ const profile = profiles[targetApiUrl];
151
+ if (!profile || typeof profile !== "object" || !profile.access_token) {
152
+ return {
153
+ apiUrl: targetApiUrl,
154
+ activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
155
+ authenticated: false,
156
+ activated: false,
157
+ profiles
158
+ };
159
+ }
160
+ const next = {
161
+ ...config,
162
+ version: config.version || 2,
163
+ profiles,
164
+ active_api_url: targetApiUrl
165
+ };
166
+ writeCloudConfig(next, { env, platform });
113
167
  return {
114
- ok: !result.error && result.status === 0,
115
- status: result.status ?? 1,
116
- stdout: result.stdout || "",
117
- stderr: result.stderr || errorMessage
168
+ apiUrl: targetApiUrl,
169
+ activeApiUrl: targetApiUrl,
170
+ profile,
171
+ authenticated: true,
172
+ activated: true,
173
+ email: typeof profile.email === "string" ? profile.email : void 0,
174
+ expiresAt: typeof profile.expires_at === "string" ? profile.expires_at : void 0,
175
+ profiles
118
176
  };
119
177
  }
120
- function runInherit(command, args, options = {}) {
121
- const result = spawnSync(command, args, {
122
- stdio: "inherit",
123
- shell: false,
124
- ...options
178
+ async function getOAuthConfig(apiUrl, redirectUri) {
179
+ const url = new URL(`${apiUrl}/api/v1/cli/auth/oauth/config`);
180
+ url.searchParams.set("redirectUri", redirectUri);
181
+ return getJson(url);
182
+ }
183
+ function authorizationUrlFor(config, { state, codeChallenge }) {
184
+ const url = new URL(String(config.authorizationUrl));
185
+ url.searchParams.set("response_type", "code");
186
+ url.searchParams.set("client_id", String(config.clientId));
187
+ url.searchParams.set("redirect_uri", String(config.redirectUri));
188
+ url.searchParams.set("scope", String(config.scopes || "email profile"));
189
+ url.searchParams.set("state", state);
190
+ url.searchParams.set("code_challenge", codeChallenge);
191
+ url.searchParams.set("code_challenge_method", "S256");
192
+ return url.toString();
193
+ }
194
+ async function exchangeCodeForClerkToken(config, code, codeVerifier, redirectUri) {
195
+ const body = new URLSearchParams({
196
+ grant_type: "authorization_code",
197
+ client_id: String(config.clientId),
198
+ code,
199
+ redirect_uri: redirectUri,
200
+ code_verifier: codeVerifier
125
201
  });
126
- if (result.error) {
127
- throw result.error;
202
+ const payload = await postForm(String(config.tokenUrl), body);
203
+ const token = payload.access_token || payload.id_token;
204
+ if (!token) {
205
+ throw new Error("OAuth token exchange did not return a Clerk token");
128
206
  }
129
- return result.status ?? 1;
207
+ return String(token);
130
208
  }
131
- function spawnForward(command, args, options = {}) {
132
- return new Promise((resolve, reject) => {
133
- const child = spawn(command, args, {
134
- stdio: "inherit",
135
- shell: false,
136
- ...options
137
- });
138
- child.on("error", reject);
139
- child.on("exit", (code, signal) => {
140
- if (signal) {
141
- reject(new Error(`command terminated by ${signal}`));
142
- return;
143
- }
144
- resolve(code ?? 1);
145
- });
146
- });
209
+ async function getJson(url, headers = {}) {
210
+ const response = await fetch(url, { headers: { Accept: "application/json", ...headers } });
211
+ return decodeResponse(response);
147
212
  }
148
-
149
- // src/python.js
150
- import fs2 from "node:fs";
213
+ async function postJson(url, body) {
214
+ const response = await fetch(url, {
215
+ method: "POST",
216
+ headers: { Accept: "application/json", "Content-Type": "application/json" },
217
+ body: JSON.stringify(body)
218
+ });
219
+ return decodeResponse(response);
220
+ }
221
+ async function postForm(url, body) {
222
+ const response = await fetch(url, {
223
+ method: "POST",
224
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
225
+ body
226
+ });
227
+ return decodeResponse(response);
228
+ }
229
+ async function decodeResponse(response) {
230
+ const text = await response.text();
231
+ let payload = {};
232
+ if (text) {
233
+ try {
234
+ payload = JSON.parse(text);
235
+ } catch {
236
+ payload = { error: text };
237
+ }
238
+ }
239
+ if (!response.ok) {
240
+ throw new Error(payload.message || payload.error || `HTTP ${response.status}`);
241
+ }
242
+ return payload;
243
+ }
244
+ async function createCallbackServer(env) {
245
+ const port = Number(env.VENDIAN_CLI_OAUTH_REDIRECT_PORT || DEFAULT_OAUTH_REDIRECT_PORT);
246
+ let server;
247
+ let settled = false;
248
+ let timeout;
249
+ const waitPromise = new Promise((resolve, reject) => {
250
+ timeout = setTimeout(() => {
251
+ settled = true;
252
+ reject(new Error("OAuth login timed out before callback completed"));
253
+ }, 3e5);
254
+ server = http.createServer((req, res) => {
255
+ const url = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
256
+ const code = url.searchParams.get("code");
257
+ const state = url.searchParams.get("state");
258
+ const error = url.searchParams.get("error");
259
+ const body = code ? "Vendian CLI authentication complete. You can close this window." : "Vendian CLI authentication failed. Return to the terminal.";
260
+ res.writeHead(code ? 200 : 400, {
261
+ "Content-Type": "text/plain; charset=utf-8",
262
+ "Content-Length": Buffer.byteLength(body)
263
+ });
264
+ res.end(body);
265
+ clearTimeout(timeout);
266
+ if (settled) {
267
+ return;
268
+ }
269
+ settled = true;
270
+ if (error) {
271
+ reject(new Error(`OAuth login failed: ${error}`));
272
+ return;
273
+ }
274
+ if (!code) {
275
+ reject(new Error("OAuth callback did not include an authorization code"));
276
+ return;
277
+ }
278
+ resolve({ code, state });
279
+ });
280
+ server.on("error", reject);
281
+ server.listen(port, "127.0.0.1");
282
+ });
283
+ await new Promise((resolve, reject) => {
284
+ server.once("listening", resolve);
285
+ server.once("error", reject);
286
+ });
287
+ return {
288
+ redirectUri: `http://127.0.0.1:${server.address().port}/callback`,
289
+ wait: () => waitPromise,
290
+ close: () => new Promise((resolve) => server.close(resolve))
291
+ };
292
+ }
293
+ function openBrowser(url) {
294
+ const platform = process.platform;
295
+ if (platform === "win32") {
296
+ spawnSync3(...buildWindowsOpenCommand(url), { stdio: "ignore", shell: false });
297
+ return;
298
+ }
299
+ if (platform === "darwin") {
300
+ spawnSync3(...buildMacOpenCommand(url), { stdio: "ignore", shell: false });
301
+ return;
302
+ }
303
+ spawnSync3(...buildLinuxOpenCommand(url), { stdio: "ignore", shell: false });
304
+ }
305
+ function buildWindowsOpenCommand(url) {
306
+ return ["rundll32.exe", ["url.dll,FileProtocolHandler", String(url)]];
307
+ }
308
+ function buildMacOpenCommand(url) {
309
+ return ["open", [String(url)]];
310
+ }
311
+ function buildLinuxOpenCommand(url) {
312
+ return ["xdg-open", [String(url)]];
313
+ }
314
+ function cloudConfigPath(env = process.env, platform = process.platform) {
315
+ if (env.VENDIAN_CLOUD_CONFIG) {
316
+ return path5.resolve(env.VENDIAN_CLOUD_CONFIG);
317
+ }
318
+ if (platform === "win32") {
319
+ const root2 = env.APPDATA || path5.join(process.env.USERPROFILE || "", "AppData", "Roaming");
320
+ return path5.win32.join(root2, "Vendian", "cloud-auth.json");
321
+ }
322
+ const root = env.XDG_CONFIG_HOME || path5.join(process.env.HOME || "", ".config");
323
+ return path5.posix.join(root, "vendian", "cloud-auth.json");
324
+ }
325
+ function saveCloudToken(token, { env = process.env, platform = process.platform } = {}) {
326
+ const tokenApiUrl = String(token.apiUrl || "").replace(/\/$/, "");
327
+ let raw = { version: 2, profiles: {}, active_api_url: token.apiUrl };
328
+ try {
329
+ const existing = loadCloudConfig(env, platform);
330
+ if (existing && typeof existing === "object" && existing.profiles && typeof existing.profiles === "object") {
331
+ raw.profiles = existing.profiles;
332
+ }
333
+ } catch {
334
+ }
335
+ raw.active_api_url = tokenApiUrl;
336
+ raw.profiles[tokenApiUrl] = {
337
+ api_url: tokenApiUrl,
338
+ access_token: token.accessToken,
339
+ user_id: token.userId,
340
+ email: token.email,
341
+ expires_at: token.expiresAt,
342
+ scopes: token.scopes,
343
+ tooling_eligible: token.toolingEligible
344
+ };
345
+ return writeCloudConfig(raw, { env, platform });
346
+ }
347
+ function writeCloudConfig(raw, { env = process.env, platform = process.platform } = {}) {
348
+ const file = cloudConfigPath(env, platform);
349
+ fs6.mkdirSync(path5.dirname(file), { recursive: true });
350
+ fs6.writeFileSync(file, `${JSON.stringify(raw, null, 2)}
351
+ `, { encoding: "utf8", mode: 384 });
352
+ try {
353
+ fs6.chmodSync(file, 384);
354
+ } catch {
355
+ }
356
+ return file;
357
+ }
358
+ var DEFAULT_OAUTH_REDIRECT_PORT;
359
+ var init_auth = __esm({
360
+ "src/auth.js"() {
361
+ init_constants();
362
+ DEFAULT_OAUTH_REDIRECT_PORT = 8765;
363
+ }
364
+ });
365
+
366
+ // src/doctor.js
367
+ import fs4 from "node:fs";
368
+
369
+ // src/config.js
370
+ init_constants();
371
+ import fs from "node:fs";
372
+ import path2 from "node:path";
373
+
374
+ // src/paths.js
375
+ import os from "node:os";
376
+ import path from "node:path";
377
+ function pathApi(platform) {
378
+ return platform === "win32" ? path.win32 : path.posix;
379
+ }
380
+ function homeDir(env, platform) {
381
+ if (platform === "win32") {
382
+ return env.USERPROFILE || os.homedir();
383
+ }
384
+ return env.HOME || os.homedir();
385
+ }
386
+ function vendianHome(env = process.env, platform = process.platform) {
387
+ if (env.VENDIAN_CLI_HOME) {
388
+ return pathApi(platform).resolve(env.VENDIAN_CLI_HOME);
389
+ }
390
+ if (platform === "win32") {
391
+ const root = env.LOCALAPPDATA || path.win32.join(homeDir(env, platform), "AppData", "Local");
392
+ return path.win32.join(root, "Vendian", "cli");
393
+ }
394
+ return path.posix.join(homeDir(env, platform), ".vendian", "cli");
395
+ }
396
+ function configPath(env = process.env, platform = process.platform) {
397
+ if (env.VENDIAN_CLI_CONFIG) {
398
+ return pathApi(platform).resolve(env.VENDIAN_CLI_CONFIG);
399
+ }
400
+ return pathApi(platform).join(vendianHome(env, platform), "config.json");
401
+ }
402
+ function managedVenvPath(env = process.env, platform = process.platform) {
403
+ if (env.VENDIAN_CLI_VENV) {
404
+ return pathApi(platform).resolve(env.VENDIAN_CLI_VENV);
405
+ }
406
+ return pathApi(platform).join(vendianHome(env, platform), ".venv");
407
+ }
408
+ function venvPython(venvPath, platform = process.platform) {
409
+ return platform === "win32" ? path.win32.join(venvPath, "Scripts", "python.exe") : path.posix.join(venvPath, "bin", "python");
410
+ }
411
+ function venvVendian(venvPath, platform = process.platform) {
412
+ return platform === "win32" ? path.win32.join(venvPath, "Scripts", "vendian.exe") : path.posix.join(venvPath, "bin", "vendian");
413
+ }
414
+
415
+ // src/config.js
416
+ function loadConfig(env = process.env, platform = process.platform) {
417
+ const file = configPath(env, platform);
418
+ try {
419
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
420
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
421
+ } catch (error) {
422
+ if (error && error.code === "ENOENT") {
423
+ return {};
424
+ }
425
+ return {};
426
+ }
427
+ }
428
+ function saveConfig(config, env = process.env, platform = process.platform) {
429
+ const file = configPath(env, platform);
430
+ fs.mkdirSync(path2.dirname(file), { recursive: true });
431
+ const next = { version: CONFIG_VERSION, ...config };
432
+ fs.writeFileSync(file, `${JSON.stringify(next, null, 2)}
433
+ `, { encoding: "utf8", mode: 384 });
434
+ try {
435
+ fs.chmodSync(file, 384);
436
+ } catch {
437
+ }
438
+ return file;
439
+ }
440
+
441
+ // src/install.js
442
+ init_constants();
443
+ import fs3 from "node:fs";
444
+ import path3 from "node:path";
445
+
446
+ // src/process.js
447
+ import { spawn, spawnSync } from "node:child_process";
448
+ function commandExists(command) {
449
+ const result = spawnSync(command, ["--version"], { stdio: "ignore", shell: false });
450
+ return result.status === 0;
451
+ }
452
+ function runCapture(command, args, options = {}) {
453
+ const result = spawnSync(command, args, {
454
+ encoding: "utf8",
455
+ shell: false,
456
+ ...options
457
+ });
458
+ const errorMessage = result.error && typeof result.error.message === "string" ? result.error.message : "";
459
+ return {
460
+ ok: !result.error && result.status === 0,
461
+ status: result.status ?? 1,
462
+ stdout: result.stdout || "",
463
+ stderr: result.stderr || errorMessage
464
+ };
465
+ }
466
+ function runInherit(command, args, options = {}) {
467
+ const result = spawnSync(command, args, {
468
+ stdio: "inherit",
469
+ shell: false,
470
+ ...options
471
+ });
472
+ if (result.error) {
473
+ throw result.error;
474
+ }
475
+ return result.status ?? 1;
476
+ }
477
+ function spawnForward(command, args, options = {}) {
478
+ return new Promise((resolve, reject) => {
479
+ const child = spawn(command, args, {
480
+ stdio: "inherit",
481
+ shell: false,
482
+ ...options
483
+ });
484
+ child.on("error", reject);
485
+ child.on("exit", (code, signal) => {
486
+ if (signal) {
487
+ reject(new Error(`command terminated by ${signal}`));
488
+ return;
489
+ }
490
+ resolve(code ?? 1);
491
+ });
492
+ });
493
+ }
494
+
495
+ // src/python.js
496
+ import fs2 from "node:fs";
151
497
  function findPython(platform = process.platform) {
152
498
  const candidates = platform === "win32" ? [
153
499
  { command: "py", args: ["-3.11"] },
@@ -413,416 +759,343 @@ function doctor({ env = process.env, platform = process.platform } = {}) {
413
759
  console.log(`uv: ${hasUv() ? "yes" : "no, will use pip fallback"}`);
414
760
  console.log(`Managed Python: ${fs4.existsSync(pythonPath) ? pythonVersion(pythonPath) || "present" : "missing"}`);
415
761
  console.log(`Vendian executable: ${fs4.existsSync(vendianPath) ? vendianPath : "missing"}`);
416
- console.log(`Vendian imports: ${fs4.existsSync(pythonPath) && verifyVendianImports(pythonPath) ? "ok" : "missing"}`);
417
- console.log(`Package access: ${registry.token ? `configured (${registry.tokenSource})` : "missing"}`);
418
- }
419
-
420
- // src/forward.js
421
- import fs8 from "node:fs";
422
-
423
- // src/docs.js
424
- import fs5 from "node:fs";
425
- import path4 from "node:path";
426
- var DOC_WORKSPACES_KEY = "agentDocWorkspaces";
427
- function pathApi2(platform) {
428
- return platform === "win32" ? path4.win32 : path4.posix;
429
- }
430
- function commandWritesAgentDocs(args = []) {
431
- return args[0] === "init";
432
- }
433
- function initOutputDir(args = []) {
434
- for (let index = 1; index < args.length; index += 1) {
435
- const arg = args[index];
436
- if (arg === "--output-dir" || arg === "-o") {
437
- return args[index + 1] || ".";
438
- }
439
- if (arg.startsWith("--output-dir=")) {
440
- return arg.slice("--output-dir=".length) || ".";
441
- }
442
- }
443
- return ".";
444
- }
445
- function resolveWorkspacePath(outputDir, { cwd = process.cwd(), platform = process.platform } = {}) {
446
- const api = pathApi2(platform);
447
- const raw = outputDir || ".";
448
- return api.isAbsolute(raw) ? api.normalize(raw) : api.resolve(cwd, raw);
449
- }
450
- function recordAgentDocsWorkspace(outputDir, {
451
- env = process.env,
452
- platform = process.platform,
453
- cwd = process.cwd(),
454
- now = /* @__PURE__ */ new Date()
455
- } = {}) {
456
- const workspacePath = resolveWorkspacePath(outputDir, { cwd, platform });
457
- const config = loadConfig(env, platform);
458
- const existing = Array.isArray(config[DOC_WORKSPACES_KEY]) ? config[DOC_WORKSPACES_KEY] : [];
459
- const workspaces = [workspacePath, ...existing.filter((entry) => entry !== workspacePath)];
460
- const next = {
461
- ...config,
462
- [DOC_WORKSPACES_KEY]: workspaces,
463
- lastAgentDocsInitAt: now.toISOString()
464
- };
465
- saveConfig(next, env, platform);
466
- return next;
467
- }
468
- function recordForwardedDocsCommand(args, code, options = {}) {
469
- if (code !== 0 || !commandWritesAgentDocs(args)) {
470
- return false;
471
- }
472
- recordAgentDocsWorkspace(initOutputDir(args), options);
473
- return true;
762
+ console.log(`Vendian imports: ${fs4.existsSync(pythonPath) && verifyVendianImports(pythonPath) ? "ok" : "missing"}`);
763
+ console.log(`Package access: ${registry.token ? `configured (${registry.tokenSource})` : "missing"}`);
474
764
  }
475
- function refreshAgentDocsWorkspaces({
476
- config,
477
- venvPath,
478
- env = process.env,
479
- platform = process.platform,
480
- run: run2 = runInherit,
481
- now = /* @__PURE__ */ new Date()
765
+
766
+ // src/dev-server.js
767
+ import http2 from "node:http";
768
+ import fs11 from "node:fs";
769
+ import path8 from "node:path";
770
+ import { fileURLToPath } from "node:url";
771
+ import { spawn as spawn3 } from "node:child_process";
772
+
773
+ // src/agent-discovery.js
774
+ import fs5 from "node:fs";
775
+ import os2 from "node:os";
776
+ import path4 from "node:path";
777
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
778
+ ".agents",
779
+ ".git",
780
+ ".hg",
781
+ ".mypy_cache",
782
+ ".pytest_cache",
783
+ ".ruff_cache",
784
+ ".svn",
785
+ ".venv",
786
+ "__pycache__",
787
+ "build",
788
+ "dist",
789
+ "node_modules",
790
+ "venv"
791
+ ]);
792
+ var MANIFEST_NAMES = /* @__PURE__ */ new Set(["manifest.yaml", "manifest.yml"]);
793
+ function findAgentDirectoryCandidates({
794
+ cwd = process.cwd(),
795
+ home = os2.homedir(),
796
+ maxDepth = 6,
797
+ maxDirs = 1e3,
798
+ limit = 12
482
799
  } = {}) {
483
- const currentConfig = config || loadConfig(env, platform);
484
- const workspaces = Array.isArray(currentConfig[DOC_WORKSPACES_KEY]) ? [...new Set(currentConfig[DOC_WORKSPACES_KEY].filter((entry) => typeof entry === "string" && entry.trim()))] : [];
485
- if (workspaces.length === 0) {
486
- saveConfig(currentConfig, env, platform);
487
- return { config: currentConfig, refreshed: 0, failed: 0 };
488
- }
489
- const vendianPath = venvVendian(venvPath, platform);
490
- let refreshed = 0;
491
- let failed = 0;
492
- for (const workspace of workspaces) {
493
- if (!fs5.existsSync(workspace)) {
494
- continue;
800
+ const start = path4.resolve(cwd);
801
+ const candidates = [];
802
+ const seen = /* @__PURE__ */ new Set();
803
+ function addCandidate(candidatePath) {
804
+ const resolved = safeResolve(candidatePath);
805
+ if (!resolved || seen.has(resolved)) {
806
+ return;
495
807
  }
496
- const status = run2(vendianPath, ["init", "--output-dir", workspace, "--yes"]);
497
- if (status === 0) {
498
- refreshed += 1;
499
- } else {
500
- failed += 1;
501
- console.error(`[vendian] Could not refresh SDK docs in ${workspace}`);
808
+ const count = countAgentManifests(resolved, { maxDepth, maxDirs, limit: 100 });
809
+ if (count < 1) {
810
+ return;
502
811
  }
812
+ seen.add(resolved);
813
+ candidates.push({
814
+ path: displayPath(resolved, start),
815
+ absolutePath: resolved,
816
+ agentCount: count
817
+ });
503
818
  }
504
- const next = {
505
- ...currentConfig,
506
- [DOC_WORKSPACES_KEY]: workspaces,
507
- lastAgentDocsRefreshAt: now.toISOString()
508
- };
509
- saveConfig(next, env, platform);
510
- return { config: next, refreshed, failed };
511
- }
512
-
513
- // src/setup.js
514
- import fs7 from "node:fs";
515
-
516
- // src/auth.js
517
- import crypto from "node:crypto";
518
- import http from "node:http";
519
- import fs6 from "node:fs";
520
- import path5 from "node:path";
521
- import { spawnSync as spawnSync3 } from "node:child_process";
522
- var DEFAULT_OAUTH_REDIRECT_PORT = 8765;
523
- function resolveApiUrl({ backend, apiUrl, env = process.env } = {}) {
524
- if (apiUrl) {
525
- return apiUrl.replace(/\/$/, "");
819
+ addCandidate(path4.join(start, "agents"));
820
+ if (hasDirectManifest(start)) {
821
+ addCandidate(start);
526
822
  }
527
- if (env.VENDIAN_API_URL) {
528
- return env.VENDIAN_API_URL.replace(/\/$/, "");
823
+ for (const parent of parentDirs(start, 3)) {
824
+ addCandidate(path4.join(parent, "agents"));
529
825
  }
530
- const target = backend || "prod";
531
- const resolved = BACKEND_TARGETS[target];
532
- if (!resolved) {
533
- throw new Error(`Unknown Vendian backend '${target}'. Use local, dev, staging, prod, or --api-url.`);
826
+ const roots = uniqueExistingDirs([
827
+ start,
828
+ ...commonSearchRoots(home)
829
+ ]);
830
+ for (const root of roots) {
831
+ for (const manifest of findManifestFiles(root, { maxDepth, maxDirs, limit: 250 })) {
832
+ const workspace = nearestAgentsAncestor(manifest, root);
833
+ addCandidate(workspace || path4.dirname(manifest));
834
+ if (candidates.length >= limit) {
835
+ return candidates.slice(0, limit);
836
+ }
837
+ }
534
838
  }
535
- return resolved;
839
+ return candidates.slice(0, limit);
536
840
  }
537
- async function loginWithVendianOAuth({ backend, apiUrl, noBrowser = false, env = process.env } = {}) {
538
- const resolvedApiUrl = resolveApiUrl({ backend, apiUrl, env });
539
- const callback = await createCallbackServer(env);
540
- try {
541
- const config = await getOAuthConfig(resolvedApiUrl, callback.redirectUri);
542
- const codeVerifier = crypto.randomBytes(48).toString("base64url");
543
- const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
544
- const state = crypto.randomBytes(18).toString("base64url");
545
- const authorizationUrl = authorizationUrlFor(config, { state, codeChallenge });
546
- if (noBrowser) {
547
- console.error(`Open this URL to authenticate the CLI: ${authorizationUrl}`);
548
- } else {
549
- openBrowser(authorizationUrl);
550
- }
551
- const callbackResult = await callback.wait();
552
- if (callbackResult.state !== state) {
553
- throw new Error("OAuth state mismatch");
841
+ function findAgentFolders(root, {
842
+ cwd = process.cwd(),
843
+ maxDepth = 6,
844
+ maxDirs = 1e3,
845
+ limit = 100
846
+ } = {}) {
847
+ const start = path4.resolve(cwd);
848
+ const resolvedRoot = safeResolve(root || ".");
849
+ if (!resolvedRoot) {
850
+ return [];
851
+ }
852
+ const seen = /* @__PURE__ */ new Set();
853
+ const folders = [];
854
+ for (const manifest of findManifestFiles(resolvedRoot, { maxDepth, maxDirs, limit })) {
855
+ const folder = path4.dirname(manifest);
856
+ const resolved = safeResolve(folder);
857
+ if (!resolved || seen.has(resolved)) {
858
+ continue;
554
859
  }
555
- const clerkToken = await exchangeCodeForClerkToken(config, callbackResult.code, codeVerifier, callback.redirectUri);
556
- const exchange = await postJson(`${resolvedApiUrl}/api/v1/cli/auth/oauth/exchange`, {
557
- clerkToken
860
+ seen.add(resolved);
861
+ folders.push({
862
+ path: displayPath(resolved, start),
863
+ absolutePath: resolved,
864
+ name: path4.basename(resolved) || displayPath(resolved, start)
558
865
  });
559
- return { apiUrl: resolvedApiUrl, ...exchange };
560
- } finally {
561
- await callback.close();
562
- }
563
- }
564
- async function fetchPackageCredentials({ apiUrl, accessToken }) {
565
- if (!apiUrl || !accessToken) {
566
- return void 0;
567
866
  }
568
- const payload = await getJson(`${String(apiUrl).replace(/\/$/, "")}/api/v1/cli/package-credentials`, {
569
- Authorization: `Bearer ${accessToken}`
570
- });
571
- return payload.packageCredentials;
867
+ return folders.sort((a, b) => a.path.localeCompare(b.path)).slice(0, limit);
572
868
  }
573
- function loadCloudConfig(env = process.env, platform = process.platform) {
574
- const file = cloudConfigPath(env, platform);
575
- try {
576
- const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
577
- return parsed && typeof parsed === "object" ? parsed : {};
578
- } catch {
579
- return {};
869
+ function countAgentManifests(root, { maxDepth = 6, maxDirs = 1e3, limit = 100 } = {}) {
870
+ let count = 0;
871
+ for (const _manifest of findManifestFiles(root, { maxDepth, maxDirs, limit })) {
872
+ count += 1;
873
+ if (count >= limit) {
874
+ return count;
875
+ }
580
876
  }
877
+ return count;
581
878
  }
582
- function cloudAuthStatus({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
583
- const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
584
- const config = loadCloudConfig(env, platform);
585
- const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
586
- const profile = profiles[targetApiUrl];
587
- return {
588
- apiUrl: targetApiUrl,
589
- activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
590
- profile: profile && typeof profile === "object" ? profile : void 0,
591
- authenticated: Boolean(profile?.access_token),
592
- email: typeof profile?.email === "string" ? profile.email : void 0,
593
- expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
594
- profiles
595
- };
596
- }
597
- function activeCloudAuthStatus({ env = process.env, platform = process.platform } = {}) {
598
- const config = loadCloudConfig(env, platform);
599
- const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
600
- const apiUrl = typeof config.active_api_url === "string" ? config.active_api_url : void 0;
601
- const profile = apiUrl ? profiles[apiUrl] : void 0;
602
- return {
603
- apiUrl,
604
- activeApiUrl: apiUrl,
605
- profile: profile && typeof profile === "object" ? profile : void 0,
606
- authenticated: Boolean(profile?.access_token),
607
- email: typeof profile?.email === "string" ? profile.email : void 0,
608
- expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
609
- profiles
610
- };
611
- }
612
- function activateCloudProfile({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
613
- const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
614
- const config = loadCloudConfig(env, platform);
615
- const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
616
- const profile = profiles[targetApiUrl];
617
- if (!profile || typeof profile !== "object" || !profile.access_token) {
618
- return {
619
- apiUrl: targetApiUrl,
620
- activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
621
- authenticated: false,
622
- activated: false,
623
- profiles
624
- };
879
+ function findManifestFiles(root, { maxDepth, maxDirs, limit }) {
880
+ const manifests = [];
881
+ const stack = [{ dir: root, depth: 0 }];
882
+ const seen = /* @__PURE__ */ new Set();
883
+ while (stack.length && manifests.length < limit && seen.size < maxDirs) {
884
+ const { dir, depth } = stack.pop();
885
+ const resolved = safeResolve(dir);
886
+ if (!resolved || seen.has(resolved)) {
887
+ continue;
888
+ }
889
+ seen.add(resolved);
890
+ let entries;
891
+ try {
892
+ entries = fs5.readdirSync(resolved, { withFileTypes: true });
893
+ } catch {
894
+ continue;
895
+ }
896
+ for (const entry of entries) {
897
+ if (entry.isFile() && MANIFEST_NAMES.has(entry.name)) {
898
+ manifests.push(path4.join(resolved, entry.name));
899
+ if (manifests.length >= limit) {
900
+ break;
901
+ }
902
+ }
903
+ }
904
+ if (depth >= maxDepth) {
905
+ continue;
906
+ }
907
+ for (const entry of entries) {
908
+ if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) {
909
+ continue;
910
+ }
911
+ stack.push({ dir: path4.join(resolved, entry.name), depth: depth + 1 });
912
+ }
625
913
  }
626
- const next = {
627
- ...config,
628
- version: config.version || 2,
629
- profiles,
630
- active_api_url: targetApiUrl
631
- };
632
- writeCloudConfig(next, { env, platform });
633
- return {
634
- apiUrl: targetApiUrl,
635
- activeApiUrl: targetApiUrl,
636
- profile,
637
- authenticated: true,
638
- activated: true,
639
- email: typeof profile.email === "string" ? profile.email : void 0,
640
- expiresAt: typeof profile.expires_at === "string" ? profile.expires_at : void 0,
641
- profiles
642
- };
643
- }
644
- async function getOAuthConfig(apiUrl, redirectUri) {
645
- const url = new URL(`${apiUrl}/api/v1/cli/auth/oauth/config`);
646
- url.searchParams.set("redirectUri", redirectUri);
647
- return getJson(url);
648
- }
649
- function authorizationUrlFor(config, { state, codeChallenge }) {
650
- const url = new URL(String(config.authorizationUrl));
651
- url.searchParams.set("response_type", "code");
652
- url.searchParams.set("client_id", String(config.clientId));
653
- url.searchParams.set("redirect_uri", String(config.redirectUri));
654
- url.searchParams.set("scope", String(config.scopes || "email profile"));
655
- url.searchParams.set("state", state);
656
- url.searchParams.set("code_challenge", codeChallenge);
657
- url.searchParams.set("code_challenge_method", "S256");
658
- return url.toString();
914
+ return manifests;
659
915
  }
660
- async function exchangeCodeForClerkToken(config, code, codeVerifier, redirectUri) {
661
- const body = new URLSearchParams({
662
- grant_type: "authorization_code",
663
- client_id: String(config.clientId),
664
- code,
665
- redirect_uri: redirectUri,
666
- code_verifier: codeVerifier
667
- });
668
- const payload = await postForm(String(config.tokenUrl), body);
669
- const token = payload.access_token || payload.id_token;
670
- if (!token) {
671
- throw new Error("OAuth token exchange did not return a Clerk token");
916
+ function nearestAgentsAncestor(manifestPath, root) {
917
+ let current = path4.dirname(manifestPath);
918
+ const stop = path4.resolve(root);
919
+ while (current.startsWith(stop)) {
920
+ if (path4.basename(current).toLowerCase() === "agents") {
921
+ return current;
922
+ }
923
+ const parent = path4.dirname(current);
924
+ if (parent === current) {
925
+ break;
926
+ }
927
+ current = parent;
672
928
  }
673
- return String(token);
929
+ return null;
674
930
  }
675
- async function getJson(url, headers = {}) {
676
- const response = await fetch(url, { headers: { Accept: "application/json", ...headers } });
677
- return decodeResponse(response);
931
+ function hasDirectManifest(root) {
932
+ return [...MANIFEST_NAMES].some((name) => fs5.existsSync(path4.join(root, name)));
678
933
  }
679
- async function postJson(url, body) {
680
- const response = await fetch(url, {
681
- method: "POST",
682
- headers: { Accept: "application/json", "Content-Type": "application/json" },
683
- body: JSON.stringify(body)
684
- });
685
- return decodeResponse(response);
934
+ function displayPath(target, cwd) {
935
+ const relative = path4.relative(cwd, target);
936
+ if (!relative) {
937
+ return ".";
938
+ }
939
+ if (relative.startsWith("..") || path4.isAbsolute(relative)) {
940
+ return target;
941
+ }
942
+ return relative.startsWith(".") ? relative : `.${path4.sep}${relative}`;
686
943
  }
687
- async function postForm(url, body) {
688
- const response = await fetch(url, {
689
- method: "POST",
690
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
691
- body
692
- });
693
- return decodeResponse(response);
944
+ function commonSearchRoots(home) {
945
+ if (!home) {
946
+ return [];
947
+ }
948
+ return [
949
+ "agents",
950
+ "Projects",
951
+ "Code",
952
+ "Developer",
953
+ "Documents",
954
+ "Desktop"
955
+ ].map((name) => path4.join(home, name));
694
956
  }
695
- async function decodeResponse(response) {
696
- const text = await response.text();
697
- let payload = {};
698
- if (text) {
957
+ function uniqueExistingDirs(paths) {
958
+ const result = [];
959
+ const seen = /* @__PURE__ */ new Set();
960
+ for (const item of paths) {
961
+ const resolved = safeResolve(item);
962
+ if (!resolved || seen.has(resolved)) {
963
+ continue;
964
+ }
699
965
  try {
700
- payload = JSON.parse(text);
966
+ if (!fs5.statSync(resolved).isDirectory()) {
967
+ continue;
968
+ }
701
969
  } catch {
702
- payload = { error: text };
970
+ continue;
703
971
  }
972
+ seen.add(resolved);
973
+ result.push(resolved);
704
974
  }
705
- if (!response.ok) {
706
- throw new Error(payload.message || payload.error || `HTTP ${response.status}`);
707
- }
708
- return payload;
709
- }
710
- async function createCallbackServer(env) {
711
- const port = Number(env.VENDIAN_CLI_OAUTH_REDIRECT_PORT || DEFAULT_OAUTH_REDIRECT_PORT);
712
- let server;
713
- let settled = false;
714
- let timeout;
715
- const waitPromise = new Promise((resolve, reject) => {
716
- timeout = setTimeout(() => {
717
- settled = true;
718
- reject(new Error("OAuth login timed out before callback completed"));
719
- }, 3e5);
720
- server = http.createServer((req, res) => {
721
- const url = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
722
- const code = url.searchParams.get("code");
723
- const state = url.searchParams.get("state");
724
- const error = url.searchParams.get("error");
725
- const body = code ? "Vendian CLI authentication complete. You can close this window." : "Vendian CLI authentication failed. Return to the terminal.";
726
- res.writeHead(code ? 200 : 400, {
727
- "Content-Type": "text/plain; charset=utf-8",
728
- "Content-Length": Buffer.byteLength(body)
729
- });
730
- res.end(body);
731
- clearTimeout(timeout);
732
- if (settled) {
733
- return;
734
- }
735
- settled = true;
736
- if (error) {
737
- reject(new Error(`OAuth login failed: ${error}`));
738
- return;
739
- }
740
- if (!code) {
741
- reject(new Error("OAuth callback did not include an authorization code"));
742
- return;
743
- }
744
- resolve({ code, state });
745
- });
746
- server.on("error", reject);
747
- server.listen(port, "127.0.0.1");
748
- });
749
- await new Promise((resolve, reject) => {
750
- server.once("listening", resolve);
751
- server.once("error", reject);
752
- });
753
- return {
754
- redirectUri: `http://127.0.0.1:${server.address().port}/callback`,
755
- wait: () => waitPromise,
756
- close: () => new Promise((resolve) => server.close(resolve))
757
- };
975
+ return result;
758
976
  }
759
- function openBrowser(url) {
760
- const platform = process.platform;
761
- if (platform === "win32") {
762
- spawnSync3(...buildWindowsOpenCommand(url), { stdio: "ignore", shell: false });
763
- return;
977
+ function parentDirs(start, limit) {
978
+ const parents = [];
979
+ let current = path4.resolve(start);
980
+ for (let index = 0; index < limit; index += 1) {
981
+ const parent = path4.dirname(current);
982
+ if (!parent || parent === current) {
983
+ break;
984
+ }
985
+ parents.push(parent);
986
+ current = parent;
764
987
  }
765
- if (platform === "darwin") {
766
- spawnSync3(...buildMacOpenCommand(url), { stdio: "ignore", shell: false });
767
- return;
988
+ return parents;
989
+ }
990
+ function safeResolve(candidate) {
991
+ try {
992
+ return path4.resolve(candidate);
993
+ } catch {
994
+ return null;
768
995
  }
769
- spawnSync3(...buildLinuxOpenCommand(url), { stdio: "ignore", shell: false });
770
996
  }
771
- function buildWindowsOpenCommand(url) {
772
- return ["rundll32.exe", ["url.dll,FileProtocolHandler", String(url)]];
997
+
998
+ // src/dev-server.js
999
+ init_auth();
1000
+ init_constants();
1001
+
1002
+ // src/forward.js
1003
+ import fs9 from "node:fs";
1004
+
1005
+ // src/docs.js
1006
+ import fs7 from "node:fs";
1007
+ import path6 from "node:path";
1008
+ var DOC_WORKSPACES_KEY = "agentDocWorkspaces";
1009
+ function pathApi2(platform) {
1010
+ return platform === "win32" ? path6.win32 : path6.posix;
773
1011
  }
774
- function buildMacOpenCommand(url) {
775
- return ["open", [String(url)]];
1012
+ function commandWritesAgentDocs(args = []) {
1013
+ return args[0] === "init";
776
1014
  }
777
- function buildLinuxOpenCommand(url) {
778
- return ["xdg-open", [String(url)]];
1015
+ function initOutputDir(args = []) {
1016
+ for (let index = 1; index < args.length; index += 1) {
1017
+ const arg = args[index];
1018
+ if (arg === "--output-dir" || arg === "-o") {
1019
+ return args[index + 1] || ".";
1020
+ }
1021
+ if (arg.startsWith("--output-dir=")) {
1022
+ return arg.slice("--output-dir=".length) || ".";
1023
+ }
1024
+ }
1025
+ return ".";
779
1026
  }
780
- function cloudConfigPath(env = process.env, platform = process.platform) {
781
- if (env.VENDIAN_CLOUD_CONFIG) {
782
- return path5.resolve(env.VENDIAN_CLOUD_CONFIG);
1027
+ function resolveWorkspacePath(outputDir, { cwd = process.cwd(), platform = process.platform } = {}) {
1028
+ const api = pathApi2(platform);
1029
+ const raw = outputDir || ".";
1030
+ return api.isAbsolute(raw) ? api.normalize(raw) : api.resolve(cwd, raw);
1031
+ }
1032
+ function recordAgentDocsWorkspace(outputDir, {
1033
+ env = process.env,
1034
+ platform = process.platform,
1035
+ cwd = process.cwd(),
1036
+ now = /* @__PURE__ */ new Date()
1037
+ } = {}) {
1038
+ const workspacePath = resolveWorkspacePath(outputDir, { cwd, platform });
1039
+ const config = loadConfig(env, platform);
1040
+ const existing = Array.isArray(config[DOC_WORKSPACES_KEY]) ? config[DOC_WORKSPACES_KEY] : [];
1041
+ const workspaces = [workspacePath, ...existing.filter((entry) => entry !== workspacePath)];
1042
+ const next = {
1043
+ ...config,
1044
+ [DOC_WORKSPACES_KEY]: workspaces,
1045
+ lastAgentDocsInitAt: now.toISOString()
1046
+ };
1047
+ saveConfig(next, env, platform);
1048
+ return next;
1049
+ }
1050
+ function recordForwardedDocsCommand(args, code, options = {}) {
1051
+ if (code !== 0 || !commandWritesAgentDocs(args)) {
1052
+ return false;
783
1053
  }
784
- if (platform === "win32") {
785
- const root2 = env.APPDATA || path5.join(process.env.USERPROFILE || "", "AppData", "Roaming");
786
- return path5.win32.join(root2, "Vendian", "cloud-auth.json");
1054
+ recordAgentDocsWorkspace(initOutputDir(args), options);
1055
+ return true;
1056
+ }
1057
+ function refreshAgentDocsWorkspaces({
1058
+ config,
1059
+ venvPath,
1060
+ env = process.env,
1061
+ platform = process.platform,
1062
+ run: run2 = runInherit,
1063
+ now = /* @__PURE__ */ new Date()
1064
+ } = {}) {
1065
+ const currentConfig = config || loadConfig(env, platform);
1066
+ const workspaces = Array.isArray(currentConfig[DOC_WORKSPACES_KEY]) ? [...new Set(currentConfig[DOC_WORKSPACES_KEY].filter((entry) => typeof entry === "string" && entry.trim()))] : [];
1067
+ if (workspaces.length === 0) {
1068
+ saveConfig(currentConfig, env, platform);
1069
+ return { config: currentConfig, refreshed: 0, failed: 0 };
787
1070
  }
788
- const root = env.XDG_CONFIG_HOME || path5.join(process.env.HOME || "", ".config");
789
- return path5.posix.join(root, "vendian", "cloud-auth.json");
790
- }
791
- function saveCloudToken(token, { env = process.env, platform = process.platform } = {}) {
792
- const tokenApiUrl = String(token.apiUrl || "").replace(/\/$/, "");
793
- let raw = { version: 2, profiles: {}, active_api_url: token.apiUrl };
794
- try {
795
- const existing = loadCloudConfig(env, platform);
796
- if (existing && typeof existing === "object" && existing.profiles && typeof existing.profiles === "object") {
797
- raw.profiles = existing.profiles;
1071
+ const vendianPath = venvVendian(venvPath, platform);
1072
+ let refreshed = 0;
1073
+ let failed = 0;
1074
+ for (const workspace of workspaces) {
1075
+ if (!fs7.existsSync(workspace)) {
1076
+ continue;
1077
+ }
1078
+ const status = run2(vendianPath, ["init", "--output-dir", workspace, "--yes"]);
1079
+ if (status === 0) {
1080
+ refreshed += 1;
1081
+ } else {
1082
+ failed += 1;
1083
+ console.error(`[vendian] Could not refresh SDK docs in ${workspace}`);
798
1084
  }
799
- } catch {
800
1085
  }
801
- raw.active_api_url = tokenApiUrl;
802
- raw.profiles[tokenApiUrl] = {
803
- api_url: tokenApiUrl,
804
- access_token: token.accessToken,
805
- user_id: token.userId,
806
- email: token.email,
807
- expires_at: token.expiresAt,
808
- scopes: token.scopes,
809
- tooling_eligible: token.toolingEligible
1086
+ const next = {
1087
+ ...currentConfig,
1088
+ [DOC_WORKSPACES_KEY]: workspaces,
1089
+ lastAgentDocsRefreshAt: now.toISOString()
810
1090
  };
811
- return writeCloudConfig(raw, { env, platform });
812
- }
813
- function writeCloudConfig(raw, { env = process.env, platform = process.platform } = {}) {
814
- const file = cloudConfigPath(env, platform);
815
- fs6.mkdirSync(path5.dirname(file), { recursive: true });
816
- fs6.writeFileSync(file, `${JSON.stringify(raw, null, 2)}
817
- `, { encoding: "utf8", mode: 384 });
818
- try {
819
- fs6.chmodSync(file, 384);
820
- } catch {
821
- }
822
- return file;
1091
+ saveConfig(next, env, platform);
1092
+ return { config: next, refreshed, failed };
823
1093
  }
824
1094
 
825
1095
  // src/setup.js
1096
+ init_auth();
1097
+ init_constants();
1098
+ import fs8 from "node:fs";
826
1099
  async function setup({
827
1100
  nonInteractive = false,
828
1101
  backend = void 0,
@@ -890,7 +1163,7 @@ async function setup({
890
1163
  throw new Error("Vendian packages installed, but import verification failed.");
891
1164
  }
892
1165
  const vendianPath = venvVendian(venvPath, platform);
893
- if (!fs7.existsSync(vendianPath)) {
1166
+ if (!fs8.existsSync(vendianPath)) {
894
1167
  throw new Error("Vendian executable was not found after install.");
895
1168
  }
896
1169
  console.log("");
@@ -989,7 +1262,7 @@ async function preparePythonVendianInvocation(args, {
989
1262
  const venvPath = managedVenvPath(env, platform);
990
1263
  const vendianPath = venvVendian(venvPath, platform);
991
1264
  reportProgress(onProgress, "Checking managed Python runtime");
992
- if (!fs8.existsSync(vendianPath)) {
1265
+ if (!fs9.existsSync(vendianPath)) {
993
1266
  throw new Error("Vendian is not set up yet. Run `vendian login` first.");
994
1267
  }
995
1268
  const loadedConfig = loadConfig(env, platform);
@@ -1010,332 +1283,148 @@ async function preparePythonVendianInvocation(args, {
1010
1283
  reportProgress(onProgress, "Launching local serve daemon");
1011
1284
  return {
1012
1285
  command: vendianPath,
1013
- args,
1014
- env: {
1015
- ...env,
1016
- ...packageIndexEnv(config, env, platform)
1017
- }
1018
- };
1019
- }
1020
- function commandNeedsPackageAccess(args = []) {
1021
- return args[0] === "cloud" && args[1] === "local" && ["serve", "run"].includes(args[2]);
1022
- }
1023
- function packageIndexEnv(config = {}, env = process.env, platform = process.platform) {
1024
- const registry = registryConfig(config, env, platform);
1025
- const indexes = buildIndexUrls(registry);
1026
- if (!indexes) {
1027
- return {};
1028
- }
1029
- const extraIndexUrls = joinIndexUrls([
1030
- indexes.runtimeIndexUrl,
1031
- "https://pypi.org/simple",
1032
- env.VENDIAN_PIP_EXTRA_INDEX_URL
1033
- ]);
1034
- const uvExtraIndexUrls = joinIndexUrls([
1035
- indexes.runtimeIndexUrl,
1036
- "https://pypi.org/simple",
1037
- env.VENDIAN_UV_EXTRA_INDEX_URL
1038
- ]);
1039
- return {
1040
- VENDIAN_PIP_INDEX_URL: indexes.sdkIndexUrl,
1041
- VENDIAN_PIP_EXTRA_INDEX_URL: extraIndexUrls,
1042
- VENDIAN_UV_INDEX_URL: indexes.sdkIndexUrl,
1043
- VENDIAN_UV_EXTRA_INDEX_URL: uvExtraIndexUrls
1044
- };
1045
- }
1046
- function joinIndexUrls(urls) {
1047
- return urls.flatMap((value) => String(value || "").split(/\s+/)).map((value) => value.trim()).filter(Boolean).filter((value, index, all) => all.indexOf(value) === index).join(" ");
1048
- }
1049
- function maybeAutoUpdateManagedEnv({
1050
- env = process.env,
1051
- platform = process.platform,
1052
- venvPath = managedVenvPath(env, platform),
1053
- force = false,
1054
- log = true,
1055
- load = loadConfig,
1056
- save = saveConfig,
1057
- installPackages = installVendianPackages,
1058
- refreshDocs = refreshAgentDocsWorkspaces,
1059
- now = Date.now(),
1060
- onProgress = null
1061
- } = {}) {
1062
- if (env.VENDIAN_SKIP_AUTO_UPDATE === "1") {
1063
- return false;
1064
- }
1065
- const config = load(env, platform);
1066
- const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
1067
- if (!force && Number.isFinite(lastUpdate) && now - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
1068
- return false;
1069
- }
1070
- const registry = registryConfig(config, env, platform);
1071
- if (!registry.token) {
1072
- return false;
1073
- }
1074
- const pythonPath = venvPython(venvPath, platform);
1075
- if (!fs8.existsSync(pythonPath)) {
1076
- return false;
1077
- }
1078
- try {
1079
- if (log) {
1080
- console.error("[vendian] Checking managed CLI/runtime updates...");
1081
- }
1082
- reportProgress(onProgress, "Installing managed CLI/runtime updates");
1083
- installPackages({ pythonPath, venvPath, config, env, platform });
1084
- const next = { ...config, lastManagedUpdateAt: new Date(now).toISOString() };
1085
- save(next, env, platform);
1086
- reportProgress(onProgress, "Refreshing generated agent docs");
1087
- refreshDocs({ config: next, venvPath, env, platform });
1088
- reportProgress(onProgress, "Managed CLI/runtime ready");
1089
- return true;
1090
- } catch (error) {
1091
- const message = error && typeof error.message === "string" ? error.message : String(error);
1092
- if (log) {
1093
- console.error(`[vendian] Update check failed; continuing with installed CLI. ${message}`);
1094
- }
1095
- return false;
1096
- }
1097
- }
1098
- function reportProgress(onProgress, message) {
1099
- if (typeof onProgress === "function") {
1100
- onProgress(message);
1101
- }
1102
- }
1103
-
1104
- // src/tui.js
1105
- import fs11 from "node:fs";
1106
- import path8 from "node:path";
1107
- import readline from "node:readline";
1108
-
1109
- // src/version.js
1110
- var CLI_VERSION = true ? "0.0.34" : process.env.npm_package_version || "0.0.0-dev";
1111
-
1112
- // src/npm-update.js
1113
- var NPM_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
1114
-
1115
- // src/agent-discovery.js
1116
- import fs9 from "node:fs";
1117
- import os2 from "node:os";
1118
- import path6 from "node:path";
1119
- var SKIP_DIRS = /* @__PURE__ */ new Set([
1120
- ".agents",
1121
- ".git",
1122
- ".hg",
1123
- ".mypy_cache",
1124
- ".pytest_cache",
1125
- ".ruff_cache",
1126
- ".svn",
1127
- ".venv",
1128
- "__pycache__",
1129
- "build",
1130
- "dist",
1131
- "node_modules",
1132
- "venv"
1133
- ]);
1134
- var MANIFEST_NAMES = /* @__PURE__ */ new Set(["manifest.yaml", "manifest.yml"]);
1135
- function findAgentDirectoryCandidates({
1136
- cwd = process.cwd(),
1137
- home = os2.homedir(),
1138
- maxDepth = 6,
1139
- maxDirs = 1e3,
1140
- limit = 12
1141
- } = {}) {
1142
- const start = path6.resolve(cwd);
1143
- const candidates = [];
1144
- const seen = /* @__PURE__ */ new Set();
1145
- function addCandidate(candidatePath) {
1146
- const resolved = safeResolve(candidatePath);
1147
- if (!resolved || seen.has(resolved)) {
1148
- return;
1149
- }
1150
- const count = countAgentManifests(resolved, { maxDepth, maxDirs, limit: 100 });
1151
- if (count < 1) {
1152
- return;
1153
- }
1154
- seen.add(resolved);
1155
- candidates.push({
1156
- path: displayPath(resolved, start),
1157
- absolutePath: resolved,
1158
- agentCount: count
1159
- });
1160
- }
1161
- addCandidate(path6.join(start, "agents"));
1162
- if (hasDirectManifest(start)) {
1163
- addCandidate(start);
1164
- }
1165
- for (const parent of parentDirs(start, 3)) {
1166
- addCandidate(path6.join(parent, "agents"));
1167
- }
1168
- const roots = uniqueExistingDirs([
1169
- start,
1170
- ...commonSearchRoots(home)
1171
- ]);
1172
- for (const root of roots) {
1173
- for (const manifest of findManifestFiles(root, { maxDepth, maxDirs, limit: 250 })) {
1174
- const workspace = nearestAgentsAncestor(manifest, root);
1175
- addCandidate(workspace || path6.dirname(manifest));
1176
- if (candidates.length >= limit) {
1177
- return candidates.slice(0, limit);
1178
- }
1179
- }
1180
- }
1181
- return candidates.slice(0, limit);
1182
- }
1183
- function findAgentFolders(root, {
1184
- cwd = process.cwd(),
1185
- maxDepth = 6,
1186
- maxDirs = 1e3,
1187
- limit = 100
1188
- } = {}) {
1189
- const start = path6.resolve(cwd);
1190
- const resolvedRoot = safeResolve(root || ".");
1191
- if (!resolvedRoot) {
1192
- return [];
1193
- }
1194
- const seen = /* @__PURE__ */ new Set();
1195
- const folders = [];
1196
- for (const manifest of findManifestFiles(resolvedRoot, { maxDepth, maxDirs, limit })) {
1197
- const folder = path6.dirname(manifest);
1198
- const resolved = safeResolve(folder);
1199
- if (!resolved || seen.has(resolved)) {
1200
- continue;
1201
- }
1202
- seen.add(resolved);
1203
- folders.push({
1204
- path: displayPath(resolved, start),
1205
- absolutePath: resolved,
1206
- name: path6.basename(resolved) || displayPath(resolved, start)
1207
- });
1208
- }
1209
- return folders.sort((a, b) => a.path.localeCompare(b.path)).slice(0, limit);
1210
- }
1211
- function countAgentManifests(root, { maxDepth = 6, maxDirs = 1e3, limit = 100 } = {}) {
1212
- let count = 0;
1213
- for (const _manifest of findManifestFiles(root, { maxDepth, maxDirs, limit })) {
1214
- count += 1;
1215
- if (count >= limit) {
1216
- return count;
1286
+ args,
1287
+ env: {
1288
+ ...env,
1289
+ ...packageIndexEnv(config, env, platform)
1217
1290
  }
1218
- }
1219
- return count;
1291
+ };
1220
1292
  }
1221
- function findManifestFiles(root, { maxDepth, maxDirs, limit }) {
1222
- const manifests = [];
1223
- const stack = [{ dir: root, depth: 0 }];
1224
- const seen = /* @__PURE__ */ new Set();
1225
- while (stack.length && manifests.length < limit && seen.size < maxDirs) {
1226
- const { dir, depth } = stack.pop();
1227
- const resolved = safeResolve(dir);
1228
- if (!resolved || seen.has(resolved)) {
1229
- continue;
1230
- }
1231
- seen.add(resolved);
1232
- let entries;
1233
- try {
1234
- entries = fs9.readdirSync(resolved, { withFileTypes: true });
1235
- } catch {
1236
- continue;
1237
- }
1238
- for (const entry of entries) {
1239
- if (entry.isFile() && MANIFEST_NAMES.has(entry.name)) {
1240
- manifests.push(path6.join(resolved, entry.name));
1241
- if (manifests.length >= limit) {
1242
- break;
1243
- }
1244
- }
1245
- }
1246
- if (depth >= maxDepth) {
1247
- continue;
1248
- }
1249
- for (const entry of entries) {
1250
- if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) {
1251
- continue;
1252
- }
1253
- stack.push({ dir: path6.join(resolved, entry.name), depth: depth + 1 });
1254
- }
1255
- }
1256
- return manifests;
1293
+ function commandNeedsPackageAccess(args = []) {
1294
+ return args[0] === "cloud" && args[1] === "local" && ["serve", "run"].includes(args[2]);
1257
1295
  }
1258
- function nearestAgentsAncestor(manifestPath, root) {
1259
- let current = path6.dirname(manifestPath);
1260
- const stop = path6.resolve(root);
1261
- while (current.startsWith(stop)) {
1262
- if (path6.basename(current).toLowerCase() === "agents") {
1263
- return current;
1264
- }
1265
- const parent = path6.dirname(current);
1266
- if (parent === current) {
1267
- break;
1268
- }
1269
- current = parent;
1296
+ function packageIndexEnv(config = {}, env = process.env, platform = process.platform) {
1297
+ const registry = registryConfig(config, env, platform);
1298
+ const indexes = buildIndexUrls(registry);
1299
+ if (!indexes) {
1300
+ return {};
1270
1301
  }
1271
- return null;
1302
+ const extraIndexUrls = joinIndexUrls([
1303
+ indexes.runtimeIndexUrl,
1304
+ "https://pypi.org/simple",
1305
+ env.VENDIAN_PIP_EXTRA_INDEX_URL
1306
+ ]);
1307
+ const uvExtraIndexUrls = joinIndexUrls([
1308
+ indexes.runtimeIndexUrl,
1309
+ "https://pypi.org/simple",
1310
+ env.VENDIAN_UV_EXTRA_INDEX_URL
1311
+ ]);
1312
+ return {
1313
+ VENDIAN_PIP_INDEX_URL: indexes.sdkIndexUrl,
1314
+ VENDIAN_PIP_EXTRA_INDEX_URL: extraIndexUrls,
1315
+ VENDIAN_UV_INDEX_URL: indexes.sdkIndexUrl,
1316
+ VENDIAN_UV_EXTRA_INDEX_URL: uvExtraIndexUrls
1317
+ };
1272
1318
  }
1273
- function hasDirectManifest(root) {
1274
- return [...MANIFEST_NAMES].some((name) => fs9.existsSync(path6.join(root, name)));
1319
+ function joinIndexUrls(urls) {
1320
+ return urls.flatMap((value) => String(value || "").split(/\s+/)).map((value) => value.trim()).filter(Boolean).filter((value, index, all) => all.indexOf(value) === index).join(" ");
1275
1321
  }
1276
- function displayPath(target, cwd) {
1277
- const relative = path6.relative(cwd, target);
1278
- if (!relative) {
1279
- return ".";
1322
+ function maybeAutoUpdateManagedEnv({
1323
+ env = process.env,
1324
+ platform = process.platform,
1325
+ venvPath = managedVenvPath(env, platform),
1326
+ force = false,
1327
+ log = true,
1328
+ load = loadConfig,
1329
+ save = saveConfig,
1330
+ installPackages = installVendianPackages,
1331
+ refreshDocs = refreshAgentDocsWorkspaces,
1332
+ now = Date.now(),
1333
+ onProgress = null
1334
+ } = {}) {
1335
+ if (env.VENDIAN_SKIP_AUTO_UPDATE === "1") {
1336
+ return false;
1280
1337
  }
1281
- if (relative.startsWith("..") || path6.isAbsolute(relative)) {
1282
- return target;
1338
+ const config = load(env, platform);
1339
+ const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
1340
+ if (!force && Number.isFinite(lastUpdate) && now - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
1341
+ return false;
1283
1342
  }
1284
- return relative.startsWith(".") ? relative : `.${path6.sep}${relative}`;
1285
- }
1286
- function commonSearchRoots(home) {
1287
- if (!home) {
1288
- return [];
1343
+ const registry = registryConfig(config, env, platform);
1344
+ if (!registry.token) {
1345
+ return false;
1289
1346
  }
1290
- return [
1291
- "agents",
1292
- "Projects",
1293
- "Code",
1294
- "Developer",
1295
- "Documents",
1296
- "Desktop"
1297
- ].map((name) => path6.join(home, name));
1298
- }
1299
- function uniqueExistingDirs(paths) {
1300
- const result = [];
1301
- const seen = /* @__PURE__ */ new Set();
1302
- for (const item of paths) {
1303
- const resolved = safeResolve(item);
1304
- if (!resolved || seen.has(resolved)) {
1305
- continue;
1347
+ const pythonPath = venvPython(venvPath, platform);
1348
+ if (!fs9.existsSync(pythonPath)) {
1349
+ return false;
1350
+ }
1351
+ try {
1352
+ if (log) {
1353
+ console.error("[vendian] Checking managed CLI/runtime updates...");
1306
1354
  }
1307
- try {
1308
- if (!fs9.statSync(resolved).isDirectory()) {
1309
- continue;
1310
- }
1311
- } catch {
1312
- continue;
1355
+ reportProgress(onProgress, "Installing managed CLI/runtime updates");
1356
+ installPackages({ pythonPath, venvPath, config, env, platform });
1357
+ const next = { ...config, lastManagedUpdateAt: new Date(now).toISOString() };
1358
+ save(next, env, platform);
1359
+ reportProgress(onProgress, "Refreshing generated agent docs");
1360
+ refreshDocs({ config: next, venvPath, env, platform });
1361
+ reportProgress(onProgress, "Managed CLI/runtime ready");
1362
+ return true;
1363
+ } catch (error) {
1364
+ const message = error && typeof error.message === "string" ? error.message : String(error);
1365
+ if (log) {
1366
+ console.error(`[vendian] Update check failed; continuing with installed CLI. ${message}`);
1313
1367
  }
1314
- seen.add(resolved);
1315
- result.push(resolved);
1368
+ return false;
1316
1369
  }
1317
- return result;
1318
1370
  }
1319
- function parentDirs(start, limit) {
1320
- const parents = [];
1321
- let current = path6.resolve(start);
1322
- for (let index = 0; index < limit; index += 1) {
1323
- const parent = path6.dirname(current);
1324
- if (!parent || parent === current) {
1325
- break;
1326
- }
1327
- parents.push(parent);
1328
- current = parent;
1371
+ function reportProgress(onProgress, message) {
1372
+ if (typeof onProgress === "function") {
1373
+ onProgress(message);
1329
1374
  }
1330
- return parents;
1331
1375
  }
1332
- function safeResolve(candidate) {
1376
+
1377
+ // src/workspaces.js
1378
+ async function listCloudWorkspaces({
1379
+ env = process.env,
1380
+ platform = process.platform,
1381
+ prepareInvocation = preparePythonVendianInvocation,
1382
+ run: run2 = runCapture,
1383
+ onProgress = null,
1384
+ timeoutMs = 2e4
1385
+ } = {}) {
1386
+ const progress = typeof onProgress === "function" ? onProgress : () => {
1387
+ };
1388
+ progress("Checking managed Python runtime");
1389
+ const invocation = await prepareInvocation(["cloud", "collections", "list", "--json"], {
1390
+ env,
1391
+ platform,
1392
+ onProgress: progress,
1393
+ skipAutoUpdate: true
1394
+ });
1395
+ progress("Loading workspaces from Vendian");
1396
+ const result = run2(invocation.command, invocation.args, { env: invocation.env, timeout: timeoutMs });
1397
+ if (!result.ok) {
1398
+ return {
1399
+ ok: false,
1400
+ workspaces: [],
1401
+ error: (result.stderr || result.stdout || `workspace list exited with code ${result.status}`).trim()
1402
+ };
1403
+ }
1333
1404
  try {
1334
- return path6.resolve(candidate);
1335
- } catch {
1336
- return null;
1405
+ return { ok: true, workspaces: normalizeWorkspaceList(JSON.parse(result.stdout)), error: "" };
1406
+ } catch (error) {
1407
+ const message = error && typeof error.message === "string" ? error.message : String(error);
1408
+ return { ok: false, workspaces: [], error: `Could not parse workspace list: ${message}` };
1337
1409
  }
1338
1410
  }
1411
+ function normalizeWorkspaceList(payload) {
1412
+ const data = payload && typeof payload === "object" && "data" in payload ? payload.data : payload;
1413
+ const raw = Array.isArray(data) ? data : data && typeof data === "object" && Array.isArray(data.data) ? data.data : data && typeof data === "object" && Array.isArray(data.collections) ? data.collections : [];
1414
+ return raw.filter((item) => item && typeof item === "object").map((item) => ({
1415
+ id: stringValue(item.id),
1416
+ name: stringValue(item.name || item.slug || item.id || "Unnamed workspace"),
1417
+ slug: stringValue(item.slug)
1418
+ })).filter((item) => item.id);
1419
+ }
1420
+ function workspaceLabel(workspace) {
1421
+ const name = workspace?.name || workspace?.slug || workspace?.id || "Unnamed workspace";
1422
+ const slug = workspace?.slug && workspace.slug !== name ? ` (${workspace.slug})` : "";
1423
+ return `${name}${slug}`;
1424
+ }
1425
+ function stringValue(value) {
1426
+ return value == null ? "" : String(value).trim();
1427
+ }
1339
1428
 
1340
1429
  // src/serve-events.js
1341
1430
  function initialServeState() {
@@ -1392,14 +1481,14 @@ function applyServeEvent(state, event) {
1392
1481
  logs: appendLog(state.logs, event)
1393
1482
  };
1394
1483
  if (event.type === "workspace_resolved") {
1395
- return { ...next, collectionId: stringValue(event.collectionId), activity: "Workspace resolved" };
1484
+ return { ...next, collectionId: stringValue2(event.collectionId), activity: "Workspace resolved" };
1396
1485
  }
1397
1486
  if (event.type === "daemon_registered") {
1398
1487
  return {
1399
1488
  ...next,
1400
1489
  connected: true,
1401
- daemonId: stringValue(event.daemonId),
1402
- cliSessionId: stringValue(event.cliSessionId),
1490
+ daemonId: stringValue2(event.daemonId),
1491
+ cliSessionId: stringValue2(event.cliSessionId),
1403
1492
  activity: "Daemon registered"
1404
1493
  };
1405
1494
  }
@@ -1419,7 +1508,7 @@ function applyServeEvent(state, event) {
1419
1508
  };
1420
1509
  }
1421
1510
  if (event.type === "daemon_wake_received") {
1422
- const sourceType = stringValue(event.sourceEventType || "backend wake");
1511
+ const sourceType = stringValue2(event.sourceEventType || "backend wake");
1423
1512
  return {
1424
1513
  ...next,
1425
1514
  activity: `Wake received: ${sourceType}`
@@ -1434,17 +1523,17 @@ function applyServeEvent(state, event) {
1434
1523
  if (event.type === "dispatch_job_received") {
1435
1524
  return {
1436
1525
  ...next,
1437
- activity: `Dispatch received for run ${stringValue(event.runId || event.sessionId || "unknown")}`
1526
+ activity: `Dispatch received for run ${stringValue2(event.runId || event.sessionId || "unknown")}`
1438
1527
  };
1439
1528
  }
1440
1529
  if (event.type === "dispatch_job_acked") {
1441
1530
  return {
1442
1531
  ...next,
1443
- activity: `Dispatch acknowledged for run ${stringValue(event.runId || event.sessionId || "unknown")}`
1532
+ activity: `Dispatch acknowledged for run ${stringValue2(event.runId || event.sessionId || "unknown")}`
1444
1533
  };
1445
1534
  }
1446
1535
  if (event.type === "daemon_stderr") {
1447
- const message = stringValue(event.message || event.stderr || "Daemon wrote to stderr");
1536
+ const message = stringValue2(event.message || event.stderr || "Daemon wrote to stderr");
1448
1537
  return {
1449
1538
  ...next,
1450
1539
  activity: `Daemon stderr: ${message}`,
@@ -1489,14 +1578,14 @@ function applyServeEvent(state, event) {
1489
1578
  if (event.type === "agent_prepare_progress") {
1490
1579
  const relativePath = agentLabel(event);
1491
1580
  const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
1492
- const message = stringValue(event.message || event.stage || "Preparing agent");
1581
+ const message = stringValue2(event.message || event.stage || "Preparing agent");
1493
1582
  return {
1494
1583
  ...next,
1495
1584
  agentLogs: appendAgentLog(state.agentLogs, event),
1496
1585
  agentRunState: setAgentRunState(state.agentRunState, relativePath, {
1497
1586
  status: "preparing",
1498
1587
  lastEventAt: timestamp,
1499
- progressStage: stringValue(event.stage),
1588
+ progressStage: stringValue2(event.stage),
1500
1589
  progressMessage: message
1501
1590
  }),
1502
1591
  activity: `${relativePath}: ${message}`
@@ -1508,11 +1597,11 @@ function applyServeEvent(state, event) {
1508
1597
  const runState = isError ? setAgentRunState(state.agentRunState, agentLabel(event), {
1509
1598
  status: "error",
1510
1599
  lastEventAt: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1511
- errorMessage: stringValue(event.error || event.errorMessage || "Agent setup failed")
1600
+ errorMessage: stringValue2(event.error || event.errorMessage || "Agent setup failed")
1512
1601
  }) : isDisabled ? setAgentRunState(state.agentRunState, agentLabel(event), {
1513
1602
  status: "disabled",
1514
1603
  lastEventAt: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1515
- disabledReason: stringValue(event.disabledReason || event.warning || "System dependency not met"),
1604
+ disabledReason: stringValue2(event.disabledReason || event.warning || "System dependency not met"),
1516
1605
  resolutionHints: Array.isArray(event.resolutionHints) ? event.resolutionHints : []
1517
1606
  }) : setAgentRunState(state.agentRunState, agentLabel(event), {
1518
1607
  status: "ready",
@@ -1548,8 +1637,8 @@ function applyServeEvent(state, event) {
1548
1637
  agentLogs: appendAgentLog(state.agentLogs, event),
1549
1638
  agentRunState: setAgentRunState(state.agentRunState, relativePath, {
1550
1639
  status: "running",
1551
- runId: stringValue(event.runId || event.deployRequestId),
1552
- jobType: stringValue(event.jobType || "run"),
1640
+ runId: stringValue2(event.runId || event.deployRequestId),
1641
+ jobType: stringValue2(event.jobType || "run"),
1553
1642
  startedAt: timestamp,
1554
1643
  completedAt: null,
1555
1644
  lastEventAt: timestamp,
@@ -1563,7 +1652,7 @@ function applyServeEvent(state, event) {
1563
1652
  const relativePath = agentLabel(event);
1564
1653
  const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
1565
1654
  const success = event.success !== false;
1566
- const runId = stringValue(event.runId || event.deployRequestId);
1655
+ const runId = stringValue2(event.runId || event.deployRequestId);
1567
1656
  const previous = (state.agentRunState || {})[relativePath];
1568
1657
  const effectiveSuccess = success && !(previous?.status === "error" && previous?.runId === runId);
1569
1658
  return {
@@ -1572,10 +1661,10 @@ function applyServeEvent(state, event) {
1572
1661
  agentRunState: setAgentRunState(state.agentRunState, relativePath, {
1573
1662
  status: effectiveSuccess ? "completed" : "error",
1574
1663
  runId,
1575
- jobType: stringValue(event.jobType || "run"),
1664
+ jobType: stringValue2(event.jobType || "run"),
1576
1665
  completedAt: timestamp,
1577
1666
  lastEventAt: timestamp,
1578
- errorMessage: effectiveSuccess ? null : previous?.errorMessage || stringValue(event.error || "Job failed")
1667
+ errorMessage: effectiveSuccess ? null : previous?.errorMessage || stringValue2(event.error || "Job failed")
1579
1668
  }),
1580
1669
  jobsRun: state.jobsRun + 1,
1581
1670
  currentJob: null,
@@ -1583,13 +1672,13 @@ function applyServeEvent(state, event) {
1583
1672
  };
1584
1673
  }
1585
1674
  if (event.type === "backend_connection_lost") {
1586
- return { ...next, connected: false, activity: `Connection lost during ${stringValue(event.activity)}`, retry: event };
1675
+ return { ...next, connected: false, activity: `Connection lost during ${stringValue2(event.activity)}`, retry: event };
1587
1676
  }
1588
1677
  if (event.type === "backend_connection_restored") {
1589
1678
  return { ...next, connected: true, activity: "Backend connection restored", retry: null };
1590
1679
  }
1591
1680
  if (event.type === "retry_scheduled") {
1592
- return { ...next, retry: event, activity: `Retrying ${stringValue(event.activity)} in ${formatSeconds(event.delaySeconds)}` };
1681
+ return { ...next, retry: event, activity: `Retrying ${stringValue2(event.activity)} in ${formatSeconds(event.delaySeconds)}` };
1593
1682
  }
1594
1683
  if (event.type === "error") {
1595
1684
  const relativePath = event.relativePath || event.path ? agentLabel(event) : "";
@@ -1600,11 +1689,11 @@ function applyServeEvent(state, event) {
1600
1689
  agentLogs: hasAgentScope ? appendAgentLog(state.agentLogs, event) : state.agentLogs,
1601
1690
  agentRunState: hasAgentScope ? setAgentRunState(state.agentRunState, relativePath, {
1602
1691
  status: "error",
1603
- runId: stringValue(event.runId || event.deployRequestId),
1604
- jobType: stringValue(event.jobType || "run"),
1692
+ runId: stringValue2(event.runId || event.deployRequestId),
1693
+ jobType: stringValue2(event.jobType || "run"),
1605
1694
  completedAt: timestamp,
1606
1695
  lastEventAt: timestamp,
1607
- errorMessage: stringValue(event.error || event.code || "Unknown error")
1696
+ errorMessage: stringValue2(event.error || event.code || "Unknown error")
1608
1697
  }) : state.agentRunState,
1609
1698
  activity: "Error",
1610
1699
  errors: appendError(state.errors, jobLabel(event), event.error || event.code || "Unknown error")
@@ -1622,8 +1711,8 @@ function applyServeEvent(state, event) {
1622
1711
  }
1623
1712
  if (event.type === "process_exit" || event.type === "daemon_exit") {
1624
1713
  const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
1625
- const message = stringValue(event.message || processExitMessage(event));
1626
- const relativePath = stringValue(event.relativePath || "");
1714
+ const message = stringValue2(event.message || processExitMessage(event));
1715
+ const relativePath = stringValue2(event.relativePath || "");
1627
1716
  const runState = failRunningAgents(state.agentRunState, message, timestamp, relativePath);
1628
1717
  return {
1629
1718
  ...next,
@@ -1678,7 +1767,7 @@ function mergeAgentLogRecords(agentLogs, records = []) {
1678
1767
  let next = agentLogs || {};
1679
1768
  for (const record of records) {
1680
1769
  if (!record || typeof record !== "object") continue;
1681
- const relativePath = stringValue(record.relativePath || ".");
1770
+ const relativePath = stringValue2(record.relativePath || ".");
1682
1771
  const entry = record.entry && typeof record.entry === "object" ? record.entry : record;
1683
1772
  const existing = next[relativePath] || [];
1684
1773
  next = {
@@ -1693,13 +1782,13 @@ function agentRunStateFromLogs(agentLogs, { includeRunning = true } = {}) {
1693
1782
  for (const [relativePath, entries] of Object.entries(agentLogs || {})) {
1694
1783
  for (const entry of entries || []) {
1695
1784
  const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
1696
- const runId = stringValue(entry.runId);
1785
+ const runId = stringValue2(entry.runId);
1697
1786
  const current = next[relativePath] || {};
1698
1787
  if (entry.eventType === "job_started") {
1699
1788
  next = setAgentRunState(next, relativePath, {
1700
1789
  status: includeRunning ? "running" : current.status,
1701
1790
  runId,
1702
- jobType: entry.jobType ? stringValue(entry.jobType) : current.jobType,
1791
+ jobType: entry.jobType ? stringValue2(entry.jobType) : current.jobType,
1703
1792
  startedAt: timestamp,
1704
1793
  lastEventAt: timestamp
1705
1794
  });
@@ -1710,10 +1799,10 @@ function agentRunStateFromLogs(agentLogs, { includeRunning = true } = {}) {
1710
1799
  next = setAgentRunState(next, relativePath, {
1711
1800
  status: failed ? "error" : "completed",
1712
1801
  runId,
1713
- jobType: entry.jobType ? stringValue(entry.jobType) : current.jobType,
1802
+ jobType: entry.jobType ? stringValue2(entry.jobType) : current.jobType,
1714
1803
  completedAt: timestamp,
1715
1804
  lastEventAt: timestamp,
1716
- errorMessage: failed ? formatErrorMessage(entry.error) || current.errorMessage || stringValue(entry.message || "Job failed") : null
1805
+ errorMessage: failed ? formatErrorMessage(entry.error) || current.errorMessage || stringValue2(entry.message || "Job failed") : null
1717
1806
  });
1718
1807
  continue;
1719
1808
  }
@@ -1721,10 +1810,10 @@ function agentRunStateFromLogs(agentLogs, { includeRunning = true } = {}) {
1721
1810
  next = setAgentRunState(next, relativePath, {
1722
1811
  status: "error",
1723
1812
  runId,
1724
- jobType: entry.jobType ? stringValue(entry.jobType) : current.jobType,
1813
+ jobType: entry.jobType ? stringValue2(entry.jobType) : current.jobType,
1725
1814
  completedAt: timestamp,
1726
1815
  lastEventAt: timestamp,
1727
- errorMessage: formatErrorMessage(entry.error) || stringValue(entry.message || "Job failed")
1816
+ errorMessage: formatErrorMessage(entry.error) || stringValue2(entry.message || "Job failed")
1728
1817
  });
1729
1818
  continue;
1730
1819
  }
@@ -1742,34 +1831,34 @@ function serveEventAgentLogEntry(event) {
1742
1831
  if (!event || typeof event !== "object") {
1743
1832
  return null;
1744
1833
  }
1745
- const type = stringValue(event.type);
1746
- const relativePath = stringValue(event.relativePath || event.path || ".");
1834
+ const type = stringValue2(event.type);
1835
+ const relativePath = stringValue2(event.relativePath || event.path || ".");
1747
1836
  const timestamp = event.timestamp || (/* @__PURE__ */ new Date()).toISOString();
1748
1837
  if (type === "run_log") {
1749
1838
  return {
1750
1839
  relativePath,
1751
1840
  entry: {
1752
1841
  timestamp,
1753
- runId: stringValue(event.runId),
1754
- eventType: stringValue(event.eventType || "log"),
1755
- level: stringValue(event.level || "info"),
1756
- message: stringValue(event.message),
1757
- stepId: event.stepId ? stringValue(event.stepId) : null,
1842
+ runId: stringValue2(event.runId),
1843
+ eventType: stringValue2(event.eventType || "log"),
1844
+ level: stringValue2(event.level || "info"),
1845
+ message: stringValue2(event.message),
1846
+ stepId: event.stepId ? stringValue2(event.stepId) : null,
1758
1847
  current: event.current ?? null,
1759
1848
  total: event.total ?? null,
1760
1849
  success: event.success ?? null,
1761
1850
  error: event.error ?? null,
1762
- jobType: event.jobType ? stringValue(event.jobType) : null
1851
+ jobType: event.jobType ? stringValue2(event.jobType) : null
1763
1852
  }
1764
1853
  };
1765
1854
  }
1766
1855
  if (type === "job_started") {
1767
- const jobType = stringValue(event.jobType || "run");
1856
+ const jobType = stringValue2(event.jobType || "run");
1768
1857
  return {
1769
1858
  relativePath,
1770
1859
  entry: {
1771
1860
  timestamp,
1772
- runId: stringValue(event.runId || event.deployRequestId),
1861
+ runId: stringValue2(event.runId || event.deployRequestId),
1773
1862
  eventType: "job_started",
1774
1863
  level: "info",
1775
1864
  message: `Started ${jobType}`,
@@ -1784,15 +1873,15 @@ function serveEventAgentLogEntry(event) {
1784
1873
  }
1785
1874
  if (type === "job_completed") {
1786
1875
  const success = event.success !== false;
1787
- const jobType = stringValue(event.jobType || "run");
1876
+ const jobType = stringValue2(event.jobType || "run");
1788
1877
  return {
1789
1878
  relativePath,
1790
1879
  entry: {
1791
1880
  timestamp,
1792
- runId: stringValue(event.runId || event.deployRequestId),
1881
+ runId: stringValue2(event.runId || event.deployRequestId),
1793
1882
  eventType: "job_completed",
1794
1883
  level: success ? "info" : "error",
1795
- message: success ? "Completed successfully" : stringValue(event.error || "Failed"),
1884
+ message: success ? "Completed successfully" : stringValue2(event.error || "Failed"),
1796
1885
  stepId: null,
1797
1886
  current: null,
1798
1887
  total: null,
@@ -1807,16 +1896,16 @@ function serveEventAgentLogEntry(event) {
1807
1896
  relativePath,
1808
1897
  entry: {
1809
1898
  timestamp,
1810
- runId: stringValue(event.runId || event.deployRequestId),
1899
+ runId: stringValue2(event.runId || event.deployRequestId),
1811
1900
  eventType: "error",
1812
1901
  level: "error",
1813
- message: stringValue(event.error || event.code || "Unknown error"),
1902
+ message: stringValue2(event.error || event.code || "Unknown error"),
1814
1903
  stepId: null,
1815
1904
  current: null,
1816
1905
  total: null,
1817
1906
  success: false,
1818
1907
  error: event.error ?? event.code ?? null,
1819
- jobType: event.jobType ? stringValue(event.jobType) : null
1908
+ jobType: event.jobType ? stringValue2(event.jobType) : null
1820
1909
  }
1821
1910
  };
1822
1911
  }
@@ -1830,7 +1919,7 @@ function serveEventAgentLogEntry(event) {
1830
1919
  eventType: type,
1831
1920
  level: event.status === "error" ? "error" : "info",
1832
1921
  message: agentPrepareLogMessage(event),
1833
- stepId: stringValue(event.stage || type),
1922
+ stepId: stringValue2(event.stage || type),
1834
1923
  current: null,
1835
1924
  total: null,
1836
1925
  success,
@@ -1840,12 +1929,12 @@ function serveEventAgentLogEntry(event) {
1840
1929
  };
1841
1930
  }
1842
1931
  if (type === "process_exit" || type === "daemon_exit") {
1843
- const message = stringValue(event.message || processExitMessage(event));
1932
+ const message = stringValue2(event.message || processExitMessage(event));
1844
1933
  return {
1845
1934
  relativePath,
1846
1935
  entry: {
1847
1936
  timestamp,
1848
- runId: stringValue(event.runId || event.sessionId || "daemon"),
1937
+ runId: stringValue2(event.runId || event.sessionId || "daemon"),
1849
1938
  eventType: type,
1850
1939
  level: "error",
1851
1940
  message,
@@ -1859,35 +1948,65 @@ function serveEventAgentLogEntry(event) {
1859
1948
  signal: event.signal ?? null,
1860
1949
  stderrTail: event.stderrTail ?? null
1861
1950
  },
1862
- jobType: event.jobType ? stringValue(event.jobType) : null
1951
+ jobType: event.jobType ? stringValue2(event.jobType) : null
1863
1952
  }
1864
1953
  };
1865
1954
  }
1866
1955
  return null;
1867
1956
  }
1957
+ function agentRuntimeStatus(agent, agentRunState = {}) {
1958
+ const path10 = stringValue2(agent?.relativePath || ".");
1959
+ const run2 = (agentRunState || {})[path10];
1960
+ const inventoryStatus = stringValue2(agent?.status);
1961
+ if (run2?.status === "running") {
1962
+ return { status: "running", label: "running", run: run2 };
1963
+ }
1964
+ if (run2?.status === "preparing") {
1965
+ return { status: "preparing", label: "preparing", run: run2 };
1966
+ }
1967
+ if (run2?.status === "error" || inventoryStatus === "error") {
1968
+ return { status: "error", label: "error", run: run2 };
1969
+ }
1970
+ if (run2?.status === "disabled" || inventoryStatus === "disabled") {
1971
+ return {
1972
+ status: "disabled",
1973
+ label: "disabled",
1974
+ run: run2,
1975
+ disabledReason: run2?.disabledReason || agent?.disabledReason || "System dependency not met locally",
1976
+ resolutionHints: run2?.resolutionHints || agent?.resolutionHints || []
1977
+ };
1978
+ }
1979
+ if (inventoryStatus === "online") {
1980
+ return { status: run2?.status === "completed" ? "completed" : "ready", label: "ready", run: run2 };
1981
+ }
1982
+ if (run2?.status === "completed") {
1983
+ return { status: "completed", label: "ready", run: run2 };
1984
+ }
1985
+ return { status: inventoryStatus || "unknown", label: inventoryStatus || "unknown", run: run2 };
1986
+ }
1868
1987
  function appendError(errors, scope, message) {
1869
- return [...errors, { scope, message: stringValue(message) }].slice(-20);
1988
+ return [...errors, { scope, message: stringValue2(message) }].slice(-20);
1870
1989
  }
1871
1990
  function serveDebugEntry(event) {
1872
1991
  if (!event || typeof event !== "object") return null;
1873
- const type = stringValue(event.type);
1992
+ const type = stringValue2(event.type);
1874
1993
  const timestamp = event.timestamp || event.occurredAt || (/* @__PURE__ */ new Date()).toISOString();
1875
1994
  const base = { timestamp, eventType: type, level: debugLevel(event) };
1876
1995
  switch (type) {
1877
1996
  case "workspace_resolved":
1878
- return { ...base, message: `workspace resolved collection=${stringValue(event.collectionId || "unknown")}` };
1997
+ return { ...base, message: `workspace resolved collection=${stringValue2(event.collectionId || "unknown")}` };
1879
1998
  case "daemon_registered":
1880
1999
  return { ...base, message: `daemon registered daemon=${shortId(event.daemonId)} cli=${shortId(event.cliSessionId)}` };
1881
2000
  case "daemon_event_stream_connected":
1882
2001
  return { ...base, message: `daemon event stream connected daemon=${shortId(event.daemonId)}` };
1883
2002
  case "daemon_event_stream_lost":
1884
- return { ...base, level: "warn", message: `daemon event stream lost retry=${formatSeconds(event.retryInSeconds)} error=${stringValue(event.error || "unknown")}` };
2003
+ return { ...base, level: "warn", message: `daemon event stream lost retry=${formatSeconds(event.retryInSeconds)} error=${stringValue2(event.error || "unknown")}` };
1885
2004
  case "daemon_wake_received":
1886
2005
  return {
1887
2006
  ...base,
1888
2007
  message: [
1889
- `wake received source=${stringValue(event.sourceEventType || "backend")}`,
1890
- event.eventType ? `event=${stringValue(event.eventType)}` : "",
2008
+ `wake received source=${stringValue2(event.sourceEventType || "backend")}`,
2009
+ event.eventType ? `event=${stringValue2(event.eventType)}` : "",
1891
2010
  event.sessionId ? `session=${shortId(event.sessionId)}` : "",
1892
2011
  event.runId ? `run=${shortId(event.runId)}` : "",
1893
2012
  event.localAgentRowId ? `agentRow=${shortId(event.localAgentRowId)}` : ""
@@ -1910,7 +2029,7 @@ function serveDebugEntry(event) {
1910
2029
  message: `dispatch acked delivery=${shortId(event.deliveryId)} run=${shortId(event.runId || event.sessionId)} attempt=${shortId(event.attemptId)}`
1911
2030
  };
1912
2031
  case "job_started":
1913
- return { ...base, message: `job started type=${stringValue(event.jobType || "run")} run=${shortId(event.runId || event.sessionId || event.deployRequestId || event.testRunId)}` };
2032
+ return { ...base, message: `job started type=${stringValue2(event.jobType || "run")} run=${shortId(event.runId || event.sessionId || event.deployRequestId || event.testRunId)}` };
1914
2033
  case "job_completed":
1915
2034
  return {
1916
2035
  ...base,
@@ -1918,18 +2037,18 @@ function serveDebugEntry(event) {
1918
2037
  message: `job completed success=${event.success !== false} run=${shortId(event.runId || event.sessionId || event.deployRequestId || event.testRunId)}${event.error ? ` error=${formatErrorMessage(event.error)}` : ""}`
1919
2038
  };
1920
2039
  case "backend_connection_lost":
1921
- return { ...base, level: "warn", message: `backend connection lost activity=${stringValue(event.activity)} error=${stringValue(event.error)}` };
2040
+ return { ...base, level: "warn", message: `backend connection lost activity=${stringValue2(event.activity)} error=${stringValue2(event.error)}` };
1922
2041
  case "backend_connection_restored":
1923
- return { ...base, message: `backend connection restored activity=${stringValue(event.activity)}` };
2042
+ return { ...base, message: `backend connection restored activity=${stringValue2(event.activity)}` };
1924
2043
  case "retry_scheduled":
1925
- return { ...base, level: "warn", message: `retry scheduled activity=${stringValue(event.activity)} delay=${formatSeconds(event.delaySeconds)}` };
2044
+ return { ...base, level: "warn", message: `retry scheduled activity=${stringValue2(event.activity)} delay=${formatSeconds(event.delaySeconds)}` };
1926
2045
  case "daemon_stderr":
1927
- return { ...base, level: looksLikeError(event.message || event.stderr) ? "error" : "warn", message: `stderr ${stringValue(event.message || event.stderr)}` };
2046
+ return { ...base, level: looksLikeError(event.message || event.stderr) ? "error" : "warn", message: `stderr ${stringValue2(event.message || event.stderr)}` };
1928
2047
  case "error":
1929
- return { ...base, level: "error", message: `error scope=${stringValue(event.relativePath || event.path || "daemon")} ${stringValue(event.error || event.code || "unknown")}` };
2048
+ return { ...base, level: "error", message: `error scope=${stringValue2(event.relativePath || event.path || "daemon")} ${stringValue2(event.error || event.code || "unknown")}` };
1930
2049
  case "process_exit":
1931
2050
  case "daemon_exit":
1932
- return { ...base, level: "error", message: stringValue(event.message || processExitMessage(event)) };
2051
+ return { ...base, level: "error", message: stringValue2(event.message || processExitMessage(event)) };
1933
2052
  case "stopped":
1934
2053
  return { ...base, message: `stopped jobsRun=${Number(event.jobsRun ?? 0)}` };
1935
2054
  default:
@@ -1937,7 +2056,7 @@ function serveDebugEntry(event) {
1937
2056
  }
1938
2057
  }
1939
2058
  function debugLevel(event) {
1940
- if (event?.level) return stringValue(event.level);
2059
+ if (event?.level) return stringValue2(event.level);
1941
2060
  if (event?.type === "error" || event?.type === "process_exit" || event?.type === "daemon_exit") return "error";
1942
2061
  return "info";
1943
2062
  }
@@ -1945,14 +2064,14 @@ function boolText(value) {
1945
2064
  return value ? "yes" : "no";
1946
2065
  }
1947
2066
  function shortId(value) {
1948
- const text = stringValue(value);
2067
+ const text = stringValue2(value);
1949
2068
  return text ? text.slice(0, 8) : "unknown";
1950
2069
  }
1951
2070
  function looksLikeError(value) {
1952
- return /(error|exception|traceback|failed|fatal|denied|unauthorized|timeout)/i.test(stringValue(value));
2071
+ return /(error|exception|traceback|failed|fatal|denied|unauthorized|timeout)/i.test(stringValue2(value));
1953
2072
  }
1954
2073
  function setAgentRunState(agentRunState, relativePath, patch) {
1955
- const key = stringValue(relativePath || ".");
2074
+ const key = stringValue2(relativePath || ".");
1956
2075
  return {
1957
2076
  ...agentRunState || {},
1958
2077
  [key]: {
@@ -1963,9 +2082,9 @@ function setAgentRunState(agentRunState, relativePath, patch) {
1963
2082
  }
1964
2083
  function failRunningAgents(agentRunState, message, timestamp, relativePath = "") {
1965
2084
  let next = agentRunState || {};
1966
- for (const [path9, run2] of Object.entries(agentRunState || {})) {
2085
+ for (const [path10, run2] of Object.entries(agentRunState || {})) {
1967
2086
  if (run2?.status !== "running") continue;
1968
- next = setAgentRunState(next, path9, {
2087
+ next = setAgentRunState(next, path10, {
1969
2088
  status: "error",
1970
2089
  completedAt: timestamp,
1971
2090
  lastEventAt: timestamp,
@@ -1987,40 +2106,40 @@ function appendProcessExitAgentLogs(agentLogs, event, agentRunState, relativePat
1987
2106
  if (relativePath) {
1988
2107
  paths.add(relativePath);
1989
2108
  }
1990
- for (const [path9, run2] of Object.entries(agentRunState || {})) {
2109
+ for (const [path10, run2] of Object.entries(agentRunState || {})) {
1991
2110
  if (run2?.status === "running") {
1992
- paths.add(path9);
2111
+ paths.add(path10);
1993
2112
  }
1994
2113
  }
1995
2114
  if (paths.size === 0) {
1996
2115
  return appendAgentLog(agentLogs, event);
1997
2116
  }
1998
2117
  let next = agentLogs || {};
1999
- for (const path9 of paths) {
2000
- next = appendAgentLog(next, { ...event, relativePath: path9 });
2118
+ for (const path10 of paths) {
2119
+ next = appendAgentLog(next, { ...event, relativePath: path10 });
2001
2120
  }
2002
2121
  return next;
2003
2122
  }
2004
2123
  function reconcileInventoryRunState(agentRunState, agents, timestamp) {
2005
2124
  let next = agentRunState || {};
2006
2125
  for (const agent of agents || []) {
2007
- const path9 = stringValue(agent?.relativePath || ".");
2008
- const current = next[path9];
2126
+ const path10 = stringValue2(agent?.relativePath || ".");
2127
+ const current = next[path10];
2009
2128
  if (agent?.status === "error") {
2010
- next = setAgentRunState(next, path9, {
2129
+ next = setAgentRunState(next, path10, {
2011
2130
  status: "error",
2012
2131
  lastEventAt: timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2013
- errorMessage: stringValue(agent.errorMessage || agent.error || "Agent setup failed")
2132
+ errorMessage: stringValue2(agent.errorMessage || agent.error || "Agent setup failed")
2014
2133
  });
2015
2134
  } else if (agent?.status === "disabled") {
2016
- next = setAgentRunState(next, path9, {
2135
+ next = setAgentRunState(next, path10, {
2017
2136
  status: "disabled",
2018
2137
  lastEventAt: timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2019
- disabledReason: stringValue(agent.disabledReason || "System dependency not met locally"),
2138
+ disabledReason: stringValue2(agent.disabledReason || "System dependency not met locally"),
2020
2139
  resolutionHints: Array.isArray(agent.resolutionHints) ? agent.resolutionHints : []
2021
2140
  });
2022
2141
  } else if (agent?.status === "online" && ["preparing", "error", "disabled"].includes(current?.status) && !current?.runId) {
2023
- next = setAgentRunState(next, path9, {
2142
+ next = setAgentRunState(next, path10, {
2024
2143
  status: "ready",
2025
2144
  lastEventAt: timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2026
2145
  progressMessage: null,
@@ -2036,22 +2155,22 @@ function reconcileInventoryRunState(agentRunState, agents, timestamp) {
2036
2155
  function normalizeStoredAgentLogEntry(entry) {
2037
2156
  return {
2038
2157
  timestamp: entry.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2039
- runId: stringValue(entry.runId),
2040
- eventType: stringValue(entry.eventType || "log"),
2041
- level: stringValue(entry.level || "info"),
2042
- message: stringValue(entry.message),
2043
- stepId: entry.stepId ? stringValue(entry.stepId) : null,
2158
+ runId: stringValue2(entry.runId),
2159
+ eventType: stringValue2(entry.eventType || "log"),
2160
+ level: stringValue2(entry.level || "info"),
2161
+ message: stringValue2(entry.message),
2162
+ stepId: entry.stepId ? stringValue2(entry.stepId) : null,
2044
2163
  current: entry.current ?? null,
2045
2164
  total: entry.total ?? null,
2046
2165
  success: entry.success ?? null,
2047
2166
  error: entry.error ?? null,
2048
- jobType: entry.jobType ? stringValue(entry.jobType) : null
2167
+ jobType: entry.jobType ? stringValue2(entry.jobType) : null
2049
2168
  };
2050
2169
  }
2051
2170
  function formatErrorMessage(error) {
2052
2171
  if (!error) return null;
2053
- if (typeof error === "object") return stringValue(error.message || "error");
2054
- return stringValue(error);
2172
+ if (typeof error === "object") return stringValue2(error.message || "error");
2173
+ return stringValue2(error);
2055
2174
  }
2056
2175
  function findNewAgents(previous, next) {
2057
2176
  const seen = new Set(previous.map(agentKey).filter(Boolean));
@@ -2064,15 +2183,15 @@ function agentKey(agent) {
2064
2183
  if (!agent || typeof agent !== "object") {
2065
2184
  return "";
2066
2185
  }
2067
- return stringValue(agent.localAgentId || agent.relativePath || agent.path || agent.manifestName || agent.id);
2186
+ return stringValue2(agent.localAgentId || agent.relativePath || agent.path || agent.manifestName || agent.id);
2068
2187
  }
2069
2188
  function agentLabel(event) {
2070
- return stringValue(event.relativePath || event.path || ".");
2189
+ return stringValue2(event.relativePath || event.path || ".");
2071
2190
  }
2072
2191
  function jobLabel(event) {
2073
- return stringValue(event.runId || event.deployRequestId || event.relativePath || event.jobType || "job");
2192
+ return stringValue2(event.runId || event.deployRequestId || event.relativePath || event.jobType || "job");
2074
2193
  }
2075
- function stringValue(value) {
2194
+ function stringValue2(value) {
2076
2195
  return value == null ? "" : String(value);
2077
2196
  }
2078
2197
  function processExitMessage(event) {
@@ -2085,54 +2204,23 @@ function processExitMessage(event) {
2085
2204
  return "Agent serve exited";
2086
2205
  }
2087
2206
  function agentPrepareLogMessage(event) {
2088
- if (event.type === "agent_prepare_started") {
2089
- return "Preparing agent";
2090
- }
2091
- if (event.type === "agent_prepare_completed") {
2092
- if (event.status === "error") return stringValue(event.error || event.errorMessage || "Agent setup failed");
2093
- if (event.status === "disabled") return stringValue(event.warning || event.disabledReason || "Agent disabled locally");
2094
- return "Agent ready";
2095
- }
2096
- return stringValue(event.message || event.stage || "Preparing agent");
2097
- }
2098
- function textTail(value, limit = 800) {
2099
- const text = String(value || "").trim().replace(/\s+/g, " ");
2100
- return text.length > limit ? text.slice(-limit) : text;
2101
- }
2102
- function formatSeconds(value) {
2103
- const numeric = Number(value);
2104
- return Number.isFinite(numeric) ? `${numeric.toFixed(1)}s` : "";
2105
- }
2106
-
2107
- // src/serve-process.js
2108
- import { spawn as spawn2 } from "node:child_process";
2109
- async function spawnLocalServeEventStream({
2110
- agentsDir = "./agents",
2111
- collectionId = "",
2112
- env = process.env,
2113
- platform = process.platform,
2114
- onProgress = null
2115
- } = {}) {
2116
- const invocation = await preparePythonVendianInvocation(buildLocalServeEventStreamArgs({ agentsDir, collectionId }), { env, platform, onProgress });
2117
- return spawn2(invocation.command, invocation.args, {
2118
- env: invocation.env,
2119
- stdio: ["ignore", "pipe", "pipe"],
2120
- shell: false
2121
- });
2122
- }
2123
- function buildLocalServeEventStreamArgs({ agentsDir = "./agents", collectionId = "" } = {}) {
2124
- const args = [
2125
- "cloud",
2126
- "local",
2127
- "serve",
2128
- "--agents-dir",
2129
- agentsDir || "./agents",
2130
- "--event-stream"
2131
- ];
2132
- if (collectionId) {
2133
- args.push("--collection-id", collectionId);
2207
+ if (event.type === "agent_prepare_started") {
2208
+ return "Preparing agent";
2134
2209
  }
2135
- return args;
2210
+ if (event.type === "agent_prepare_completed") {
2211
+ if (event.status === "error") return stringValue2(event.error || event.errorMessage || "Agent setup failed");
2212
+ if (event.status === "disabled") return stringValue2(event.warning || event.disabledReason || "Agent disabled locally");
2213
+ return "Agent ready";
2214
+ }
2215
+ return stringValue2(event.message || event.stage || "Preparing agent");
2216
+ }
2217
+ function textTail(value, limit = 800) {
2218
+ const text = String(value || "").trim().replace(/\s+/g, " ");
2219
+ return text.length > limit ? text.slice(-limit) : text;
2220
+ }
2221
+ function formatSeconds(value) {
2222
+ const numeric = Number(value);
2223
+ return Number.isFinite(numeric) ? `${numeric.toFixed(1)}s` : "";
2136
2224
  }
2137
2225
 
2138
2226
  // src/serve-log-store.js
@@ -2142,12 +2230,12 @@ import path7 from "node:path";
2142
2230
  var MAX_EVENTS = 5e3;
2143
2231
  var MAX_RUNS = 50;
2144
2232
  function createServeLogStore({
2145
- agentsDir = "./agents",
2146
- collectionId = "",
2233
+ agentsDir: agentsDir2 = "./agents",
2234
+ collectionId: collectionId2 = "",
2147
2235
  env = process.env,
2148
2236
  platform = process.platform
2149
2237
  } = {}) {
2150
- const file = serveLogFilePath({ agentsDir, collectionId, env, platform });
2238
+ const file = serveLogFilePath({ agentsDir: agentsDir2, collectionId: collectionId2, env, platform });
2151
2239
  let appendCount = 0;
2152
2240
  return {
2153
2241
  file,
@@ -2162,8 +2250,8 @@ function createServeLogStore({
2162
2250
  fs10.mkdirSync(path7.dirname(file), { recursive: true });
2163
2251
  const record = {
2164
2252
  version: 1,
2165
- collectionId: collectionId || "",
2166
- agentsDir: String(agentsDir || "./agents"),
2253
+ collectionId: collectionId2 || "",
2254
+ agentsDir: String(agentsDir2 || "./agents"),
2167
2255
  relativePath: normalized.relativePath,
2168
2256
  entry: normalized.entry
2169
2257
  };
@@ -2181,15 +2269,15 @@ function createServeLogStore({
2181
2269
  };
2182
2270
  }
2183
2271
  function serveLogFilePath({
2184
- agentsDir = "./agents",
2185
- collectionId = "",
2272
+ agentsDir: agentsDir2 = "./agents",
2273
+ collectionId: collectionId2 = "",
2186
2274
  env = process.env,
2187
2275
  platform = process.platform
2188
2276
  } = {}) {
2189
2277
  const root = vendianHome(env, platform);
2190
- const resolvedAgentsDir = path7.resolve(String(agentsDir || "./agents"));
2191
- const hash = crypto2.createHash("sha256").update(`${collectionId || "default"}\0${resolvedAgentsDir}`).digest("hex").slice(0, 16);
2192
- return path7.join(root, "serve-logs", `${safePathPart(collectionId || "default")}-${hash}.jsonl`);
2278
+ const resolvedAgentsDir = path7.resolve(String(agentsDir2 || "./agents"));
2279
+ const hash = crypto2.createHash("sha256").update(`${collectionId2 || "default"}\0${resolvedAgentsDir}`).digest("hex").slice(0, 16);
2280
+ return path7.join(root, "serve-logs", `${safePathPart(collectionId2 || "default")}-${hash}.jsonl`);
2193
2281
  }
2194
2282
  function readServeLogRecords(file) {
2195
2283
  if (!file || !fs10.existsSync(file)) {
@@ -2243,57 +2331,605 @@ function safePathPart(value) {
2243
2331
  return String(value || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "default";
2244
2332
  }
2245
2333
 
2246
- // src/workspaces.js
2247
- async function listCloudWorkspaces({
2334
+ // src/serve-process.js
2335
+ import { spawn as spawn2 } from "node:child_process";
2336
+ async function spawnLocalServeEventStream({
2337
+ agentsDir: agentsDir2 = "./agents",
2338
+ collectionId: collectionId2 = "",
2248
2339
  env = process.env,
2249
2340
  platform = process.platform,
2250
- prepareInvocation = preparePythonVendianInvocation,
2251
- run: run2 = runCapture,
2252
- onProgress = null,
2253
- timeoutMs = 2e4
2341
+ onProgress = null
2254
2342
  } = {}) {
2255
- const progress = typeof onProgress === "function" ? onProgress : () => {
2256
- };
2257
- progress("Checking managed Python runtime");
2258
- const invocation = await prepareInvocation(["cloud", "collections", "list", "--json"], {
2259
- env,
2260
- platform,
2261
- onProgress: progress,
2262
- skipAutoUpdate: true
2343
+ const invocation = await preparePythonVendianInvocation(buildLocalServeEventStreamArgs({ agentsDir: agentsDir2, collectionId: collectionId2 }), { env, platform, onProgress });
2344
+ return spawn2(invocation.command, invocation.args, {
2345
+ env: invocation.env,
2346
+ stdio: ["ignore", "pipe", "pipe"],
2347
+ shell: false
2263
2348
  });
2264
- progress("Loading workspaces from Vendian");
2265
- const result = run2(invocation.command, invocation.args, { env: invocation.env, timeout: timeoutMs });
2266
- if (!result.ok) {
2349
+ }
2350
+ function buildLocalServeEventStreamArgs({ agentsDir: agentsDir2 = "./agents", collectionId: collectionId2 = "" } = {}) {
2351
+ const args = [
2352
+ "cloud",
2353
+ "local",
2354
+ "serve",
2355
+ "--agents-dir",
2356
+ agentsDir2 || "./agents",
2357
+ "--event-stream"
2358
+ ];
2359
+ if (collectionId2) {
2360
+ args.push("--collection-id", collectionId2);
2361
+ }
2362
+ return args;
2363
+ }
2364
+
2365
+ // src/version.js
2366
+ var CLI_VERSION = true ? "0.0.36" : process.env.npm_package_version || "0.0.0-dev";
2367
+
2368
+ // src/dev-server.js
2369
+ var __dirname = path8.dirname(fileURLToPath(import.meta.url));
2370
+ var UI_DIR = fs11.existsSync(path8.join(__dirname, "dev-ui")) ? path8.join(__dirname, "dev-ui") : fs11.existsSync(path8.join(__dirname, "src", "dev-ui")) ? path8.join(__dirname, "src", "dev-ui") : path8.join(__dirname, "dev-ui");
2371
+ var serveChild = null;
2372
+ var serveState = initialServeState();
2373
+ var serveLogs = [];
2374
+ var logStore = null;
2375
+ var agentsDir = "";
2376
+ var collectionId = "";
2377
+ async function startDevServer({
2378
+ port = 3859,
2379
+ agents = "",
2380
+ noOpen = false,
2381
+ env = process.env,
2382
+ platform = process.platform
2383
+ } = {}) {
2384
+ if (agents) {
2385
+ agentsDir = path8.resolve(agents);
2386
+ } else {
2387
+ const candidates = findAgentDirectoryCandidates();
2388
+ agentsDir = candidates[0]?.absolutePath || path8.resolve("./agents");
2389
+ }
2390
+ const server = http2.createServer(handleRequest);
2391
+ server.listen(port, "127.0.0.1", () => {
2392
+ const url = `http://localhost:${port}`;
2393
+ console.log("");
2394
+ console.log(" \x1B[1;36m\u26A1 Vendian Dev UI\x1B[0m");
2395
+ console.log(` \x1B[90m${"\u2500".repeat(44)}\x1B[0m`);
2396
+ console.log(` \x1B[1mLocal:\x1B[0m ${url}`);
2397
+ console.log(` \x1B[1mAgents:\x1B[0m ${agentsDir}`);
2398
+ console.log(` \x1B[1mVersion:\x1B[0m ${CLI_VERSION}`);
2399
+ console.log(` \x1B[90m${"\u2500".repeat(44)}\x1B[0m`);
2400
+ console.log(" \x1B[90mPress Ctrl+C to stop.\x1B[0m");
2401
+ console.log("");
2402
+ if (!noOpen) {
2403
+ setTimeout(() => openUrl(url), 400);
2404
+ }
2405
+ });
2406
+ server.on("error", (err) => {
2407
+ if (err.code === "EADDRINUSE") {
2408
+ console.error(`
2409
+ \x1B[31mPort ${port} is already in use.\x1B[0m Try: vendian dev --port ${port + 1}
2410
+ `);
2411
+ } else {
2412
+ console.error(`
2413
+ \x1B[31m${err.message}\x1B[0m
2414
+ `);
2415
+ }
2416
+ process.exit(1);
2417
+ });
2418
+ process.on("SIGINT", () => shutdown(server));
2419
+ process.on("SIGTERM", () => shutdown(server));
2420
+ }
2421
+ function shutdown(server) {
2422
+ console.log("\n \x1B[90mShutting down...\x1B[0m");
2423
+ if (serveChild && !serveChild.killed) {
2424
+ serveChild.kill("SIGTERM");
2425
+ }
2426
+ server.close();
2427
+ process.exit(0);
2428
+ }
2429
+ function handleRequest(req, res) {
2430
+ const parsed = new URL(req.url, `http://${req.headers.host}`);
2431
+ const pathname = parsed.pathname;
2432
+ res.setHeader("Access-Control-Allow-Origin", "*");
2433
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2434
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
2435
+ if (req.method === "OPTIONS") {
2436
+ res.writeHead(204);
2437
+ res.end();
2438
+ return;
2439
+ }
2440
+ if (pathname.startsWith("/api/")) {
2441
+ return handleApi(req, res, pathname, parsed);
2442
+ }
2443
+ serveStatic(req, res, pathname);
2444
+ }
2445
+ var MIME_TYPES = {
2446
+ ".html": "text/html",
2447
+ ".css": "text/css",
2448
+ ".js": "application/javascript",
2449
+ ".json": "application/json",
2450
+ ".svg": "image/svg+xml",
2451
+ ".png": "image/png",
2452
+ ".ico": "image/x-icon"
2453
+ };
2454
+ function serveStatic(req, res, pathname) {
2455
+ let filePath = path8.join(UI_DIR, pathname === "/" ? "index.html" : pathname);
2456
+ if (!fs11.existsSync(filePath) || fs11.statSync(filePath).isDirectory()) {
2457
+ filePath = path8.join(UI_DIR, "index.html");
2458
+ }
2459
+ if (!fs11.existsSync(filePath)) {
2460
+ res.writeHead(404);
2461
+ res.end("Not Found");
2462
+ return;
2463
+ }
2464
+ const ext = path8.extname(filePath);
2465
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
2466
+ const content = fs11.readFileSync(filePath);
2467
+ res.writeHead(200, { "Content-Type": contentType });
2468
+ res.end(content);
2469
+ }
2470
+ async function handleApi(req, res, pathname, parsed) {
2471
+ try {
2472
+ if (req.method === "GET") {
2473
+ switch (pathname) {
2474
+ case "/api/status":
2475
+ return apiStatus(req, res);
2476
+ case "/api/agents":
2477
+ return apiAgents(req, res);
2478
+ case "/api/logs":
2479
+ return apiLogs(req, res, parsed);
2480
+ case "/api/serve/state":
2481
+ return apiServeState(req, res);
2482
+ case "/api/auth":
2483
+ return apiAuth(req, res);
2484
+ case "/api/workspaces":
2485
+ return await apiWorkspaces(req, res);
2486
+ default:
2487
+ return jsonResponse(res, { error: "not found" }, 404);
2488
+ }
2489
+ }
2490
+ if (req.method === "POST") {
2491
+ const body = await readBody(req);
2492
+ switch (pathname) {
2493
+ case "/api/validate":
2494
+ return await apiValidate(req, res, body);
2495
+ case "/api/serve/start":
2496
+ return await apiServeStart(req, res, body);
2497
+ case "/api/serve/stop":
2498
+ return apiServeStop(req, res);
2499
+ case "/api/create":
2500
+ return await apiCreate(req, res, body);
2501
+ case "/api/auth/switch":
2502
+ return await apiAuthSwitch(req, res, body);
2503
+ default:
2504
+ return jsonResponse(res, { error: "not found" }, 404);
2505
+ }
2506
+ }
2507
+ jsonResponse(res, { error: "method not allowed" }, 405);
2508
+ } catch (err) {
2509
+ jsonResponse(res, { error: err.message || "Internal error" }, 500);
2510
+ }
2511
+ }
2512
+ function apiStatus(req, res) {
2513
+ const serving = serveChild !== null && !serveChild.killed;
2514
+ jsonResponse(res, {
2515
+ serving,
2516
+ agentsDir,
2517
+ collectionId,
2518
+ connected: serveState.connected,
2519
+ activity: serveState.activity,
2520
+ agentCount: serveState.agents.length,
2521
+ jobsRun: serveState.jobsRun,
2522
+ version: CLI_VERSION
2523
+ });
2524
+ }
2525
+ function apiServeState(req, res) {
2526
+ const serving = serveChild !== null && !serveChild.killed;
2527
+ const agentStatuses = serveState.agents.map((agent) => {
2528
+ const relPath = agent.relativePath || ".";
2529
+ const runtime = agentRuntimeStatus(agent, serveState.agentRunState);
2530
+ const runState = (serveState.agentRunState || {})[relPath] || {};
2267
2531
  return {
2268
- ok: false,
2269
- workspaces: [],
2270
- error: (result.stderr || result.stdout || `workspace list exited with code ${result.status}`).trim()
2532
+ name: agent.manifestName || agent.relativePath || "Agent",
2533
+ relativePath: relPath,
2534
+ status: runtime.status,
2535
+ errorMessage: agent.errorMessage || runState.errorMessage || null,
2536
+ progressMessage: runState.progressMessage || null,
2537
+ lastEventAt: runState.lastEventAt || null,
2538
+ runId: runState.runId || null,
2539
+ jobType: runState.jobType || null
2540
+ };
2541
+ });
2542
+ const activity = [];
2543
+ for (const [agentPath, entries] of Object.entries(serveState.agentLogs || {})) {
2544
+ const agent = serveState.agents.find((a) => (a.relativePath || ".") === agentPath);
2545
+ const name = agent?.manifestName || agentPath;
2546
+ for (const e of (entries || []).slice(-10)) {
2547
+ activity.push({ ...e, _agentName: name, _agentPath: agentPath });
2548
+ }
2549
+ }
2550
+ for (const event of (serveState.logs || []).slice(-30)) {
2551
+ if (!event || !event.type) continue;
2552
+ const ts = event.timestamp || "";
2553
+ const type = event.type;
2554
+ if ([
2555
+ "daemon_registered",
2556
+ "daemon_event_stream_connected",
2557
+ "inventory_synced",
2558
+ "agent_discovery_completed",
2559
+ "backend_connection_lost",
2560
+ "backend_connection_restored",
2561
+ "retry_scheduled",
2562
+ "daemon_event_stream_lost"
2563
+ ].includes(type)) {
2564
+ activity.push({
2565
+ timestamp: ts,
2566
+ eventType: type,
2567
+ level: type.includes("lost") || type.includes("retry") ? "warn" : "info",
2568
+ message: formatDaemonEvent(event),
2569
+ _agentName: "System",
2570
+ _agentPath: ""
2571
+ });
2572
+ }
2573
+ }
2574
+ activity.sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || ""));
2575
+ jsonResponse(res, {
2576
+ serving,
2577
+ connected: serveState.connected,
2578
+ stopped: serveState.stopped,
2579
+ activity: serveState.activity,
2580
+ agentCount: serveState.agents.length,
2581
+ jobsRun: serveState.jobsRun,
2582
+ agents: agentStatuses,
2583
+ recentActivity: activity.slice(-40),
2584
+ errors: serveState.errors || []
2585
+ });
2586
+ }
2587
+ function apiAgents(req, res) {
2588
+ const folders = findAgentFolders(agentsDir);
2589
+ const agents = folders.map((folder) => {
2590
+ const manifestPath = path8.join(folder.absolutePath, "manifest.yaml");
2591
+ const info = parseManifestBasic(manifestPath);
2592
+ const relPath = path8.relative(agentsDir, folder.absolutePath).replace(/\\/g, "/") || folder.name;
2593
+ const inventoryAgent = serveState.agents.find(
2594
+ (a) => (a.relativePath || ".") === relPath || a.relativePath === folder.name
2595
+ );
2596
+ const runtime = inventoryAgent ? agentRuntimeStatus(inventoryAgent, serveState.agentRunState) : { status: "unknown" };
2597
+ return {
2598
+ path: folder.absolutePath,
2599
+ relativePath: relPath,
2600
+ name: folder.name,
2601
+ displayName: info.name || folder.name,
2602
+ id: info.id || "",
2603
+ description: info.description || "",
2604
+ version: info.version || "",
2605
+ triggers: info.triggers || [],
2606
+ credentials: info.credentials || [],
2607
+ triggerCount: info.triggerCount || (info.triggers || []).length,
2608
+ credentialCount: info.credentialCount || (info.credentials || []).length,
2609
+ hasAgentPy: fs11.existsSync(path8.join(folder.absolutePath, "agent.py")),
2610
+ hasRequirements: fs11.existsSync(path8.join(folder.absolutePath, "requirements.txt")),
2611
+ runtimeStatus: runtime.status
2612
+ };
2613
+ });
2614
+ jsonResponse(res, { agents, agentsDir });
2615
+ }
2616
+ function apiLogs(req, res, parsed) {
2617
+ const since = parseInt(parsed.searchParams.get("since") || "0", 10);
2618
+ const lines = serveLogs.slice(since);
2619
+ jsonResponse(res, { logs: lines, total: serveLogs.length, since });
2620
+ }
2621
+ function apiAuth(req, res) {
2622
+ const active = activeCloudAuthStatus();
2623
+ const activeUrl = (active.activeApiUrl || active.apiUrl || "").replace(/\/$/, "");
2624
+ const backends = Object.entries(BACKEND_TARGETS).filter(([key]) => !["localhost", "production"].includes(key)).map(([key, url]) => {
2625
+ const canonicalUrl = url.replace(/\/$/, "");
2626
+ const status = cloudAuthStatus({ backend: key, env: { ...process.env, VENDIAN_API_URL: "" } });
2627
+ const isActive = Boolean(activeUrl && canonicalUrl === activeUrl && active.authenticated);
2628
+ return {
2629
+ key,
2630
+ url,
2631
+ authenticated: status.authenticated,
2632
+ email: status.email || null,
2633
+ active: isActive
2271
2634
  };
2635
+ });
2636
+ jsonResponse(res, {
2637
+ authenticated: active.authenticated,
2638
+ email: active.email || null,
2639
+ apiUrl: active.apiUrl || null,
2640
+ backends
2641
+ });
2642
+ }
2643
+ async function apiWorkspaces(req, res) {
2644
+ const result = await listCloudWorkspaces({ timeoutMs: 15e3 });
2645
+ jsonResponse(res, result);
2646
+ }
2647
+ async function apiAuthSwitch(req, res, body) {
2648
+ const backend = body.backend;
2649
+ if (!backend || !BACKEND_TARGETS[backend]) {
2650
+ return jsonResponse(res, { ok: false, error: `Unknown backend: ${backend}` }, 400);
2272
2651
  }
2273
2652
  try {
2274
- return { ok: true, workspaces: normalizeWorkspaceList(JSON.parse(result.stdout)), error: "" };
2275
- } catch (error) {
2276
- const message = error && typeof error.message === "string" ? error.message : String(error);
2277
- return { ok: false, workspaces: [], error: `Could not parse workspace list: ${message}` };
2653
+ const { activateCloudProfile: activateCloudProfile2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
2654
+ const result = activateCloudProfile2({ backend });
2655
+ if (result?.activated) {
2656
+ jsonResponse(res, { ok: true, switched: true, backend });
2657
+ } else {
2658
+ jsonResponse(res, { ok: false, needsLogin: true, backend, url: BACKEND_TARGETS[backend] });
2659
+ }
2660
+ } catch (err) {
2661
+ jsonResponse(res, { ok: false, error: err.message });
2278
2662
  }
2279
2663
  }
2280
- function normalizeWorkspaceList(payload) {
2281
- const data = payload && typeof payload === "object" && "data" in payload ? payload.data : payload;
2282
- const raw = Array.isArray(data) ? data : data && typeof data === "object" && Array.isArray(data.data) ? data.data : data && typeof data === "object" && Array.isArray(data.collections) ? data.collections : [];
2283
- return raw.filter((item) => item && typeof item === "object").map((item) => ({
2284
- id: stringValue2(item.id),
2285
- name: stringValue2(item.name || item.slug || item.id || "Unnamed workspace"),
2286
- slug: stringValue2(item.slug)
2287
- })).filter((item) => item.id);
2664
+ async function apiValidate(req, res, body) {
2665
+ const targetPath = body.path || agentsDir;
2666
+ const invocation = await preparePythonVendianInvocation(
2667
+ ["validate", targetPath, "--json", "--no-runtime"],
2668
+ { skipAutoUpdate: true }
2669
+ );
2670
+ const result = await runCaptureAsync(invocation.command, invocation.args, { env: invocation.env });
2671
+ try {
2672
+ const parsed = JSON.parse(result.stdout);
2673
+ jsonResponse(res, parsed);
2674
+ } catch {
2675
+ jsonResponse(res, {
2676
+ valid: false,
2677
+ errors: result.stderr ? [result.stderr.trim()] : ["Validation failed"],
2678
+ stdout: result.stdout
2679
+ });
2680
+ }
2288
2681
  }
2289
- function workspaceLabel(workspace) {
2290
- const name = workspace?.name || workspace?.slug || workspace?.id || "Unnamed workspace";
2291
- const slug = workspace?.slug && workspace.slug !== name ? ` (${workspace.slug})` : "";
2292
- return `${name}${slug}`;
2682
+ async function apiServeStart(req, res, body) {
2683
+ if (serveChild && !serveChild.killed) {
2684
+ return jsonResponse(res, { ok: false, error: "Already serving" });
2685
+ }
2686
+ const targetDir = body.agentsDir || agentsDir;
2687
+ const targetCollection = body.collectionId || collectionId;
2688
+ try {
2689
+ const invocation = await preparePythonVendianInvocation(
2690
+ buildLocalServeEventStreamArgs({ agentsDir: targetDir, collectionId: targetCollection }),
2691
+ { onProgress: null }
2692
+ );
2693
+ serveState = initialServeState();
2694
+ serveLogs = [];
2695
+ logStore = createServeLogStore({ agentsDir: targetDir, collectionId: targetCollection });
2696
+ serveChild = spawn3(invocation.command, invocation.args, {
2697
+ env: invocation.env,
2698
+ stdio: ["ignore", "pipe", "pipe"],
2699
+ shell: false
2700
+ });
2701
+ let buffer = "";
2702
+ serveChild.stdout.setEncoding("utf8");
2703
+ serveChild.stdout.on("data", (chunk) => {
2704
+ buffer += chunk;
2705
+ const lines = buffer.split(/\r?\n/);
2706
+ buffer = lines.pop() || "";
2707
+ for (const line of lines) {
2708
+ serveLogs.push(line);
2709
+ if (serveLogs.length > 5e3) serveLogs.shift();
2710
+ try {
2711
+ const event = parseServeEventLine(line);
2712
+ if (event) {
2713
+ serveState = applyServeEvent(serveState, event);
2714
+ try {
2715
+ logStore?.append(event);
2716
+ } catch {
2717
+ }
2718
+ }
2719
+ } catch {
2720
+ }
2721
+ }
2722
+ });
2723
+ serveChild.stderr.setEncoding("utf8");
2724
+ serveChild.stderr.on("data", (chunk) => {
2725
+ const text = chunk.trim();
2726
+ if (text) {
2727
+ serveLogs.push(`[stderr] ${text}`);
2728
+ if (serveLogs.length > 5e3) serveLogs.shift();
2729
+ }
2730
+ });
2731
+ serveChild.on("exit", (code, signal) => {
2732
+ serveState = { ...serveState, stopped: true, connected: false };
2733
+ serveLogs.push(`[process] Exited code=${code} signal=${signal || "none"}`);
2734
+ serveChild = null;
2735
+ try {
2736
+ logStore?.compact();
2737
+ } catch {
2738
+ }
2739
+ });
2740
+ jsonResponse(res, { ok: true, pid: serveChild.pid });
2741
+ } catch (err) {
2742
+ jsonResponse(res, { ok: false, error: err.message });
2743
+ }
2293
2744
  }
2294
- function stringValue2(value) {
2295
- return value == null ? "" : String(value).trim();
2745
+ function apiServeStop(req, res) {
2746
+ if (!serveChild || serveChild.killed) {
2747
+ return jsonResponse(res, { ok: true, wasRunning: false });
2748
+ }
2749
+ serveChild.kill("SIGTERM");
2750
+ setTimeout(() => {
2751
+ if (serveChild && !serveChild.killed) {
2752
+ serveChild.kill("SIGKILL");
2753
+ }
2754
+ }, 3e3);
2755
+ jsonResponse(res, { ok: true, wasRunning: true });
2756
+ }
2757
+ async function apiCreate(req, res, body) {
2758
+ const name = (body.name || "").trim();
2759
+ if (!name) {
2760
+ return jsonResponse(res, { ok: false, error: "Name is required" }, 400);
2761
+ }
2762
+ try {
2763
+ const invocation = await preparePythonVendianInvocation(
2764
+ ["create", name, "--output-dir", agentsDir],
2765
+ { skipAutoUpdate: true }
2766
+ );
2767
+ const result = await runCaptureAsync(invocation.command, invocation.args, { env: invocation.env });
2768
+ if (result.code === 0) {
2769
+ jsonResponse(res, { ok: true, name, output: result.stdout });
2770
+ } else {
2771
+ jsonResponse(res, { ok: false, error: result.stderr || result.stdout || "Create failed" });
2772
+ }
2773
+ } catch (err) {
2774
+ jsonResponse(res, { ok: false, error: err.message });
2775
+ }
2776
+ }
2777
+ function jsonResponse(res, data, status = 200) {
2778
+ const body = JSON.stringify(data);
2779
+ res.writeHead(status, { "Content-Type": "application/json" });
2780
+ res.end(body);
2781
+ }
2782
+ function readBody(req) {
2783
+ return new Promise((resolve) => {
2784
+ const chunks = [];
2785
+ req.on("data", (chunk) => chunks.push(chunk));
2786
+ req.on("end", () => {
2787
+ const raw = Buffer.concat(chunks).toString("utf8");
2788
+ try {
2789
+ resolve(JSON.parse(raw));
2790
+ } catch {
2791
+ resolve({});
2792
+ }
2793
+ });
2794
+ });
2795
+ }
2796
+ function runCaptureAsync(command, args, options = {}) {
2797
+ return new Promise((resolve) => {
2798
+ const child = spawn3(command, args, {
2799
+ stdio: ["ignore", "pipe", "pipe"],
2800
+ shell: false,
2801
+ ...options
2802
+ });
2803
+ let stdout = "";
2804
+ let stderr = "";
2805
+ child.stdout.on("data", (d) => {
2806
+ stdout += d;
2807
+ });
2808
+ child.stderr.on("data", (d) => {
2809
+ stderr += d;
2810
+ });
2811
+ child.on("exit", (code) => {
2812
+ resolve({ code: code ?? 1, stdout, stderr });
2813
+ });
2814
+ child.on("error", (err) => {
2815
+ resolve({ code: 1, stdout, stderr: err.message });
2816
+ });
2817
+ });
2818
+ }
2819
+ function parseManifestBasic(manifestPath) {
2820
+ try {
2821
+ const content = fs11.readFileSync(manifestPath, "utf8");
2822
+ const get = (key) => {
2823
+ const m = content.match(new RegExp(`^${key}\\s*:\\s*['"]?([^'"\\n#]+?)['"]?\\s*$`, "m"));
2824
+ return m ? m[1].trim() : "";
2825
+ };
2826
+ const countListItems = (key) => {
2827
+ const m = content.match(new RegExp(`^${key}\\s*:`, "m"));
2828
+ if (!m) return 0;
2829
+ const after = content.slice(m.index + m[0].length);
2830
+ const lines = after.split("\n");
2831
+ let count = 0;
2832
+ let firstIndent = -1;
2833
+ for (const line of lines) {
2834
+ if (/^\S/.test(line) && count > 0) break;
2835
+ const listMatch = line.match(/^(\s+)-\s/);
2836
+ if (listMatch) {
2837
+ const indent = listMatch[1].length;
2838
+ if (firstIndent < 0) firstIndent = indent;
2839
+ if (indent === firstIndent) count++;
2840
+ }
2841
+ }
2842
+ return count;
2843
+ };
2844
+ const getListLabels = (key) => {
2845
+ const m = content.match(new RegExp(`^${key}\\s*:`, "m"));
2846
+ if (!m) return [];
2847
+ const after = content.slice(m.index + m[0].length);
2848
+ const lines = after.split("\n");
2849
+ const items = [];
2850
+ let currentItem = null;
2851
+ let firstIndent = -1;
2852
+ for (const line of lines) {
2853
+ if (/^\S/.test(line) && items.length + (currentItem ? 1 : 0) > 0) break;
2854
+ const listMatch = line.match(/^(\s+)-\s*(.*)/);
2855
+ if (listMatch) {
2856
+ const indent = listMatch[1].length;
2857
+ if (firstIndent < 0) firstIndent = indent;
2858
+ if (indent === firstIndent) {
2859
+ if (currentItem) items.push(currentItem);
2860
+ const inlineValue = listMatch[2].trim();
2861
+ if (inlineValue && !inlineValue.includes(":")) {
2862
+ currentItem = { label: inlineValue };
2863
+ } else {
2864
+ currentItem = {};
2865
+ const kvMatch = inlineValue.match(/^(\w+)\s*:\s*(.+)/);
2866
+ if (kvMatch) currentItem[kvMatch[1]] = kvMatch[2].replace(/['"]*/g, "").trim();
2867
+ }
2868
+ } else if (currentItem) {
2869
+ const kvMatch = line.match(/^\s+(\w+)\s*:\s*(.+)/);
2870
+ if (kvMatch) currentItem[kvMatch[1]] = kvMatch[2].replace(/['"]*/g, "").trim();
2871
+ }
2872
+ } else if (currentItem && /^\s+\w+\s*:/.test(line)) {
2873
+ const kvMatch = line.match(/^\s+(\w+)\s*:\s*(.+)/);
2874
+ if (kvMatch) currentItem[kvMatch[1]] = kvMatch[2].replace(/['"]*/g, "").trim();
2875
+ }
2876
+ }
2877
+ if (currentItem) items.push(currentItem);
2878
+ return items.map((item) => item.label || item.type || item.id || Object.values(item)[0] || "").filter(Boolean);
2879
+ };
2880
+ return {
2881
+ id: get("id"),
2882
+ name: get("name"),
2883
+ description: get("description"),
2884
+ version: get("version"),
2885
+ triggers: getListLabels("triggers"),
2886
+ credentials: getListLabels("credentials"),
2887
+ triggerCount: countListItems("triggers"),
2888
+ credentialCount: countListItems("credentials")
2889
+ };
2890
+ } catch {
2891
+ return {};
2892
+ }
2893
+ }
2894
+ function formatDaemonEvent(event) {
2895
+ switch (event.type) {
2896
+ case "daemon_registered":
2897
+ return "Connected to backend";
2898
+ case "daemon_event_stream_connected":
2899
+ return "Event stream connected";
2900
+ case "daemon_event_stream_lost":
2901
+ return `Event stream lost: ${event.error || "unknown"}`;
2902
+ case "inventory_synced":
2903
+ return `Inventory synced (${event.agentCount || 0} agents)`;
2904
+ case "agent_discovery_completed":
2905
+ return `Discovered ${event.agentCount || 0} agents`;
2906
+ case "backend_connection_lost":
2907
+ return `Connection lost: ${event.error || event.activity || "unknown"}`;
2908
+ case "backend_connection_restored":
2909
+ return "Connection restored";
2910
+ case "retry_scheduled":
2911
+ return `Retrying ${event.activity || "operation"} in ${event.delaySeconds || "?"}s`;
2912
+ default:
2913
+ return event.type;
2914
+ }
2296
2915
  }
2916
+ function openUrl(url) {
2917
+ const cmd = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
2918
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
2919
+ const shell = process.platform === "win32" ? "cmd.exe" : void 0;
2920
+ spawn3(shell || cmd, shell ? args : [url], { detached: true, stdio: "ignore" }).unref();
2921
+ }
2922
+
2923
+ // src/tui.js
2924
+ init_constants();
2925
+ init_auth();
2926
+ import { spawnSync as spawnSync4 } from "node:child_process";
2927
+ import fs12 from "node:fs";
2928
+ import path9 from "node:path";
2929
+ import readline from "node:readline";
2930
+
2931
+ // src/npm-update.js
2932
+ var NPM_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
2297
2933
 
2298
2934
  // src/ui/figures.js
2299
2935
  var supportsUnicode = process.platform !== "win32" || Boolean(
@@ -2602,6 +3238,7 @@ var COMMAND_GROUPS = [
2602
3238
  { cmd: "vendian models", desc: "List available AI models" }
2603
3239
  ] },
2604
3240
  { section: "Running agents", commands: [
3241
+ { cmd: "vendian dev", desc: "Open the dev UI in your browser" },
2605
3242
  { cmd: "vendian cloud local run --collection-id ID --path . --input-json '{}'", desc: "Run one agent" },
2606
3243
  { cmd: "vendian cloud local serve --agents-dir .", desc: "Start agents" },
2607
3244
  { cmd: "vendian login --backend staging", desc: "Sign in to staging" }
@@ -2654,7 +3291,7 @@ async function pickProject({ env, platform, candidates }) {
2654
3291
  print(col.bold(" Which project do you want to run?"));
2655
3292
  print("");
2656
3293
  const items = candidates.map((candidate) => {
2657
- const name = readManifestName(candidate.absolutePath) || friendlyName(path8.basename(candidate.absolutePath) || candidate.path);
3294
+ const name = readManifestName(candidate.absolutePath) || friendlyName(path9.basename(candidate.absolutePath) || candidate.path);
2658
3295
  return `${name.padEnd(32)} ${col.gray(`${candidate.agentCount} agent${candidate.agentCount === 1 ? "" : "s"}`)}`;
2659
3296
  });
2660
3297
  const idx = await pickMenu(items, { allowBack: true });
@@ -2668,7 +3305,7 @@ async function pickAgentsFromFolder({ env, platform, candidate }) {
2668
3305
  if (folders.length <= 1) return { agentsDir: candidate.path };
2669
3306
  const named = folders.map((f) => ({
2670
3307
  ...f,
2671
- displayName: readManifestName(f.absolutePath) || friendlyName(f.name || path8.basename(f.path))
3308
+ displayName: readManifestName(f.absolutePath) || friendlyName(f.name || path9.basename(f.path))
2672
3309
  }));
2673
3310
  clearScreen();
2674
3311
  printHeader({ env, platform });
@@ -2691,19 +3328,19 @@ async function pickSingleAgentToRun({ env, platform, candidates }) {
2691
3328
  print(col.yellow(" Couldn't find any agents."));
2692
3329
  const input = await ask("Agent folder", "./agents/my-agent");
2693
3330
  if (!input) return null;
2694
- return { path: input, displayName: friendlyName(path8.basename(input) || "Agent") };
3331
+ return { path: input, displayName: friendlyName(path9.basename(input) || "Agent") };
2695
3332
  }
2696
3333
  const agents = candidates.flatMap((candidate) => {
2697
3334
  const folders = findAgentFolders(candidate.absolutePath);
2698
3335
  if (folders.length > 0) {
2699
3336
  return folders.map((folder) => ({
2700
3337
  path: folder.path,
2701
- displayName: readManifestName(folder.absolutePath) || friendlyName(folder.name || path8.basename(folder.path))
3338
+ displayName: readManifestName(folder.absolutePath) || friendlyName(folder.name || path9.basename(folder.path))
2702
3339
  }));
2703
3340
  }
2704
3341
  return [{
2705
3342
  path: candidate.path,
2706
- displayName: readManifestName(candidate.absolutePath) || friendlyName(path8.basename(candidate.absolutePath) || candidate.path)
3343
+ displayName: readManifestName(candidate.absolutePath) || friendlyName(path9.basename(candidate.absolutePath) || candidate.path)
2707
3344
  }];
2708
3345
  });
2709
3346
  if (agents.length === 1) return agents[0];
@@ -2803,7 +3440,7 @@ async function showServe({ env, platform }) {
2803
3440
  const candidates = findAgentDirectoryCandidates();
2804
3441
  const picked = await pickAgentsToRun({ env, platform, candidates });
2805
3442
  if (!picked) return "home";
2806
- const { agentsDir } = picked;
3443
+ const { agentsDir: agentsDir2 } = picked;
2807
3444
  const workspace = await chooseCloudWorkspace({
2808
3445
  env,
2809
3446
  platform,
@@ -2811,9 +3448,9 @@ async function showServe({ env, platform }) {
2811
3448
  pickerTitle: "Which project do you want to run?"
2812
3449
  });
2813
3450
  if (!workspace) return "home";
2814
- return await runServeDashboard({ env, platform, agentsDir, collectionId: workspace.id, wsLabel: workspace.label });
3451
+ return await runServeDashboard({ env, platform, agentsDir: agentsDir2, collectionId: workspace.id, wsLabel: workspace.label });
2815
3452
  }
2816
- async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLabel = "" }) {
3453
+ async function runServeDashboard({ env, platform, agentsDir: agentsDir2, collectionId: collectionId2, wsLabel = "" }) {
2817
3454
  let state = initialServeState();
2818
3455
  let child = null;
2819
3456
  let ctrlCArmed = false;
@@ -2822,7 +3459,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2822
3459
  let stopSignalAttempt = 0;
2823
3460
  let stopTimer = null;
2824
3461
  let dashboardClosed = false;
2825
- let logStore = null;
3462
+ let logStore2 = null;
2826
3463
  let lastAgentOnlinePrint = 0;
2827
3464
  let agentsSeenOnline = false;
2828
3465
  let initialSetupDone = false;
@@ -2832,8 +3469,8 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2832
3469
  print("");
2833
3470
  print(col.gray("\u2550".repeat(W)));
2834
3471
  print(` ${col.cyan(col.bold("\u2191 VENDIAN"))} ${col.bold("Serving agents")}`);
2835
- print(` ${col.gray("Dir:")} ${agentsDir}`);
2836
- print(` ${col.gray("Workspace:")} ${wsLabel || collectionId || "\u2014"}`);
3472
+ print(` ${col.gray("Dir:")} ${agentsDir2}`);
3473
+ print(` ${col.gray("Workspace:")} ${wsLabel || collectionId2 || "\u2014"}`);
2837
3474
  print(col.gray(hr(W)));
2838
3475
  print(col.gray(" S stop gracefully | ^c ^c exit"));
2839
3476
  print(col.gray(hr(W)));
@@ -2860,7 +3497,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2860
3497
  if (!event) return;
2861
3498
  const type = String(event.type || "");
2862
3499
  const agentPath = String(event.relativePath || event.path || "");
2863
- const agentName = agentPath ? friendlyName(path8.basename(agentPath)) || agentPath : ".";
3500
+ const agentName = agentPath ? friendlyName(path9.basename(agentPath)) || agentPath : ".";
2864
3501
  if (type === "daemon_wake_received") return;
2865
3502
  if (type === "lease_claim_no_job") return;
2866
3503
  if (type === "dispatch_job_received" || type === "dispatch_job_acked") return;
@@ -2871,7 +3508,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2871
3508
  const now = Date.now();
2872
3509
  if (!hasProblems && allOnline && agentsSeenOnline && now - lastAgentOnlinePrint < 12e4) return;
2873
3510
  for (const a of agents) {
2874
- const name = friendlyName(path8.basename(a.relativePath || ".")) || a.manifestName || a.relativePath || "?";
3511
+ const name = friendlyName(path9.basename(a.relativePath || ".")) || a.manifestName || a.relativePath || "?";
2875
3512
  if (a.status === "error") {
2876
3513
  printAgent(name, col.red(`\u2716 Error: ${a.errorMessage || ""}`));
2877
3514
  continue;
@@ -2950,15 +3587,15 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2950
3587
  }
2951
3588
  }
2952
3589
  try {
2953
- logStore = createServeLogStore({ agentsDir, collectionId, env, platform });
2954
- const historicalLogs = logStore.load();
3590
+ logStore2 = createServeLogStore({ agentsDir: agentsDir2, collectionId: collectionId2, env, platform });
3591
+ const historicalLogs = logStore2.load();
2955
3592
  if (historicalLogs.length > 0) {
2956
3593
  const agentLogs = mergeAgentLogRecords(state.agentLogs, historicalLogs);
2957
3594
  state = { ...state, agentLogs, agentRunState: { ...agentRunStateFromLogs(agentLogs, { includeRunning: false }), ...state.agentRunState } };
2958
3595
  }
2959
3596
  child = await spawnLocalServeEventStream({
2960
- agentsDir,
2961
- collectionId,
3597
+ agentsDir: agentsDir2,
3598
+ collectionId: collectionId2,
2962
3599
  env,
2963
3600
  platform,
2964
3601
  onProgress: (msg) => {
@@ -2967,7 +3604,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2967
3604
  }
2968
3605
  });
2969
3606
  process.stdout.write("\r" + " ".repeat(80) + "\r");
2970
- printDaemon("serve_started", col.gray(`dir=${agentsDir}`));
3607
+ printDaemon("serve_started", col.gray(`dir=${agentsDir2}`));
2971
3608
  } catch (error) {
2972
3609
  setCursorVisible(true);
2973
3610
  print(col.red(` ${fig.cross} Failed to start: ${errMsg(error)}`));
@@ -2996,9 +3633,13 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2996
3633
  if (!stopRequested || dashboardClosed || !childIsRunning()) return;
2997
3634
  stopSignalAttempt += 1;
2998
3635
  const signal = stopSignalForAttempt(stopSignalAttempt);
2999
- try {
3000
- child.kill(signal);
3001
- } catch {
3636
+ if (process.platform === "win32") {
3637
+ killProcessTree(child);
3638
+ } else {
3639
+ try {
3640
+ child.kill(signal);
3641
+ } catch {
3642
+ }
3002
3643
  }
3003
3644
  if (signal !== "SIGKILL") scheduleStopEscalation();
3004
3645
  }, 4e3);
@@ -3014,18 +3655,25 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
3014
3655
  print(col.yellow("\n Stopping your agents\u2026"));
3015
3656
  clearTimeout(ctrlCArmTimer);
3016
3657
  ctrlCArmed = false;
3017
- try {
3018
- child.kill(stopSignalForAttempt(stopSignalAttempt));
3019
- } catch {
3658
+ if (process.platform === "win32") {
3659
+ killProcessTree(child);
3660
+ } else {
3661
+ try {
3662
+ child.kill(stopSignalForAttempt(stopSignalAttempt));
3663
+ } catch {
3664
+ }
3665
+ scheduleStopEscalation();
3020
3666
  }
3021
- scheduleStopEscalation();
3022
3667
  }
3023
3668
  if (process.stdin.isTTY) {
3024
3669
  let onKeypress = function(str, key) {
3025
3670
  if (!key) return;
3026
3671
  if (key.ctrl && key.name === "c") {
3027
3672
  if (ctrlCArmed) {
3028
- child?.kill("SIGINT");
3673
+ try {
3674
+ child?.kill("SIGINT");
3675
+ } catch (error) {
3676
+ }
3029
3677
  finish("exit");
3030
3678
  return;
3031
3679
  }
@@ -3065,7 +3713,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
3065
3713
  const event = parseServeEventLine(line);
3066
3714
  if (event) {
3067
3715
  try {
3068
- logStore?.append(event);
3716
+ logStore2?.append(event);
3069
3717
  } catch {
3070
3718
  }
3071
3719
  state = applyServeEvent(state, event);
@@ -3107,7 +3755,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
3107
3755
  print(col.gray("\n Agents stopped."));
3108
3756
  }
3109
3757
  try {
3110
- logStore?.compact();
3758
+ logStore2?.compact();
3111
3759
  } catch {
3112
3760
  }
3113
3761
  removeKeypress();
@@ -3243,7 +3891,7 @@ function endpointErrorStatus(error) {
3243
3891
  }
3244
3892
  function buildLocalRunArgs({
3245
3893
  agentPath = ".",
3246
- collectionId = "",
3894
+ collectionId: collectionId2 = "",
3247
3895
  inputJson = "{}"
3248
3896
  } = {}) {
3249
3897
  return [
@@ -3251,7 +3899,7 @@ function buildLocalRunArgs({
3251
3899
  "local",
3252
3900
  "run",
3253
3901
  "--collection-id",
3254
- collectionId,
3902
+ collectionId2,
3255
3903
  "--path",
3256
3904
  agentPath || ".",
3257
3905
  "--input-json",
@@ -3263,21 +3911,39 @@ function stopSignalForAttempt(attempt = 0) {
3263
3911
  if (attempt === 1) return "SIGTERM";
3264
3912
  return "SIGKILL";
3265
3913
  }
3914
+ function killProcessTree(child) {
3915
+ if (!child || child.exitCode != null || child.signalCode != null) return;
3916
+ if (process.platform === "win32") {
3917
+ try {
3918
+ spawnSync4("taskkill", ["/F", "/T", "/PID", String(child.pid)], { stdio: "ignore" });
3919
+ } catch {
3920
+ }
3921
+ } else {
3922
+ try {
3923
+ child.kill("SIGTERM");
3924
+ } catch {
3925
+ }
3926
+ }
3927
+ }
3266
3928
  function helpText() {
3267
3929
  return [
3268
3930
  "",
3269
3931
  ` \u2191 VENDIAN CLI v${CLI_VERSION}`,
3270
3932
  "",
3271
3933
  " Usage:",
3272
- " vendian Open the interactive shell",
3934
+ " vendian Open the dev UI in your browser",
3935
+ " vendian terminal Open the interactive terminal shell",
3273
3936
  " vendian login Sign in and prepare the local runtime",
3274
3937
  " vendian doctor Check local bootstrap health",
3275
3938
  " vendian update Update the managed runtime",
3276
3939
  " vendian init Write SDK agent docs into a workspace",
3277
3940
  ' vendian create "My Agent" Scaffold a new agent from templates',
3941
+ " vendian dev Open the dev UI (alias for bare vendian)",
3278
3942
  " vendian <command> Run a managed Python SDK/cloud command",
3279
3943
  "",
3280
3944
  " Examples:",
3945
+ " vendian",
3946
+ " vendian terminal",
3281
3947
  " vendian login",
3282
3948
  " vendian init --output-dir ./agents",
3283
3949
  ' vendian create "My Agent" --output-dir ./agents',
@@ -3289,7 +3955,7 @@ function helpText() {
3289
3955
  " vendian doctor",
3290
3956
  " vendian update",
3291
3957
  "",
3292
- ` Run \u203A vendian \u203A to open the interactive shell`,
3958
+ ` Run \u203A vendian terminal \u203A for the interactive terminal shell`,
3293
3959
  ""
3294
3960
  ].join("\n");
3295
3961
  }
@@ -3300,7 +3966,7 @@ function readManifestName(absoluteFolderPath) {
3300
3966
  if (!absoluteFolderPath) return null;
3301
3967
  for (const fname of ["manifest.yaml", "manifest.yml"]) {
3302
3968
  try {
3303
- const content = fs11.readFileSync(path8.join(absoluteFolderPath, fname), "utf8");
3969
+ const content = fs12.readFileSync(path9.join(absoluteFolderPath, fname), "utf8");
3304
3970
  const m = content.match(/^name\s*:\s*['"]?([^'"\n#]+?)['"]?\s*$/m);
3305
3971
  if (m?.[1]) return m[1].trim();
3306
3972
  } catch {
@@ -3348,11 +4014,7 @@ Environment:
3348
4014
  async function main(argv) {
3349
4015
  const [command, ...rest] = argv;
3350
4016
  if (!command) {
3351
- if (process.stdin.isTTY && process.stdout.isTTY) {
3352
- await runTui();
3353
- } else {
3354
- printHelp();
3355
- }
4017
+ await startDevServer(parseDevOptions([]));
3356
4018
  return;
3357
4019
  }
3358
4020
  if (command === "--help" || command === "-h") {
@@ -3363,10 +4025,22 @@ async function main(argv) {
3363
4025
  console.log(CLI_VERSION);
3364
4026
  return;
3365
4027
  }
4028
+ if (command === "terminal" || command === "tui") {
4029
+ if (process.stdin.isTTY && process.stdout.isTTY) {
4030
+ await runTui();
4031
+ } else {
4032
+ printHelp();
4033
+ }
4034
+ return;
4035
+ }
3366
4036
  if (isBootstrapCommand(command)) {
3367
4037
  await setup(parseSetupOptions(rest));
3368
4038
  return;
3369
4039
  }
4040
+ if (command === "dev") {
4041
+ await startDevServer(parseDevOptions(rest));
4042
+ return;
4043
+ }
3370
4044
  if (command === "doctor") {
3371
4045
  doctor();
3372
4046
  return;
@@ -3375,11 +4049,33 @@ async function main(argv) {
3375
4049
  await setup({ nonInteractive: true });
3376
4050
  return;
3377
4051
  }
4052
+ if (command.startsWith("--port") || command.startsWith("-p") || command === "--no-open" || command === "--agents-dir" || command === "--agents") {
4053
+ await startDevServer(parseDevOptions(argv));
4054
+ return;
4055
+ }
3378
4056
  await forwardToPythonVendian(argv);
3379
4057
  }
3380
4058
  function isBootstrapCommand(command) {
3381
4059
  return command === "login" || command === "setup";
3382
4060
  }
4061
+ function parseDevOptions(args) {
4062
+ const options = {};
4063
+ for (let i = 0; i < args.length; i++) {
4064
+ const arg = args[i];
4065
+ if (arg === "--port" || arg === "-p") {
4066
+ options.port = parseInt(args[++i], 10) || 3859;
4067
+ } else if (arg.startsWith("--port=")) {
4068
+ options.port = parseInt(arg.slice(7), 10) || 3859;
4069
+ } else if (arg === "--agents-dir" || arg === "--agents") {
4070
+ options.agents = args[++i];
4071
+ } else if (arg.startsWith("--agents-dir=")) {
4072
+ options.agents = arg.slice(13);
4073
+ } else if (arg === "--no-open") {
4074
+ options.noOpen = true;
4075
+ }
4076
+ }
4077
+ return options;
4078
+ }
3383
4079
  function parseSetupOptions(args) {
3384
4080
  const options = {
3385
4081
  nonInteractive: args.includes("--yes") || args.includes("--non-interactive"),