@vendian/cli 0.0.33 → 0.0.35

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 +1817 -1159
  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"] },
@@ -408,421 +754,348 @@ function doctor({ env = process.env, platform = process.platform } = {}) {
408
754
  const vendianPath = venvVendian(venvPath, platform);
409
755
  const registry = registryConfig(config, env, platform);
410
756
  console.log("Vendian doctor");
411
- console.log(`Home: ${venvPath}`);
412
- console.log(`System Python 3.11+: ${findPython(platform) ? "yes" : "no"}`);
413
- console.log(`uv: ${hasUv() ? "yes" : "no, will use pip fallback"}`);
414
- console.log(`Managed Python: ${fs4.existsSync(pythonPath) ? pythonVersion(pythonPath) || "present" : "missing"}`);
415
- 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;
757
+ console.log(`Home: ${venvPath}`);
758
+ console.log(`System Python 3.11+: ${findPython(platform) ? "yes" : "no"}`);
759
+ console.log(`uv: ${hasUv() ? "yes" : "no, will use pip fallback"}`);
760
+ console.log(`Managed Python: ${fs4.existsSync(pythonPath) ? pythonVersion(pythonPath) || "present" : "missing"}`);
761
+ console.log(`Vendian executable: ${fs4.existsSync(vendianPath) ? vendianPath : "missing"}`);
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;
764
- }
765
- if (platform === "darwin") {
766
- spawnSync3(...buildMacOpenCommand(url), { stdio: "ignore", shell: false });
767
- 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;
768
987
  }
769
- spawnSync3(...buildLinuxOpenCommand(url), { stdio: "ignore", shell: false });
988
+ return parents;
770
989
  }
771
- function buildWindowsOpenCommand(url) {
772
- return ["rundll32.exe", ["url.dll,FileProtocolHandler", String(url)]];
990
+ function safeResolve(candidate) {
991
+ try {
992
+ return path4.resolve(candidate);
993
+ } catch {
994
+ return null;
995
+ }
773
996
  }
774
- function buildMacOpenCommand(url) {
775
- return ["open", [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;
776
1011
  }
777
- function buildLinuxOpenCommand(url) {
778
- return ["xdg-open", [String(url)]];
1012
+ function commandWritesAgentDocs(args = []) {
1013
+ return args[0] === "init";
779
1014
  }
780
- function cloudConfigPath(env = process.env, platform = process.platform) {
781
- if (env.VENDIAN_CLOUD_CONFIG) {
782
- return path5.resolve(env.VENDIAN_CLOUD_CONFIG);
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
+ }
783
1024
  }
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");
1025
+ return ".";
1026
+ }
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;
787
1053
  }
788
- const root = env.XDG_CONFIG_HOME || path5.join(process.env.HOME || "", ".config");
789
- return path5.posix.join(root, "vendian", "cloud-auth.json");
1054
+ recordAgentDocsWorkspace(initOutputDir(args), options);
1055
+ return true;
790
1056
  }
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;
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 };
1070
+ }
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,
@@ -868,474 +1141,290 @@ async function setup({
868
1141
  if (registry.tokenSource !== "secret-store") {
869
1142
  next.gitlabToken = registry.token;
870
1143
  }
871
- console.log("Using saved package access.");
872
- }
873
- const installRegistry = registryConfig(next, env, platform);
874
- if (!installRegistry.token) {
875
- throw new Error("Package access is missing. Run interactive `vendian login` to sign in.");
876
- }
877
- const python = findPython(platform);
878
- if (!python) {
879
- throw new Error("Python 3.11+ was not found. Install Python 3.11 or newer, then rerun `vendian login`.");
880
- }
881
- const venvPath = managedVenvPath(env, platform);
882
- console.log(`Managed environment: ${venvPath}`);
883
- const pythonPath = ensureVenv(venvPath, python, platform);
884
- console.log(`Python: ${pythonVersion(pythonPath) || pythonPath}`);
885
- saveConfig(next, env, platform);
886
- installVendianPackages({ pythonPath, venvPath, config: next, env, platform });
887
- const updated = { ...next, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() };
888
- refreshAgentDocsWorkspaces({ config: updated, venvPath, env, platform });
889
- if (!verifyVendianImports(pythonPath)) {
890
- throw new Error("Vendian packages installed, but import verification failed.");
891
- }
892
- const vendianPath = venvVendian(venvPath, platform);
893
- if (!fs7.existsSync(vendianPath)) {
894
- throw new Error("Vendian executable was not found after install.");
895
- }
896
- console.log("");
897
- for (const line of setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl })) {
898
- console.log(line);
899
- }
900
- }
901
- function savePackageCredentials(config, packageCredentials, { platform = process.platform } = {}) {
902
- if (!packageCredentials?.token) {
903
- return false;
904
- }
905
- config.gitlabHost = packageCredentials.gitlabHost || config.gitlabHost;
906
- config.gitlabUsername = packageCredentials.username || config.gitlabUsername;
907
- config.sdkPublicProjectId = packageCredentials.sdkProjectId || config.sdkPublicProjectId;
908
- config.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || config.sdkRuntimeProjectId;
909
- config.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || config.vendianAgentsVersion;
910
- config.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || config.vendianAgentsRuntimeVersion;
911
- const secret = savePackageTokenSecret(packageCredentials.token, config, platform);
912
- if (secret.ok) {
913
- Object.assign(config, secret.config || {});
914
- delete config.gitlabToken;
915
- console.log("Package access saved.");
916
- } else {
917
- config.gitlabToken = packageCredentials.token;
918
- console.log("Package access saved in the local Vendian CLI config file.");
919
- }
920
- return true;
921
- }
922
- async function refreshPackageAccessFromCloudAuth({
923
- config,
924
- auth,
925
- env = process.env,
926
- platform = process.platform,
927
- save = true
928
- } = {}) {
929
- const currentConfig = config || loadConfig(env, platform);
930
- const registry = registryConfig(currentConfig, env, platform);
931
- if (registry.token) {
932
- return { config: currentConfig, refreshed: false };
933
- }
934
- const status = auth || activeCloudAuthStatus({ env, platform });
935
- if (!status.authenticated || !status.apiUrl || !status.profile?.access_token) {
936
- return { config: currentConfig, refreshed: false };
937
- }
938
- const packageCredentials = await fetchPackageCredentials({
939
- apiUrl: status.apiUrl,
940
- accessToken: status.profile.access_token
941
- });
942
- if (!packageCredentials?.token) {
943
- return { config: currentConfig, refreshed: false };
944
- }
945
- const next = { ...currentConfig };
946
- const stored = savePackageCredentials(next, packageCredentials, { platform });
947
- if (stored && save) {
948
- saveConfig(next, env, platform);
949
- }
950
- return { config: next, refreshed: stored };
951
- }
952
- function setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl } = {}) {
953
- const lines = ["Vendian login complete."];
954
- if (cloudAuthApiUrl) {
955
- lines.push(`Connected to ${cloudAuthApiUrl}.`);
956
- lines.push("Next: vendian cloud local serve --agents-dir ./agents");
957
- return lines;
958
- }
959
- lines.push(`Next: ${cloudAuthLoginCommand({ backend, apiUrl })}`);
960
- lines.push("Then: vendian cloud local serve --agents-dir ./agents");
961
- return lines;
962
- }
963
- function cloudAuthLoginCommand({ backend, apiUrl } = {}) {
964
- if (apiUrl) {
965
- return `vendian cloud auth login --api-url ${apiUrl}`;
966
- }
967
- if (backend) {
968
- return `vendian cloud auth login --backend ${backend}`;
969
- }
970
- return "vendian cloud auth login";
971
- }
972
-
973
- // src/forward.js
974
- var AUTO_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
975
- async function forwardToPythonVendian(args, { env = process.env, platform = process.platform } = {}) {
976
- const invocation = await preparePythonVendianInvocation(args, { env, platform });
977
- const code = await spawnForward(invocation.command, invocation.args, {
978
- env: invocation.env
979
- });
980
- recordForwardedDocsCommand(args, code, { env, platform });
981
- process.exitCode = code;
982
- }
983
- async function preparePythonVendianInvocation(args, {
984
- env = process.env,
985
- platform = process.platform,
986
- onProgress = null,
987
- skipAutoUpdate = false
988
- } = {}) {
989
- const venvPath = managedVenvPath(env, platform);
990
- const vendianPath = venvVendian(venvPath, platform);
991
- reportProgress(onProgress, "Checking managed Python runtime");
992
- if (!fs8.existsSync(vendianPath)) {
993
- throw new Error("Vendian is not set up yet. Run `vendian login` first.");
994
- }
995
- const loadedConfig = loadConfig(env, platform);
996
- const requiresPackageAccess = commandNeedsPackageAccess(args);
997
- if (requiresPackageAccess) {
998
- reportProgress(onProgress, "Refreshing package access");
999
- }
1000
- const refreshed = requiresPackageAccess ? await refreshPackageAccessFromCloudAuth({ config: loadedConfig, env, platform }) : { config: loadedConfig };
1001
- if (!skipAutoUpdate) {
1002
- maybeAutoUpdateManagedEnv({ env, platform, venvPath, onProgress });
1003
- }
1004
- const config = refreshed.config;
1005
- reportProgress(onProgress, "Preparing package indexes");
1006
- const registry = registryConfig(config, env, platform);
1007
- if (requiresPackageAccess && !registry.token) {
1008
- throw new Error("Package access is missing. Run `vendian login` or `vendian update` before starting local agents.");
1009
- }
1010
- reportProgress(onProgress, "Launching local serve daemon");
1011
- return {
1012
- 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;
1144
+ console.log("Using saved package access.");
1064
1145
  }
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;
1146
+ const installRegistry = registryConfig(next, env, platform);
1147
+ if (!installRegistry.token) {
1148
+ throw new Error("Package access is missing. Run interactive `vendian login` to sign in.");
1069
1149
  }
1070
- const registry = registryConfig(config, env, platform);
1071
- if (!registry.token) {
1072
- return false;
1150
+ const python = findPython(platform);
1151
+ if (!python) {
1152
+ throw new Error("Python 3.11+ was not found. Install Python 3.11 or newer, then rerun `vendian login`.");
1073
1153
  }
1074
- const pythonPath = venvPython(venvPath, platform);
1075
- if (!fs8.existsSync(pythonPath)) {
1076
- return false;
1154
+ const venvPath = managedVenvPath(env, platform);
1155
+ console.log(`Managed environment: ${venvPath}`);
1156
+ const pythonPath = ensureVenv(venvPath, python, platform);
1157
+ console.log(`Python: ${pythonVersion(pythonPath) || pythonPath}`);
1158
+ saveConfig(next, env, platform);
1159
+ installVendianPackages({ pythonPath, venvPath, config: next, env, platform });
1160
+ const updated = { ...next, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() };
1161
+ refreshAgentDocsWorkspaces({ config: updated, venvPath, env, platform });
1162
+ if (!verifyVendianImports(pythonPath)) {
1163
+ throw new Error("Vendian packages installed, but import verification failed.");
1077
1164
  }
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;
1165
+ const vendianPath = venvVendian(venvPath, platform);
1166
+ if (!fs8.existsSync(vendianPath)) {
1167
+ throw new Error("Vendian executable was not found after install.");
1168
+ }
1169
+ console.log("");
1170
+ for (const line of setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl })) {
1171
+ console.log(line);
1096
1172
  }
1097
1173
  }
1098
- function reportProgress(onProgress, message) {
1099
- if (typeof onProgress === "function") {
1100
- onProgress(message);
1174
+ function savePackageCredentials(config, packageCredentials, { platform = process.platform } = {}) {
1175
+ if (!packageCredentials?.token) {
1176
+ return false;
1177
+ }
1178
+ config.gitlabHost = packageCredentials.gitlabHost || config.gitlabHost;
1179
+ config.gitlabUsername = packageCredentials.username || config.gitlabUsername;
1180
+ config.sdkPublicProjectId = packageCredentials.sdkProjectId || config.sdkPublicProjectId;
1181
+ config.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || config.sdkRuntimeProjectId;
1182
+ config.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || config.vendianAgentsVersion;
1183
+ config.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || config.vendianAgentsRuntimeVersion;
1184
+ const secret = savePackageTokenSecret(packageCredentials.token, config, platform);
1185
+ if (secret.ok) {
1186
+ Object.assign(config, secret.config || {});
1187
+ delete config.gitlabToken;
1188
+ console.log("Package access saved.");
1189
+ } else {
1190
+ config.gitlabToken = packageCredentials.token;
1191
+ console.log("Package access saved in the local Vendian CLI config file.");
1101
1192
  }
1193
+ return true;
1102
1194
  }
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.33" : 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
1195
+ async function refreshPackageAccessFromCloudAuth({
1196
+ config,
1197
+ auth,
1198
+ env = process.env,
1199
+ platform = process.platform,
1200
+ save = true
1141
1201
  } = {}) {
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
- });
1202
+ const currentConfig = config || loadConfig(env, platform);
1203
+ const registry = registryConfig(currentConfig, env, platform);
1204
+ if (registry.token) {
1205
+ return { config: currentConfig, refreshed: false };
1160
1206
  }
1161
- addCandidate(path6.join(start, "agents"));
1162
- if (hasDirectManifest(start)) {
1163
- addCandidate(start);
1207
+ const status = auth || activeCloudAuthStatus({ env, platform });
1208
+ if (!status.authenticated || !status.apiUrl || !status.profile?.access_token) {
1209
+ return { config: currentConfig, refreshed: false };
1164
1210
  }
1165
- for (const parent of parentDirs(start, 3)) {
1166
- addCandidate(path6.join(parent, "agents"));
1211
+ const packageCredentials = await fetchPackageCredentials({
1212
+ apiUrl: status.apiUrl,
1213
+ accessToken: status.profile.access_token
1214
+ });
1215
+ if (!packageCredentials?.token) {
1216
+ return { config: currentConfig, refreshed: false };
1167
1217
  }
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
- }
1218
+ const next = { ...currentConfig };
1219
+ const stored = savePackageCredentials(next, packageCredentials, { platform });
1220
+ if (stored && save) {
1221
+ saveConfig(next, env, platform);
1180
1222
  }
1181
- return candidates.slice(0, limit);
1223
+ return { config: next, refreshed: stored };
1182
1224
  }
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
- });
1225
+ function setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl } = {}) {
1226
+ const lines = ["Vendian login complete."];
1227
+ if (cloudAuthApiUrl) {
1228
+ lines.push(`Connected to ${cloudAuthApiUrl}.`);
1229
+ lines.push("Next: vendian cloud local serve --agents-dir ./agents");
1230
+ return lines;
1208
1231
  }
1209
- return folders.sort((a, b) => a.path.localeCompare(b.path)).slice(0, limit);
1232
+ lines.push(`Next: ${cloudAuthLoginCommand({ backend, apiUrl })}`);
1233
+ lines.push("Then: vendian cloud local serve --agents-dir ./agents");
1234
+ return lines;
1210
1235
  }
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;
1217
- }
1236
+ function cloudAuthLoginCommand({ backend, apiUrl } = {}) {
1237
+ if (apiUrl) {
1238
+ return `vendian cloud auth login --api-url ${apiUrl}`;
1218
1239
  }
1219
- return count;
1220
- }
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
- }
1240
+ if (backend) {
1241
+ return `vendian cloud auth login --backend ${backend}`;
1255
1242
  }
1256
- return manifests;
1243
+ return "vendian cloud auth login";
1244
+ }
1245
+
1246
+ // src/forward.js
1247
+ var AUTO_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1248
+ async function forwardToPythonVendian(args, { env = process.env, platform = process.platform } = {}) {
1249
+ const invocation = await preparePythonVendianInvocation(args, { env, platform });
1250
+ const code = await spawnForward(invocation.command, invocation.args, {
1251
+ env: invocation.env
1252
+ });
1253
+ recordForwardedDocsCommand(args, code, { env, platform });
1254
+ process.exitCode = code;
1257
1255
  }
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;
1256
+ async function preparePythonVendianInvocation(args, {
1257
+ env = process.env,
1258
+ platform = process.platform,
1259
+ onProgress = null,
1260
+ skipAutoUpdate = false
1261
+ } = {}) {
1262
+ const venvPath = managedVenvPath(env, platform);
1263
+ const vendianPath = venvVendian(venvPath, platform);
1264
+ reportProgress(onProgress, "Checking managed Python runtime");
1265
+ if (!fs9.existsSync(vendianPath)) {
1266
+ throw new Error("Vendian is not set up yet. Run `vendian login` first.");
1267
+ }
1268
+ const loadedConfig = loadConfig(env, platform);
1269
+ const requiresPackageAccess = commandNeedsPackageAccess(args);
1270
+ if (requiresPackageAccess) {
1271
+ reportProgress(onProgress, "Refreshing package access");
1272
+ }
1273
+ const refreshed = requiresPackageAccess ? await refreshPackageAccessFromCloudAuth({ config: loadedConfig, env, platform }) : { config: loadedConfig };
1274
+ if (!skipAutoUpdate) {
1275
+ maybeAutoUpdateManagedEnv({ env, platform, venvPath, onProgress });
1276
+ }
1277
+ const config = refreshed.config;
1278
+ reportProgress(onProgress, "Preparing package indexes");
1279
+ const registry = registryConfig(config, env, platform);
1280
+ if (requiresPackageAccess && !registry.token) {
1281
+ throw new Error("Package access is missing. Run `vendian login` or `vendian update` before starting local agents.");
1282
+ }
1283
+ reportProgress(onProgress, "Launching local serve daemon");
1284
+ return {
1285
+ command: vendianPath,
1286
+ args,
1287
+ env: {
1288
+ ...env,
1289
+ ...packageIndexEnv(config, env, platform)
1268
1290
  }
1269
- current = parent;
1291
+ };
1292
+ }
1293
+ function commandNeedsPackageAccess(args = []) {
1294
+ return args[0] === "cloud" && args[1] === "local" && ["serve", "run"].includes(args[2]);
1295
+ }
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) {
@@ -2080,59 +2199,28 @@ function processExitMessage(event) {
2080
2199
  return `Agent serve exited with code ${event.code}`;
2081
2200
  }
2082
2201
  if (event?.signal) {
2083
- return `Agent serve exited from ${event.signal}`;
2084
- }
2085
- return "Agent serve exited";
2086
- }
2087
- 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);
2202
+ return `Agent serve exited from ${event.signal}`;
2134
2203
  }
2135
- return args;
2204
+ return "Agent serve exited";
2205
+ }
2206
+ function agentPrepareLogMessage(event) {
2207
+ if (event.type === "agent_prepare_started") {
2208
+ return "Preparing agent";
2209
+ }
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,58 +2331,561 @@ 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.35" : 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
+ hasAgentPy: fs11.existsSync(path8.join(folder.absolutePath, "agent.py")),
2608
+ hasRequirements: fs11.existsSync(path8.join(folder.absolutePath, "requirements.txt")),
2609
+ runtimeStatus: runtime.status
2610
+ };
2611
+ });
2612
+ jsonResponse(res, { agents, agentsDir });
2613
+ }
2614
+ function apiLogs(req, res, parsed) {
2615
+ const since = parseInt(parsed.searchParams.get("since") || "0", 10);
2616
+ const lines = serveLogs.slice(since);
2617
+ jsonResponse(res, { logs: lines, total: serveLogs.length, since });
2618
+ }
2619
+ function apiAuth(req, res) {
2620
+ const active = activeCloudAuthStatus();
2621
+ const backends = Object.entries(BACKEND_TARGETS).filter(([key]) => !["localhost", "production"].includes(key)).map(([key, url]) => {
2622
+ const status = cloudAuthStatus({ backend: key });
2623
+ return {
2624
+ key,
2625
+ url,
2626
+ authenticated: status.authenticated,
2627
+ email: status.email || null,
2628
+ active: status.apiUrl === active.apiUrl && active.authenticated
2271
2629
  };
2630
+ });
2631
+ jsonResponse(res, {
2632
+ authenticated: active.authenticated,
2633
+ email: active.email || null,
2634
+ apiUrl: active.apiUrl || null,
2635
+ backends
2636
+ });
2637
+ }
2638
+ async function apiWorkspaces(req, res) {
2639
+ const result = await listCloudWorkspaces({ timeoutMs: 15e3 });
2640
+ jsonResponse(res, result);
2641
+ }
2642
+ async function apiAuthSwitch(req, res, body) {
2643
+ const backend = body.backend;
2644
+ if (!backend || !BACKEND_TARGETS[backend]) {
2645
+ return jsonResponse(res, { ok: false, error: `Unknown backend: ${backend}` }, 400);
2272
2646
  }
2273
2647
  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}` };
2648
+ const { activateCloudProfile: activateCloudProfile2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
2649
+ const result = activateCloudProfile2({ backend });
2650
+ if (result?.activated) {
2651
+ jsonResponse(res, { ok: true, switched: true, backend });
2652
+ } else {
2653
+ jsonResponse(res, { ok: false, needsLogin: true, backend, url: BACKEND_TARGETS[backend] });
2654
+ }
2655
+ } catch (err) {
2656
+ jsonResponse(res, { ok: false, error: err.message });
2278
2657
  }
2279
2658
  }
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);
2659
+ async function apiValidate(req, res, body) {
2660
+ const targetPath = body.path || agentsDir;
2661
+ const invocation = await preparePythonVendianInvocation(
2662
+ ["validate", targetPath, "--json", "--no-runtime"],
2663
+ { skipAutoUpdate: true }
2664
+ );
2665
+ const result = await runCaptureAsync(invocation.command, invocation.args, { env: invocation.env });
2666
+ try {
2667
+ const parsed = JSON.parse(result.stdout);
2668
+ jsonResponse(res, parsed);
2669
+ } catch {
2670
+ jsonResponse(res, {
2671
+ valid: false,
2672
+ errors: result.stderr ? [result.stderr.trim()] : ["Validation failed"],
2673
+ stdout: result.stdout
2674
+ });
2675
+ }
2288
2676
  }
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}`;
2677
+ async function apiServeStart(req, res, body) {
2678
+ if (serveChild && !serveChild.killed) {
2679
+ return jsonResponse(res, { ok: false, error: "Already serving" });
2680
+ }
2681
+ const targetDir = body.agentsDir || agentsDir;
2682
+ const targetCollection = body.collectionId || collectionId;
2683
+ try {
2684
+ const invocation = await preparePythonVendianInvocation(
2685
+ buildLocalServeEventStreamArgs({ agentsDir: targetDir, collectionId: targetCollection }),
2686
+ { onProgress: null }
2687
+ );
2688
+ serveState = initialServeState();
2689
+ serveLogs = [];
2690
+ logStore = createServeLogStore({ agentsDir: targetDir, collectionId: targetCollection });
2691
+ serveChild = spawn3(invocation.command, invocation.args, {
2692
+ env: invocation.env,
2693
+ stdio: ["ignore", "pipe", "pipe"],
2694
+ shell: false
2695
+ });
2696
+ let buffer = "";
2697
+ serveChild.stdout.setEncoding("utf8");
2698
+ serveChild.stdout.on("data", (chunk) => {
2699
+ buffer += chunk;
2700
+ const lines = buffer.split(/\r?\n/);
2701
+ buffer = lines.pop() || "";
2702
+ for (const line of lines) {
2703
+ serveLogs.push(line);
2704
+ if (serveLogs.length > 5e3) serveLogs.shift();
2705
+ try {
2706
+ const event = parseServeEventLine(line);
2707
+ if (event) {
2708
+ serveState = applyServeEvent(serveState, event);
2709
+ try {
2710
+ logStore?.append(event);
2711
+ } catch {
2712
+ }
2713
+ }
2714
+ } catch {
2715
+ }
2716
+ }
2717
+ });
2718
+ serveChild.stderr.setEncoding("utf8");
2719
+ serveChild.stderr.on("data", (chunk) => {
2720
+ const text = chunk.trim();
2721
+ if (text) {
2722
+ serveLogs.push(`[stderr] ${text}`);
2723
+ if (serveLogs.length > 5e3) serveLogs.shift();
2724
+ }
2725
+ });
2726
+ serveChild.on("exit", (code, signal) => {
2727
+ serveState = { ...serveState, stopped: true, connected: false };
2728
+ serveLogs.push(`[process] Exited code=${code} signal=${signal || "none"}`);
2729
+ serveChild = null;
2730
+ try {
2731
+ logStore?.compact();
2732
+ } catch {
2733
+ }
2734
+ });
2735
+ jsonResponse(res, { ok: true, pid: serveChild.pid });
2736
+ } catch (err) {
2737
+ jsonResponse(res, { ok: false, error: err.message });
2738
+ }
2293
2739
  }
2294
- function stringValue2(value) {
2295
- return value == null ? "" : String(value).trim();
2740
+ function apiServeStop(req, res) {
2741
+ if (!serveChild || serveChild.killed) {
2742
+ return jsonResponse(res, { ok: true, wasRunning: false });
2743
+ }
2744
+ serveChild.kill("SIGTERM");
2745
+ setTimeout(() => {
2746
+ if (serveChild && !serveChild.killed) {
2747
+ serveChild.kill("SIGKILL");
2748
+ }
2749
+ }, 3e3);
2750
+ jsonResponse(res, { ok: true, wasRunning: true });
2751
+ }
2752
+ async function apiCreate(req, res, body) {
2753
+ const name = (body.name || "").trim();
2754
+ if (!name) {
2755
+ return jsonResponse(res, { ok: false, error: "Name is required" }, 400);
2756
+ }
2757
+ try {
2758
+ const invocation = await preparePythonVendianInvocation(
2759
+ ["create", name, "--output-dir", agentsDir],
2760
+ { skipAutoUpdate: true }
2761
+ );
2762
+ const result = await runCaptureAsync(invocation.command, invocation.args, { env: invocation.env });
2763
+ if (result.code === 0) {
2764
+ jsonResponse(res, { ok: true, name, output: result.stdout });
2765
+ } else {
2766
+ jsonResponse(res, { ok: false, error: result.stderr || result.stdout || "Create failed" });
2767
+ }
2768
+ } catch (err) {
2769
+ jsonResponse(res, { ok: false, error: err.message });
2770
+ }
2771
+ }
2772
+ function jsonResponse(res, data, status = 200) {
2773
+ const body = JSON.stringify(data);
2774
+ res.writeHead(status, { "Content-Type": "application/json" });
2775
+ res.end(body);
2776
+ }
2777
+ function readBody(req) {
2778
+ return new Promise((resolve) => {
2779
+ const chunks = [];
2780
+ req.on("data", (chunk) => chunks.push(chunk));
2781
+ req.on("end", () => {
2782
+ const raw = Buffer.concat(chunks).toString("utf8");
2783
+ try {
2784
+ resolve(JSON.parse(raw));
2785
+ } catch {
2786
+ resolve({});
2787
+ }
2788
+ });
2789
+ });
2790
+ }
2791
+ function runCaptureAsync(command, args, options = {}) {
2792
+ return new Promise((resolve) => {
2793
+ const child = spawn3(command, args, {
2794
+ stdio: ["ignore", "pipe", "pipe"],
2795
+ shell: false,
2796
+ ...options
2797
+ });
2798
+ let stdout = "";
2799
+ let stderr = "";
2800
+ child.stdout.on("data", (d) => {
2801
+ stdout += d;
2802
+ });
2803
+ child.stderr.on("data", (d) => {
2804
+ stderr += d;
2805
+ });
2806
+ child.on("exit", (code) => {
2807
+ resolve({ code: code ?? 1, stdout, stderr });
2808
+ });
2809
+ child.on("error", (err) => {
2810
+ resolve({ code: 1, stdout, stderr: err.message });
2811
+ });
2812
+ });
2813
+ }
2814
+ function parseManifestBasic(manifestPath) {
2815
+ try {
2816
+ const content = fs11.readFileSync(manifestPath, "utf8");
2817
+ const get = (key) => {
2818
+ const m = content.match(new RegExp(`^${key}\\s*:\\s*['"]?([^'"\\n#]+?)['"]?\\s*$`, "m"));
2819
+ return m ? m[1].trim() : "";
2820
+ };
2821
+ const getList = (key) => {
2822
+ const m = content.match(new RegExp(`^${key}\\s*:`, "m"));
2823
+ if (!m) return [];
2824
+ const after = content.slice(m.index + m[0].length);
2825
+ const lines = after.split("\n");
2826
+ const items = [];
2827
+ for (const line of lines) {
2828
+ if (/^\s+-\s/.test(line) || /^\s+\w+\s*:/.test(line)) {
2829
+ const val = line.replace(/^\s+[-]?\s*/, "").replace(/:.*$/, "").trim();
2830
+ if (val) items.push(val);
2831
+ } else if (/^\S/.test(line) && items.length > 0) {
2832
+ break;
2833
+ }
2834
+ }
2835
+ return items;
2836
+ };
2837
+ return {
2838
+ id: get("id"),
2839
+ name: get("name"),
2840
+ description: get("description"),
2841
+ version: get("version"),
2842
+ triggers: getList("triggers"),
2843
+ credentials: getList("credentials")
2844
+ };
2845
+ } catch {
2846
+ return {};
2847
+ }
2848
+ }
2849
+ function formatDaemonEvent(event) {
2850
+ switch (event.type) {
2851
+ case "daemon_registered":
2852
+ return "Connected to backend";
2853
+ case "daemon_event_stream_connected":
2854
+ return "Event stream connected";
2855
+ case "daemon_event_stream_lost":
2856
+ return `Event stream lost: ${event.error || "unknown"}`;
2857
+ case "inventory_synced":
2858
+ return `Inventory synced (${event.agentCount || 0} agents)`;
2859
+ case "agent_discovery_completed":
2860
+ return `Discovered ${event.agentCount || 0} agents`;
2861
+ case "backend_connection_lost":
2862
+ return `Connection lost: ${event.error || event.activity || "unknown"}`;
2863
+ case "backend_connection_restored":
2864
+ return "Connection restored";
2865
+ case "retry_scheduled":
2866
+ return `Retrying ${event.activity || "operation"} in ${event.delaySeconds || "?"}s`;
2867
+ default:
2868
+ return event.type;
2869
+ }
2870
+ }
2871
+ function openUrl(url) {
2872
+ const cmd = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
2873
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
2874
+ const shell = process.platform === "win32" ? "cmd.exe" : void 0;
2875
+ spawn3(shell || cmd, shell ? args : [url], { detached: true, stdio: "ignore" }).unref();
2296
2876
  }
2297
2877
 
2878
+ // src/tui.js
2879
+ init_constants();
2880
+ init_auth();
2881
+ import { spawnSync as spawnSync4 } from "node:child_process";
2882
+ import fs12 from "node:fs";
2883
+ import path9 from "node:path";
2884
+ import readline from "node:readline";
2885
+
2886
+ // src/npm-update.js
2887
+ var NPM_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
2888
+
2298
2889
  // src/ui/figures.js
2299
2890
  var supportsUnicode = process.platform !== "win32" || Boolean(
2300
2891
  process.env.WT_SESSION || // Windows Terminal
@@ -2602,10 +3193,16 @@ var COMMAND_GROUPS = [
2602
3193
  { cmd: "vendian models", desc: "List available AI models" }
2603
3194
  ] },
2604
3195
  { section: "Running agents", commands: [
3196
+ { cmd: "vendian dev", desc: "Open the dev UI in your browser" },
2605
3197
  { cmd: "vendian cloud local run --collection-id ID --path . --input-json '{}'", desc: "Run one agent" },
2606
3198
  { cmd: "vendian cloud local serve --agents-dir .", desc: "Start agents" },
2607
3199
  { cmd: "vendian login --backend staging", desc: "Sign in to staging" }
2608
3200
  ] },
3201
+ { section: "Syncing & deploying", commands: [
3202
+ { cmd: "vendian cloud local push --collection-id ID --project-id PID", desc: "Push code to project repo" },
3203
+ { cmd: "vendian cloud local push --branch feature/x --from dev", desc: "Push to a new branch" },
3204
+ { cmd: "vendian cloud local deploy --collection-id ID --project-id PID", desc: "Deploy to cloud" }
3205
+ ] },
2609
3206
  { section: "Maintenance", commands: [
2610
3207
  { cmd: "vendian doctor", desc: "Check if everything is set up" },
2611
3208
  { cmd: "vendian update", desc: "Update to the latest version" }
@@ -2649,7 +3246,7 @@ async function pickProject({ env, platform, candidates }) {
2649
3246
  print(col.bold(" Which project do you want to run?"));
2650
3247
  print("");
2651
3248
  const items = candidates.map((candidate) => {
2652
- const name = readManifestName(candidate.absolutePath) || friendlyName(path8.basename(candidate.absolutePath) || candidate.path);
3249
+ const name = readManifestName(candidate.absolutePath) || friendlyName(path9.basename(candidate.absolutePath) || candidate.path);
2653
3250
  return `${name.padEnd(32)} ${col.gray(`${candidate.agentCount} agent${candidate.agentCount === 1 ? "" : "s"}`)}`;
2654
3251
  });
2655
3252
  const idx = await pickMenu(items, { allowBack: true });
@@ -2663,7 +3260,7 @@ async function pickAgentsFromFolder({ env, platform, candidate }) {
2663
3260
  if (folders.length <= 1) return { agentsDir: candidate.path };
2664
3261
  const named = folders.map((f) => ({
2665
3262
  ...f,
2666
- displayName: readManifestName(f.absolutePath) || friendlyName(f.name || path8.basename(f.path))
3263
+ displayName: readManifestName(f.absolutePath) || friendlyName(f.name || path9.basename(f.path))
2667
3264
  }));
2668
3265
  clearScreen();
2669
3266
  printHeader({ env, platform });
@@ -2686,19 +3283,19 @@ async function pickSingleAgentToRun({ env, platform, candidates }) {
2686
3283
  print(col.yellow(" Couldn't find any agents."));
2687
3284
  const input = await ask("Agent folder", "./agents/my-agent");
2688
3285
  if (!input) return null;
2689
- return { path: input, displayName: friendlyName(path8.basename(input) || "Agent") };
3286
+ return { path: input, displayName: friendlyName(path9.basename(input) || "Agent") };
2690
3287
  }
2691
3288
  const agents = candidates.flatMap((candidate) => {
2692
3289
  const folders = findAgentFolders(candidate.absolutePath);
2693
3290
  if (folders.length > 0) {
2694
3291
  return folders.map((folder) => ({
2695
3292
  path: folder.path,
2696
- displayName: readManifestName(folder.absolutePath) || friendlyName(folder.name || path8.basename(folder.path))
3293
+ displayName: readManifestName(folder.absolutePath) || friendlyName(folder.name || path9.basename(folder.path))
2697
3294
  }));
2698
3295
  }
2699
3296
  return [{
2700
3297
  path: candidate.path,
2701
- displayName: readManifestName(candidate.absolutePath) || friendlyName(path8.basename(candidate.absolutePath) || candidate.path)
3298
+ displayName: readManifestName(candidate.absolutePath) || friendlyName(path9.basename(candidate.absolutePath) || candidate.path)
2702
3299
  }];
2703
3300
  });
2704
3301
  if (agents.length === 1) return agents[0];
@@ -2798,7 +3395,7 @@ async function showServe({ env, platform }) {
2798
3395
  const candidates = findAgentDirectoryCandidates();
2799
3396
  const picked = await pickAgentsToRun({ env, platform, candidates });
2800
3397
  if (!picked) return "home";
2801
- const { agentsDir } = picked;
3398
+ const { agentsDir: agentsDir2 } = picked;
2802
3399
  const workspace = await chooseCloudWorkspace({
2803
3400
  env,
2804
3401
  platform,
@@ -2806,9 +3403,9 @@ async function showServe({ env, platform }) {
2806
3403
  pickerTitle: "Which project do you want to run?"
2807
3404
  });
2808
3405
  if (!workspace) return "home";
2809
- return await runServeDashboard({ env, platform, agentsDir, collectionId: workspace.id, wsLabel: workspace.label });
3406
+ return await runServeDashboard({ env, platform, agentsDir: agentsDir2, collectionId: workspace.id, wsLabel: workspace.label });
2810
3407
  }
2811
- async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLabel = "" }) {
3408
+ async function runServeDashboard({ env, platform, agentsDir: agentsDir2, collectionId: collectionId2, wsLabel = "" }) {
2812
3409
  let state = initialServeState();
2813
3410
  let child = null;
2814
3411
  let ctrlCArmed = false;
@@ -2817,7 +3414,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2817
3414
  let stopSignalAttempt = 0;
2818
3415
  let stopTimer = null;
2819
3416
  let dashboardClosed = false;
2820
- let logStore = null;
3417
+ let logStore2 = null;
2821
3418
  let lastAgentOnlinePrint = 0;
2822
3419
  let agentsSeenOnline = false;
2823
3420
  let initialSetupDone = false;
@@ -2827,8 +3424,8 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2827
3424
  print("");
2828
3425
  print(col.gray("\u2550".repeat(W)));
2829
3426
  print(` ${col.cyan(col.bold("\u2191 VENDIAN"))} ${col.bold("Serving agents")}`);
2830
- print(` ${col.gray("Dir:")} ${agentsDir}`);
2831
- print(` ${col.gray("Workspace:")} ${wsLabel || collectionId || "\u2014"}`);
3427
+ print(` ${col.gray("Dir:")} ${agentsDir2}`);
3428
+ print(` ${col.gray("Workspace:")} ${wsLabel || collectionId2 || "\u2014"}`);
2832
3429
  print(col.gray(hr(W)));
2833
3430
  print(col.gray(" S stop gracefully | ^c ^c exit"));
2834
3431
  print(col.gray(hr(W)));
@@ -2855,7 +3452,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2855
3452
  if (!event) return;
2856
3453
  const type = String(event.type || "");
2857
3454
  const agentPath = String(event.relativePath || event.path || "");
2858
- const agentName = agentPath ? friendlyName(path8.basename(agentPath)) || agentPath : ".";
3455
+ const agentName = agentPath ? friendlyName(path9.basename(agentPath)) || agentPath : ".";
2859
3456
  if (type === "daemon_wake_received") return;
2860
3457
  if (type === "lease_claim_no_job") return;
2861
3458
  if (type === "dispatch_job_received" || type === "dispatch_job_acked") return;
@@ -2866,7 +3463,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2866
3463
  const now = Date.now();
2867
3464
  if (!hasProblems && allOnline && agentsSeenOnline && now - lastAgentOnlinePrint < 12e4) return;
2868
3465
  for (const a of agents) {
2869
- const name = friendlyName(path8.basename(a.relativePath || ".")) || a.manifestName || a.relativePath || "?";
3466
+ const name = friendlyName(path9.basename(a.relativePath || ".")) || a.manifestName || a.relativePath || "?";
2870
3467
  if (a.status === "error") {
2871
3468
  printAgent(name, col.red(`\u2716 Error: ${a.errorMessage || ""}`));
2872
3469
  continue;
@@ -2945,15 +3542,15 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2945
3542
  }
2946
3543
  }
2947
3544
  try {
2948
- logStore = createServeLogStore({ agentsDir, collectionId, env, platform });
2949
- const historicalLogs = logStore.load();
3545
+ logStore2 = createServeLogStore({ agentsDir: agentsDir2, collectionId: collectionId2, env, platform });
3546
+ const historicalLogs = logStore2.load();
2950
3547
  if (historicalLogs.length > 0) {
2951
3548
  const agentLogs = mergeAgentLogRecords(state.agentLogs, historicalLogs);
2952
3549
  state = { ...state, agentLogs, agentRunState: { ...agentRunStateFromLogs(agentLogs, { includeRunning: false }), ...state.agentRunState } };
2953
3550
  }
2954
3551
  child = await spawnLocalServeEventStream({
2955
- agentsDir,
2956
- collectionId,
3552
+ agentsDir: agentsDir2,
3553
+ collectionId: collectionId2,
2957
3554
  env,
2958
3555
  platform,
2959
3556
  onProgress: (msg) => {
@@ -2962,7 +3559,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2962
3559
  }
2963
3560
  });
2964
3561
  process.stdout.write("\r" + " ".repeat(80) + "\r");
2965
- printDaemon("serve_started", col.gray(`dir=${agentsDir}`));
3562
+ printDaemon("serve_started", col.gray(`dir=${agentsDir2}`));
2966
3563
  } catch (error) {
2967
3564
  setCursorVisible(true);
2968
3565
  print(col.red(` ${fig.cross} Failed to start: ${errMsg(error)}`));
@@ -2991,9 +3588,13 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
2991
3588
  if (!stopRequested || dashboardClosed || !childIsRunning()) return;
2992
3589
  stopSignalAttempt += 1;
2993
3590
  const signal = stopSignalForAttempt(stopSignalAttempt);
2994
- try {
2995
- child.kill(signal);
2996
- } catch {
3591
+ if (process.platform === "win32") {
3592
+ killProcessTree(child);
3593
+ } else {
3594
+ try {
3595
+ child.kill(signal);
3596
+ } catch {
3597
+ }
2997
3598
  }
2998
3599
  if (signal !== "SIGKILL") scheduleStopEscalation();
2999
3600
  }, 4e3);
@@ -3009,18 +3610,25 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
3009
3610
  print(col.yellow("\n Stopping your agents\u2026"));
3010
3611
  clearTimeout(ctrlCArmTimer);
3011
3612
  ctrlCArmed = false;
3012
- try {
3013
- child.kill(stopSignalForAttempt(stopSignalAttempt));
3014
- } catch {
3613
+ if (process.platform === "win32") {
3614
+ killProcessTree(child);
3615
+ } else {
3616
+ try {
3617
+ child.kill(stopSignalForAttempt(stopSignalAttempt));
3618
+ } catch {
3619
+ }
3620
+ scheduleStopEscalation();
3015
3621
  }
3016
- scheduleStopEscalation();
3017
3622
  }
3018
3623
  if (process.stdin.isTTY) {
3019
3624
  let onKeypress = function(str, key) {
3020
3625
  if (!key) return;
3021
3626
  if (key.ctrl && key.name === "c") {
3022
3627
  if (ctrlCArmed) {
3023
- child?.kill("SIGINT");
3628
+ try {
3629
+ child?.kill("SIGINT");
3630
+ } catch (error) {
3631
+ }
3024
3632
  finish("exit");
3025
3633
  return;
3026
3634
  }
@@ -3060,7 +3668,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
3060
3668
  const event = parseServeEventLine(line);
3061
3669
  if (event) {
3062
3670
  try {
3063
- logStore?.append(event);
3671
+ logStore2?.append(event);
3064
3672
  } catch {
3065
3673
  }
3066
3674
  state = applyServeEvent(state, event);
@@ -3102,7 +3710,7 @@ async function runServeDashboard({ env, platform, agentsDir, collectionId, wsLab
3102
3710
  print(col.gray("\n Agents stopped."));
3103
3711
  }
3104
3712
  try {
3105
- logStore?.compact();
3713
+ logStore2?.compact();
3106
3714
  } catch {
3107
3715
  }
3108
3716
  removeKeypress();
@@ -3238,7 +3846,7 @@ function endpointErrorStatus(error) {
3238
3846
  }
3239
3847
  function buildLocalRunArgs({
3240
3848
  agentPath = ".",
3241
- collectionId = "",
3849
+ collectionId: collectionId2 = "",
3242
3850
  inputJson = "{}"
3243
3851
  } = {}) {
3244
3852
  return [
@@ -3246,7 +3854,7 @@ function buildLocalRunArgs({
3246
3854
  "local",
3247
3855
  "run",
3248
3856
  "--collection-id",
3249
- collectionId,
3857
+ collectionId2,
3250
3858
  "--path",
3251
3859
  agentPath || ".",
3252
3860
  "--input-json",
@@ -3258,31 +3866,51 @@ function stopSignalForAttempt(attempt = 0) {
3258
3866
  if (attempt === 1) return "SIGTERM";
3259
3867
  return "SIGKILL";
3260
3868
  }
3869
+ function killProcessTree(child) {
3870
+ if (!child || child.exitCode != null || child.signalCode != null) return;
3871
+ if (process.platform === "win32") {
3872
+ try {
3873
+ spawnSync4("taskkill", ["/F", "/T", "/PID", String(child.pid)], { stdio: "ignore" });
3874
+ } catch {
3875
+ }
3876
+ } else {
3877
+ try {
3878
+ child.kill("SIGTERM");
3879
+ } catch {
3880
+ }
3881
+ }
3882
+ }
3261
3883
  function helpText() {
3262
3884
  return [
3263
3885
  "",
3264
3886
  ` \u2191 VENDIAN CLI v${CLI_VERSION}`,
3265
3887
  "",
3266
3888
  " Usage:",
3267
- " vendian Open the interactive shell",
3889
+ " vendian Open the dev UI in your browser",
3890
+ " vendian terminal Open the interactive terminal shell",
3268
3891
  " vendian login Sign in and prepare the local runtime",
3269
3892
  " vendian doctor Check local bootstrap health",
3270
3893
  " vendian update Update the managed runtime",
3271
3894
  " vendian init Write SDK agent docs into a workspace",
3272
3895
  ' vendian create "My Agent" Scaffold a new agent from templates',
3896
+ " vendian dev Open the dev UI (alias for bare vendian)",
3273
3897
  " vendian <command> Run a managed Python SDK/cloud command",
3274
3898
  "",
3275
3899
  " Examples:",
3900
+ " vendian",
3901
+ " vendian terminal",
3276
3902
  " vendian login",
3277
3903
  " vendian init --output-dir ./agents",
3278
3904
  ' vendian create "My Agent" --output-dir ./agents',
3279
3905
  " vendian validate ./agents/my-agent --runtime",
3280
3906
  " vendian models",
3281
3907
  " vendian cloud local serve --agents-dir ./agents",
3908
+ " vendian cloud local push --collection-id ID --project-id PID --branch dev",
3909
+ " vendian cloud local push --branch feature/x --from dev",
3282
3910
  " vendian doctor",
3283
3911
  " vendian update",
3284
3912
  "",
3285
- ` Run \u203A vendian \u203A to open the interactive shell`,
3913
+ ` Run \u203A vendian terminal \u203A for the interactive terminal shell`,
3286
3914
  ""
3287
3915
  ].join("\n");
3288
3916
  }
@@ -3293,7 +3921,7 @@ function readManifestName(absoluteFolderPath) {
3293
3921
  if (!absoluteFolderPath) return null;
3294
3922
  for (const fname of ["manifest.yaml", "manifest.yml"]) {
3295
3923
  try {
3296
- const content = fs11.readFileSync(path8.join(absoluteFolderPath, fname), "utf8");
3924
+ const content = fs12.readFileSync(path9.join(absoluteFolderPath, fname), "utf8");
3297
3925
  const m = content.match(/^name\s*:\s*['"]?([^'"\n#]+?)['"]?\s*$/m);
3298
3926
  if (m?.[1]) return m[1].trim();
3299
3927
  } catch {
@@ -3341,11 +3969,7 @@ Environment:
3341
3969
  async function main(argv) {
3342
3970
  const [command, ...rest] = argv;
3343
3971
  if (!command) {
3344
- if (process.stdin.isTTY && process.stdout.isTTY) {
3345
- await runTui();
3346
- } else {
3347
- printHelp();
3348
- }
3972
+ await startDevServer(parseDevOptions([]));
3349
3973
  return;
3350
3974
  }
3351
3975
  if (command === "--help" || command === "-h") {
@@ -3356,10 +3980,22 @@ async function main(argv) {
3356
3980
  console.log(CLI_VERSION);
3357
3981
  return;
3358
3982
  }
3983
+ if (command === "terminal" || command === "tui") {
3984
+ if (process.stdin.isTTY && process.stdout.isTTY) {
3985
+ await runTui();
3986
+ } else {
3987
+ printHelp();
3988
+ }
3989
+ return;
3990
+ }
3359
3991
  if (isBootstrapCommand(command)) {
3360
3992
  await setup(parseSetupOptions(rest));
3361
3993
  return;
3362
3994
  }
3995
+ if (command === "dev") {
3996
+ await startDevServer(parseDevOptions(rest));
3997
+ return;
3998
+ }
3363
3999
  if (command === "doctor") {
3364
4000
  doctor();
3365
4001
  return;
@@ -3368,11 +4004,33 @@ async function main(argv) {
3368
4004
  await setup({ nonInteractive: true });
3369
4005
  return;
3370
4006
  }
4007
+ if (command.startsWith("--port") || command.startsWith("-p") || command === "--no-open" || command === "--agents-dir" || command === "--agents") {
4008
+ await startDevServer(parseDevOptions(argv));
4009
+ return;
4010
+ }
3371
4011
  await forwardToPythonVendian(argv);
3372
4012
  }
3373
4013
  function isBootstrapCommand(command) {
3374
4014
  return command === "login" || command === "setup";
3375
4015
  }
4016
+ function parseDevOptions(args) {
4017
+ const options = {};
4018
+ for (let i = 0; i < args.length; i++) {
4019
+ const arg = args[i];
4020
+ if (arg === "--port" || arg === "-p") {
4021
+ options.port = parseInt(args[++i], 10) || 3859;
4022
+ } else if (arg.startsWith("--port=")) {
4023
+ options.port = parseInt(arg.slice(7), 10) || 3859;
4024
+ } else if (arg === "--agents-dir" || arg === "--agents") {
4025
+ options.agents = args[++i];
4026
+ } else if (arg.startsWith("--agents-dir=")) {
4027
+ options.agents = arg.slice(13);
4028
+ } else if (arg === "--no-open") {
4029
+ options.noOpen = true;
4030
+ }
4031
+ }
4032
+ return options;
4033
+ }
3376
4034
  function parseSetupOptions(args) {
3377
4035
  const options = {
3378
4036
  nonInteractive: args.includes("--yes") || args.includes("--non-interactive"),