@vendian/cli 0.0.41 → 0.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli-wrapper.mjs CHANGED
@@ -33,611 +33,256 @@ var init_constants = __esm({
33
33
  }
34
34
  });
35
35
 
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
- defaultOAuthRedirectUri: () => defaultOAuthRedirectUri,
47
- fetchPackageCredentials: () => fetchPackageCredentials,
48
- formatOAuthError: () => formatOAuthError,
49
- loadCloudConfig: () => loadCloudConfig,
50
- loginWithVendianOAuth: () => loginWithVendianOAuth,
51
- resolveApiUrl: () => resolveApiUrl,
52
- saveCloudToken: () => saveCloudToken
53
- });
54
- import crypto from "node:crypto";
55
- import http from "node:http";
56
- import fs6 from "node:fs";
57
- import path5 from "node:path";
58
- import { spawnSync as spawnSync3 } from "node:child_process";
59
- function resolveApiUrl({ backend, apiUrl, env = process.env } = {}) {
60
- if (apiUrl) {
61
- return apiUrl.replace(/\/$/, "");
36
+ // src/paths.js
37
+ import os from "node:os";
38
+ import path from "node:path";
39
+ function pathApi(platform) {
40
+ return platform === "win32" ? path.win32 : path.posix;
41
+ }
42
+ function homeDir(env, platform) {
43
+ if (platform === "win32") {
44
+ return env.USERPROFILE || os.homedir();
62
45
  }
63
- if (env.VENDIAN_API_URL) {
64
- return env.VENDIAN_API_URL.replace(/\/$/, "");
46
+ return env.HOME || os.homedir();
47
+ }
48
+ function vendianHome(env = process.env, platform = process.platform) {
49
+ if (env.VENDIAN_CLI_HOME) {
50
+ return pathApi(platform).resolve(env.VENDIAN_CLI_HOME);
65
51
  }
66
- const target = backend || "prod";
67
- const resolved = BACKEND_TARGETS[target];
68
- if (!resolved) {
69
- throw new Error(`Unknown Vendian backend '${target}'. Use local, dev, staging, prod, or --api-url.`);
52
+ if (platform === "win32") {
53
+ const root = env.LOCALAPPDATA || path.win32.join(homeDir(env, platform), "AppData", "Local");
54
+ return path.win32.join(root, "Vendian", "cli");
70
55
  }
71
- return resolved;
56
+ return path.posix.join(homeDir(env, platform), ".vendian", "cli");
72
57
  }
73
- async function loginWithVendianOAuth({ backend, apiUrl, noBrowser = false, env = process.env } = {}) {
74
- const resolvedApiUrl = resolveApiUrl({ backend, apiUrl, env });
75
- const callback = await createCallbackServer(env);
76
- try {
77
- const config = await getOAuthConfig(resolvedApiUrl, callback.redirectUri);
78
- const codeVerifier = crypto.randomBytes(48).toString("base64url");
79
- const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
80
- const state = crypto.randomBytes(18).toString("base64url");
81
- const authorizationUrl = authorizationUrlFor(config, { state, codeChallenge });
82
- if (noBrowser) {
83
- console.error(`Open this URL to authenticate the CLI: ${authorizationUrl}`);
84
- } else {
85
- openBrowser(authorizationUrl);
86
- }
87
- const callbackResult = await callback.wait();
88
- if (callbackResult.state !== state) {
89
- throw new Error("OAuth state mismatch");
90
- }
91
- const clerkToken = await exchangeCodeForClerkToken(config, callbackResult.code, codeVerifier, callback.redirectUri);
92
- const exchange = await postJson(`${resolvedApiUrl}/api/v1/cli/auth/oauth/exchange`, {
93
- clerkToken
94
- });
95
- return { apiUrl: resolvedApiUrl, ...exchange };
96
- } catch (error) {
97
- throw new Error(formatOAuthError(error, {
98
- apiUrl: resolvedApiUrl,
99
- redirectUri: callback.redirectUri
100
- }), { cause: error });
101
- } finally {
102
- await callback.close();
58
+ function configPath(env = process.env, platform = process.platform) {
59
+ if (env.VENDIAN_CLI_CONFIG) {
60
+ return pathApi(platform).resolve(env.VENDIAN_CLI_CONFIG);
103
61
  }
62
+ return pathApi(platform).join(vendianHome(env, platform), "config.json");
104
63
  }
105
- async function fetchPackageCredentials({ apiUrl, accessToken }) {
106
- if (!apiUrl || !accessToken) {
107
- return void 0;
64
+ function managedVenvPath(env = process.env, platform = process.platform) {
65
+ if (env.VENDIAN_CLI_VENV) {
66
+ return pathApi(platform).resolve(env.VENDIAN_CLI_VENV);
108
67
  }
109
- const payload = await getJson(`${String(apiUrl).replace(/\/$/, "")}/api/v1/cli/package-credentials`, {
110
- Authorization: `Bearer ${accessToken}`
111
- });
112
- return payload.packageCredentials;
68
+ return pathApi(platform).join(vendianHome(env, platform), ".venv");
113
69
  }
114
- function loadCloudConfig(env = process.env, platform = process.platform) {
115
- const file = cloudConfigPath(env, platform);
70
+ function venvPython(venvPath, platform = process.platform) {
71
+ return platform === "win32" ? path.win32.join(venvPath, "Scripts", "python.exe") : path.posix.join(venvPath, "bin", "python");
72
+ }
73
+ function venvVendian(venvPath, platform = process.platform) {
74
+ return platform === "win32" ? path.win32.join(venvPath, "Scripts", "vendian.exe") : path.posix.join(venvPath, "bin", "vendian");
75
+ }
76
+ var init_paths = __esm({
77
+ "src/paths.js"() {
78
+ }
79
+ });
80
+
81
+ // src/config.js
82
+ import fs from "node:fs";
83
+ import path2 from "node:path";
84
+ function loadConfig(env = process.env, platform = process.platform) {
85
+ const file = configPath(env, platform);
116
86
  try {
117
- const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
118
- return parsed && typeof parsed === "object" ? parsed : {};
119
- } catch {
87
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
88
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
89
+ } catch (error) {
90
+ if (error && error.code === "ENOENT") {
91
+ return {};
92
+ }
120
93
  return {};
121
94
  }
122
95
  }
123
- function cloudAuthStatus({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
124
- const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
125
- const config = loadCloudConfig(env, platform);
126
- const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
127
- const profile = profiles[targetApiUrl];
128
- return {
129
- apiUrl: targetApiUrl,
130
- activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
131
- profile: profile && typeof profile === "object" ? profile : void 0,
132
- authenticated: Boolean(profile?.access_token),
133
- email: typeof profile?.email === "string" ? profile.email : void 0,
134
- expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
135
- profiles
136
- };
137
- }
138
- function activeCloudAuthStatus({ env = process.env, platform = process.platform } = {}) {
139
- const config = loadCloudConfig(env, platform);
140
- const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
141
- const apiUrl = typeof config.active_api_url === "string" ? config.active_api_url : void 0;
142
- const profile = apiUrl ? profiles[apiUrl] : void 0;
143
- return {
144
- apiUrl,
145
- activeApiUrl: apiUrl,
146
- profile: profile && typeof profile === "object" ? profile : void 0,
147
- authenticated: Boolean(profile?.access_token),
148
- email: typeof profile?.email === "string" ? profile.email : void 0,
149
- expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
150
- profiles
151
- };
96
+ function saveConfig(config, env = process.env, platform = process.platform) {
97
+ const file = configPath(env, platform);
98
+ fs.mkdirSync(path2.dirname(file), { recursive: true });
99
+ const next = { version: CONFIG_VERSION, ...config };
100
+ fs.writeFileSync(file, `${JSON.stringify(next, null, 2)}
101
+ `, { encoding: "utf8", mode: 384 });
102
+ try {
103
+ fs.chmodSync(file, 384);
104
+ } catch {
105
+ }
106
+ return file;
152
107
  }
153
- function activateCloudProfile({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
154
- const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
155
- const config = loadCloudConfig(env, platform);
156
- const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
157
- const profile = profiles[targetApiUrl];
158
- if (!profile || typeof profile !== "object" || !profile.access_token) {
159
- return {
160
- apiUrl: targetApiUrl,
161
- activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
162
- authenticated: false,
163
- activated: false,
164
- profiles
165
- };
108
+ var init_config = __esm({
109
+ "src/config.js"() {
110
+ init_constants();
111
+ init_paths();
166
112
  }
167
- const next = {
168
- ...config,
169
- version: config.version || 2,
170
- profiles,
171
- active_api_url: targetApiUrl
172
- };
173
- writeCloudConfig(next, { env, platform });
113
+ });
114
+
115
+ // src/process.js
116
+ import { spawn, spawnSync } from "node:child_process";
117
+ function commandExists(command) {
118
+ const result = spawnSync(command, ["--version"], { stdio: "ignore", shell: false });
119
+ return result.status === 0;
120
+ }
121
+ function runCapture(command, args, options = {}) {
122
+ const result = spawnSync(command, args, {
123
+ encoding: "utf8",
124
+ shell: false,
125
+ ...options
126
+ });
127
+ const errorMessage = result.error && typeof result.error.message === "string" ? result.error.message : "";
174
128
  return {
175
- apiUrl: targetApiUrl,
176
- activeApiUrl: targetApiUrl,
177
- profile,
178
- authenticated: true,
179
- activated: true,
180
- email: typeof profile.email === "string" ? profile.email : void 0,
181
- expiresAt: typeof profile.expires_at === "string" ? profile.expires_at : void 0,
182
- profiles
129
+ ok: !result.error && result.status === 0,
130
+ status: result.status ?? 1,
131
+ stdout: result.stdout || "",
132
+ stderr: result.stderr || errorMessage
183
133
  };
184
134
  }
185
- function defaultOAuthRedirectUri(env = process.env) {
186
- const port = Number(env.VENDIAN_CLI_OAUTH_REDIRECT_PORT || DEFAULT_OAUTH_REDIRECT_PORT);
187
- return `http://127.0.0.1:${port}/callback`;
188
- }
189
- function formatOAuthError(error, { apiUrl, redirectUri } = {}) {
190
- const message = error && typeof error.message === "string" ? error.message : String(error || "OAuth login failed");
191
- const hint = oauthRedirectHint(message, { apiUrl, redirectUri });
192
- return hint ? `${message}
193
- ${hint}` : message;
194
- }
195
- async function getOAuthConfig(apiUrl, redirectUri) {
196
- const url = new URL(`${apiUrl}/api/v1/cli/auth/oauth/config`);
197
- url.searchParams.set("redirectUri", redirectUri);
198
- return getJson(url);
199
- }
200
- function authorizationUrlFor(config, { state, codeChallenge }) {
201
- const url = new URL(String(config.authorizationUrl));
202
- url.searchParams.set("response_type", "code");
203
- url.searchParams.set("client_id", String(config.clientId));
204
- url.searchParams.set("redirect_uri", String(config.redirectUri));
205
- url.searchParams.set("scope", String(config.scopes || "email profile"));
206
- url.searchParams.set("state", state);
207
- url.searchParams.set("code_challenge", codeChallenge);
208
- url.searchParams.set("code_challenge_method", "S256");
209
- return url.toString();
210
- }
211
- async function exchangeCodeForClerkToken(config, code, codeVerifier, redirectUri) {
212
- const body = new URLSearchParams({
213
- grant_type: "authorization_code",
214
- client_id: String(config.clientId),
215
- code,
216
- redirect_uri: redirectUri,
217
- code_verifier: codeVerifier
135
+ function runInherit(command, args, options = {}) {
136
+ const result = spawnSync(command, args, {
137
+ stdio: "inherit",
138
+ shell: false,
139
+ ...options
218
140
  });
219
- const payload = await postForm(String(config.tokenUrl), body);
220
- const token = payload.access_token || payload.id_token;
221
- if (!token) {
222
- throw new Error("OAuth token exchange did not return a Clerk token");
141
+ if (result.error) {
142
+ throw result.error;
223
143
  }
224
- return String(token);
144
+ return result.status ?? 1;
225
145
  }
226
- async function getJson(url, headers = {}) {
227
- const response = await fetch(url, { headers: { Accept: "application/json", ...headers } });
228
- return decodeResponse(response);
229
- }
230
- async function postJson(url, body) {
231
- const response = await fetch(url, {
232
- method: "POST",
233
- headers: { Accept: "application/json", "Content-Type": "application/json" },
234
- body: JSON.stringify(body)
146
+ function spawnForward(command, args, options = {}) {
147
+ return new Promise((resolve, reject) => {
148
+ const child = spawn(command, args, {
149
+ stdio: "inherit",
150
+ shell: false,
151
+ ...options
152
+ });
153
+ child.on("error", reject);
154
+ child.on("exit", (code, signal) => {
155
+ if (signal) {
156
+ reject(new Error(`command terminated by ${signal}`));
157
+ return;
158
+ }
159
+ resolve(code ?? 1);
160
+ });
235
161
  });
236
- return decodeResponse(response);
237
162
  }
238
- async function postForm(url, body) {
239
- const response = await fetch(url, {
240
- method: "POST",
241
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
242
- body
243
- });
244
- return decodeResponse(response);
163
+ function childProcessIsRunning(child) {
164
+ return Boolean(child) && child.exitCode == null && child.signalCode == null;
245
165
  }
246
- async function decodeResponse(response) {
247
- const text = await response.text();
248
- let payload = {};
249
- if (text) {
166
+ function killProcessTree(child, {
167
+ platform = process.platform,
168
+ signal = "SIGTERM",
169
+ spawnSyncFn = spawnSync
170
+ } = {}) {
171
+ if (!childProcessIsRunning(child)) return false;
172
+ if (platform === "win32") {
250
173
  try {
251
- payload = JSON.parse(text);
174
+ spawnSyncFn("taskkill", ["/F", "/T", "/PID", String(child.pid)], { stdio: "ignore" });
252
175
  } catch {
253
- payload = { error: text };
254
176
  }
177
+ return true;
255
178
  }
256
- if (!response.ok) {
257
- throw new Error(payload.message || payload.error || `HTTP ${response.status}`);
179
+ try {
180
+ child.kill(signal);
181
+ return true;
182
+ } catch {
183
+ return false;
258
184
  }
259
- return payload;
260
- }
261
- async function createCallbackServer(env) {
262
- const port = Number(env.VENDIAN_CLI_OAUTH_REDIRECT_PORT || DEFAULT_OAUTH_REDIRECT_PORT);
263
- let server;
264
- let settled = false;
265
- let timeout;
266
- const waitPromise = new Promise((resolve, reject) => {
267
- timeout = setTimeout(() => {
268
- settled = true;
269
- reject(new Error("OAuth login timed out before callback completed"));
270
- }, 3e5);
271
- server = http.createServer((req, res) => {
272
- const url = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
273
- const code = url.searchParams.get("code");
274
- const state = url.searchParams.get("state");
275
- const error = url.searchParams.get("error");
276
- const errorDescription = url.searchParams.get("error_description");
277
- const body = code ? "Vendian CLI authentication complete. You can close this window." : "Vendian CLI authentication failed. Return to the terminal.";
278
- res.writeHead(code ? 200 : 400, {
279
- "Content-Type": "text/plain; charset=utf-8",
280
- "Content-Length": Buffer.byteLength(body)
281
- });
282
- res.end(body);
283
- clearTimeout(timeout);
284
- if (settled) {
285
- return;
286
- }
287
- settled = true;
288
- if (error) {
289
- const details = errorDescription ? `${error} (${errorDescription})` : error;
290
- reject(new Error(`OAuth login failed: ${details}`));
291
- return;
292
- }
293
- if (!code) {
294
- reject(new Error("OAuth callback did not include an authorization code"));
295
- return;
296
- }
297
- resolve({ code, state });
298
- });
299
- server.on("error", reject);
300
- server.listen(port, "127.0.0.1");
301
- });
302
- await new Promise((resolve, reject) => {
303
- server.once("listening", resolve);
304
- server.once("error", reject);
305
- });
306
- return {
307
- redirectUri: `http://127.0.0.1:${server.address().port}/callback`,
308
- wait: () => waitPromise,
309
- close: () => new Promise((resolve) => server.close(resolve))
310
- };
311
185
  }
312
- function oauthRedirectHint(message, { apiUrl, redirectUri } = {}) {
313
- if (!redirectUri) {
314
- return "";
315
- }
316
- const lower = String(message || "").toLowerCase();
317
- const looksLikeRedirectMismatch = lower.includes("redirect_uri") || lower.includes("pre-registered redirect") || lower.includes("oauth login timed out before callback completed") || lower.includes("oauth callback did not include an authorization code");
318
- if (!looksLikeRedirectMismatch) {
319
- return "";
186
+ var init_process = __esm({
187
+ "src/process.js"() {
320
188
  }
321
- const target = apiUrl || "this backend";
322
- return `If Clerk rejected the CLI callback for ${target}, add this redirect URL in Clerk: ${redirectUri}`;
323
- }
324
- function openBrowser(url) {
325
- const platform = process.platform;
189
+ });
190
+
191
+ // src/python.js
192
+ import fs2 from "node:fs";
193
+ function pythonCandidates(platform = process.platform) {
326
194
  if (platform === "win32") {
327
- spawnSync3(...buildWindowsOpenCommand(url), { stdio: "ignore", shell: false });
328
- return;
195
+ return [
196
+ { command: "py", args: ["-3.11"] },
197
+ { command: "python", args: [] },
198
+ { command: "python3", args: [] }
199
+ ];
329
200
  }
330
201
  if (platform === "darwin") {
331
- spawnSync3(...buildMacOpenCommand(url), { stdio: "ignore", shell: false });
332
- return;
202
+ return [
203
+ { command: "/opt/homebrew/bin/python3.12", args: [] },
204
+ { command: "/opt/homebrew/bin/python3.11", args: [] },
205
+ { command: "/usr/local/bin/python3.12", args: [] },
206
+ { command: "/usr/local/bin/python3.11", args: [] },
207
+ { command: "python3.12", args: [] },
208
+ { command: "python3.11", args: [] },
209
+ { command: "python3", args: [] },
210
+ { command: "python", args: [] }
211
+ ];
333
212
  }
334
- spawnSync3(...buildLinuxOpenCommand(url), { stdio: "ignore", shell: false });
335
- }
336
- function buildWindowsOpenCommand(url) {
337
- return ["rundll32.exe", ["url.dll,FileProtocolHandler", String(url)]];
338
- }
339
- function buildMacOpenCommand(url) {
340
- return ["open", [String(url)]];
213
+ return [
214
+ { command: "python3.12", args: [] },
215
+ { command: "python3.11", args: [] },
216
+ { command: "python3", args: [] },
217
+ { command: "python", args: [] }
218
+ ];
341
219
  }
342
- function buildLinuxOpenCommand(url) {
343
- return ["xdg-open", [String(url)]];
220
+ function findPython(platform = process.platform) {
221
+ const candidates = pythonCandidates(platform);
222
+ for (const candidate of candidates) {
223
+ const version = runCapture(candidate.command, [
224
+ ...candidate.args,
225
+ "-c",
226
+ 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")'
227
+ ]);
228
+ if (!version.ok) {
229
+ continue;
230
+ }
231
+ const match = version.stdout.trim().match(/^(\d+)\.(\d+)\./);
232
+ if (!match) {
233
+ continue;
234
+ }
235
+ const major = Number(match[1]);
236
+ const minor = Number(match[2]);
237
+ if (major > 3 || major === 3 && minor >= 11) {
238
+ return candidate;
239
+ }
240
+ }
241
+ return null;
344
242
  }
345
- function cloudConfigPath(env = process.env, platform = process.platform) {
346
- if (env.VENDIAN_CLOUD_CONFIG) {
347
- return path5.resolve(env.VENDIAN_CLOUD_CONFIG);
243
+ function ensureVenv(venvPath, pythonCandidate, platform = process.platform) {
244
+ const pythonPath = venvPython(venvPath, platform);
245
+ if (fs2.existsSync(pythonPath)) {
246
+ return pythonPath;
348
247
  }
349
- if (platform === "win32") {
350
- const root2 = env.APPDATA || path5.join(process.env.USERPROFILE || "", "AppData", "Roaming");
351
- return path5.win32.join(root2, "Vendian", "cloud-auth.json");
248
+ fs2.mkdirSync(venvPath, { recursive: true });
249
+ const status = runInherit(pythonCandidate.command, [
250
+ ...pythonCandidate.args,
251
+ "-m",
252
+ "venv",
253
+ venvPath
254
+ ]);
255
+ if (status !== 0) {
256
+ throw new Error("Could not create the managed Vendian Python environment.");
352
257
  }
353
- const root = env.XDG_CONFIG_HOME || path5.join(process.env.HOME || "", ".config");
354
- return path5.posix.join(root, "vendian", "cloud-auth.json");
258
+ return pythonPath;
355
259
  }
356
- function saveCloudToken(token, { env = process.env, platform = process.platform } = {}) {
357
- const tokenApiUrl = String(token.apiUrl || "").replace(/\/$/, "");
358
- let raw = { version: 2, profiles: {}, active_api_url: token.apiUrl };
359
- try {
360
- const existing = loadCloudConfig(env, platform);
361
- if (existing && typeof existing === "object" && existing.profiles && typeof existing.profiles === "object") {
362
- raw.profiles = existing.profiles;
363
- }
364
- } catch {
365
- }
366
- raw.active_api_url = tokenApiUrl;
367
- raw.profiles[tokenApiUrl] = {
368
- api_url: tokenApiUrl,
369
- access_token: token.accessToken,
370
- user_id: token.userId,
371
- email: token.email,
372
- expires_at: token.expiresAt,
373
- scopes: token.scopes,
374
- tooling_eligible: token.toolingEligible
375
- };
376
- return writeCloudConfig(raw, { env, platform });
260
+ function hasUv() {
261
+ return commandExists("uv");
377
262
  }
378
- function writeCloudConfig(raw, { env = process.env, platform = process.platform } = {}) {
379
- const file = cloudConfigPath(env, platform);
380
- fs6.mkdirSync(path5.dirname(file), { recursive: true });
381
- fs6.writeFileSync(file, `${JSON.stringify(raw, null, 2)}
382
- `, { encoding: "utf8", mode: 384 });
383
- try {
384
- fs6.chmodSync(file, 384);
385
- } catch {
386
- }
387
- return file;
263
+ function pythonVersion(pythonPath) {
264
+ const result = runCapture(pythonPath, [
265
+ "-c",
266
+ 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")'
267
+ ]);
268
+ return result.ok ? result.stdout.trim() : null;
388
269
  }
389
- var DEFAULT_OAUTH_REDIRECT_PORT;
390
- var init_auth = __esm({
391
- "src/auth.js"() {
392
- init_constants();
393
- DEFAULT_OAUTH_REDIRECT_PORT = 8765;
270
+ function verifyVendianImports(pythonPath) {
271
+ const result = runCapture(pythonPath, [
272
+ "-c",
273
+ 'import vendian_agents, vendian_agents_runtime; print("ok")'
274
+ ]);
275
+ return result.ok;
276
+ }
277
+ var init_python = __esm({
278
+ "src/python.js"() {
279
+ init_process();
280
+ init_paths();
394
281
  }
395
282
  });
396
283
 
397
- // src/doctor.js
398
- import fs4 from "node:fs";
399
-
400
- // src/config.js
401
- init_constants();
402
- import fs from "node:fs";
403
- import path2 from "node:path";
404
-
405
- // src/paths.js
406
- import os from "node:os";
407
- import path from "node:path";
408
- function pathApi(platform) {
409
- return platform === "win32" ? path.win32 : path.posix;
410
- }
411
- function homeDir(env, platform) {
412
- if (platform === "win32") {
413
- return env.USERPROFILE || os.homedir();
414
- }
415
- return env.HOME || os.homedir();
416
- }
417
- function vendianHome(env = process.env, platform = process.platform) {
418
- if (env.VENDIAN_CLI_HOME) {
419
- return pathApi(platform).resolve(env.VENDIAN_CLI_HOME);
420
- }
421
- if (platform === "win32") {
422
- const root = env.LOCALAPPDATA || path.win32.join(homeDir(env, platform), "AppData", "Local");
423
- return path.win32.join(root, "Vendian", "cli");
424
- }
425
- return path.posix.join(homeDir(env, platform), ".vendian", "cli");
426
- }
427
- function configPath(env = process.env, platform = process.platform) {
428
- if (env.VENDIAN_CLI_CONFIG) {
429
- return pathApi(platform).resolve(env.VENDIAN_CLI_CONFIG);
430
- }
431
- return pathApi(platform).join(vendianHome(env, platform), "config.json");
432
- }
433
- function managedVenvPath(env = process.env, platform = process.platform) {
434
- if (env.VENDIAN_CLI_VENV) {
435
- return pathApi(platform).resolve(env.VENDIAN_CLI_VENV);
436
- }
437
- return pathApi(platform).join(vendianHome(env, platform), ".venv");
438
- }
439
- function venvPython(venvPath, platform = process.platform) {
440
- return platform === "win32" ? path.win32.join(venvPath, "Scripts", "python.exe") : path.posix.join(venvPath, "bin", "python");
441
- }
442
- function venvVendian(venvPath, platform = process.platform) {
443
- return platform === "win32" ? path.win32.join(venvPath, "Scripts", "vendian.exe") : path.posix.join(venvPath, "bin", "vendian");
444
- }
445
-
446
- // src/config.js
447
- function loadConfig(env = process.env, platform = process.platform) {
448
- const file = configPath(env, platform);
449
- try {
450
- const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
451
- return typeof parsed === "object" && parsed !== null ? parsed : {};
452
- } catch (error) {
453
- if (error && error.code === "ENOENT") {
454
- return {};
455
- }
456
- return {};
457
- }
458
- }
459
- function saveConfig(config, env = process.env, platform = process.platform) {
460
- const file = configPath(env, platform);
461
- fs.mkdirSync(path2.dirname(file), { recursive: true });
462
- const next = { version: CONFIG_VERSION, ...config };
463
- fs.writeFileSync(file, `${JSON.stringify(next, null, 2)}
464
- `, { encoding: "utf8", mode: 384 });
465
- try {
466
- fs.chmodSync(file, 384);
467
- } catch {
468
- }
469
- return file;
470
- }
471
-
472
- // src/install.js
473
- init_constants();
474
- import fs3 from "node:fs";
475
- import path3 from "node:path";
476
-
477
- // src/process.js
478
- import { spawn, spawnSync } from "node:child_process";
479
- function commandExists(command) {
480
- const result = spawnSync(command, ["--version"], { stdio: "ignore", shell: false });
481
- return result.status === 0;
482
- }
483
- function runCapture(command, args, options = {}) {
484
- const result = spawnSync(command, args, {
485
- encoding: "utf8",
486
- shell: false,
487
- ...options
488
- });
489
- const errorMessage = result.error && typeof result.error.message === "string" ? result.error.message : "";
490
- return {
491
- ok: !result.error && result.status === 0,
492
- status: result.status ?? 1,
493
- stdout: result.stdout || "",
494
- stderr: result.stderr || errorMessage
495
- };
496
- }
497
- function runInherit(command, args, options = {}) {
498
- const result = spawnSync(command, args, {
499
- stdio: "inherit",
500
- shell: false,
501
- ...options
502
- });
503
- if (result.error) {
504
- throw result.error;
505
- }
506
- return result.status ?? 1;
507
- }
508
- function spawnForward(command, args, options = {}) {
509
- return new Promise((resolve, reject) => {
510
- const child = spawn(command, args, {
511
- stdio: "inherit",
512
- shell: false,
513
- ...options
514
- });
515
- child.on("error", reject);
516
- child.on("exit", (code, signal) => {
517
- if (signal) {
518
- reject(new Error(`command terminated by ${signal}`));
519
- return;
520
- }
521
- resolve(code ?? 1);
522
- });
523
- });
524
- }
525
- function childProcessIsRunning(child) {
526
- return Boolean(child) && child.exitCode == null && child.signalCode == null;
527
- }
528
- function killProcessTree(child, {
529
- platform = process.platform,
530
- signal = "SIGTERM",
531
- spawnSyncFn = spawnSync
532
- } = {}) {
533
- if (!childProcessIsRunning(child)) return false;
534
- if (platform === "win32") {
535
- try {
536
- spawnSyncFn("taskkill", ["/F", "/T", "/PID", String(child.pid)], { stdio: "ignore" });
537
- } catch {
538
- }
539
- return true;
540
- }
541
- try {
542
- child.kill(signal);
543
- return true;
544
- } catch {
545
- return false;
546
- }
547
- }
548
-
549
- // src/python.js
550
- import fs2 from "node:fs";
551
- function pythonCandidates(platform = process.platform) {
552
- if (platform === "win32") {
553
- return [
554
- { command: "py", args: ["-3.11"] },
555
- { command: "python", args: [] },
556
- { command: "python3", args: [] }
557
- ];
558
- }
559
- if (platform === "darwin") {
560
- return [
561
- { command: "/opt/homebrew/bin/python3.12", args: [] },
562
- { command: "/opt/homebrew/bin/python3.11", args: [] },
563
- { command: "/usr/local/bin/python3.12", args: [] },
564
- { command: "/usr/local/bin/python3.11", args: [] },
565
- { command: "python3.12", args: [] },
566
- { command: "python3.11", args: [] },
567
- { command: "python3", args: [] },
568
- { command: "python", args: [] }
569
- ];
570
- }
571
- return [
572
- { command: "python3.12", args: [] },
573
- { command: "python3.11", args: [] },
574
- { command: "python3", args: [] },
575
- { command: "python", args: [] }
576
- ];
577
- }
578
- function findPython(platform = process.platform) {
579
- const candidates = pythonCandidates(platform);
580
- for (const candidate of candidates) {
581
- const version = runCapture(candidate.command, [
582
- ...candidate.args,
583
- "-c",
584
- 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")'
585
- ]);
586
- if (!version.ok) {
587
- continue;
588
- }
589
- const match = version.stdout.trim().match(/^(\d+)\.(\d+)\./);
590
- if (!match) {
591
- continue;
592
- }
593
- const major = Number(match[1]);
594
- const minor = Number(match[2]);
595
- if (major > 3 || major === 3 && minor >= 11) {
596
- return candidate;
597
- }
598
- }
599
- return null;
600
- }
601
- function ensureVenv(venvPath, pythonCandidate, platform = process.platform) {
602
- const pythonPath = venvPython(venvPath, platform);
603
- if (fs2.existsSync(pythonPath)) {
604
- return pythonPath;
605
- }
606
- fs2.mkdirSync(venvPath, { recursive: true });
607
- const status = runInherit(pythonCandidate.command, [
608
- ...pythonCandidate.args,
609
- "-m",
610
- "venv",
611
- venvPath
612
- ]);
613
- if (status !== 0) {
614
- throw new Error("Could not create the managed Vendian Python environment.");
615
- }
616
- return pythonPath;
617
- }
618
- function hasUv() {
619
- return commandExists("uv");
620
- }
621
- function pythonVersion(pythonPath) {
622
- const result = runCapture(pythonPath, [
623
- "-c",
624
- 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")'
625
- ]);
626
- return result.ok ? result.stdout.trim() : null;
627
- }
628
- function verifyVendianImports(pythonPath) {
629
- const result = runCapture(pythonPath, [
630
- "-c",
631
- 'import vendian_agents, vendian_agents_runtime; print("ok")'
632
- ]);
633
- return result.ok;
634
- }
635
-
636
284
  // src/secret-store.js
637
285
  import { spawnSync as spawnSync2 } from "node:child_process";
638
- var SERVICE = "Vendian CLI";
639
- var PACKAGE_TOKEN_ACCOUNT = "gitlab-package-token";
640
- var SECRET_STORE_TIMEOUT_MS = 5e3;
641
286
  function run(command, args, options = {}) {
642
287
  return spawnSync2(command, args, {
643
288
  encoding: "utf8",
@@ -734,8 +379,18 @@ function loadPackageTokenSecret(config = {}, platform = process.platform) {
734
379
  }
735
380
  return "";
736
381
  }
382
+ var SERVICE, PACKAGE_TOKEN_ACCOUNT, SECRET_STORE_TIMEOUT_MS;
383
+ var init_secret_store = __esm({
384
+ "src/secret-store.js"() {
385
+ SERVICE = "Vendian CLI";
386
+ PACKAGE_TOKEN_ACCOUNT = "gitlab-package-token";
387
+ SECRET_STORE_TIMEOUT_MS = 5e3;
388
+ }
389
+ });
737
390
 
738
391
  // src/install.js
392
+ import fs3 from "node:fs";
393
+ import path3 from "node:path";
739
394
  function registryConfig(config = {}, env = process.env, platform = process.platform) {
740
395
  const gitlabHost = env.GITLAB_HOST || config.gitlabHost || DEFAULT_GITLAB_HOST;
741
396
  const username = env.GITLAB_USERNAME || config.gitlabUsername || "__token__";
@@ -817,267 +472,379 @@ function writeInstallMarker(venvPath, marker) {
817
472
  "utf8"
818
473
  );
819
474
  }
475
+ var init_install = __esm({
476
+ "src/install.js"() {
477
+ init_constants();
478
+ init_process();
479
+ init_python();
480
+ init_secret_store();
481
+ }
482
+ });
820
483
 
821
- // src/doctor.js
822
- function doctor({ env = process.env, platform = process.platform } = {}) {
823
- const config = loadConfig(env, platform);
824
- const venvPath = managedVenvPath(env, platform);
825
- const pythonPath = venvPython(venvPath, platform);
826
- const vendianPath = venvVendian(venvPath, platform);
827
- const registry = registryConfig(config, env, platform);
828
- console.log("Vendian doctor");
829
- console.log(`Home: ${venvPath}`);
830
- console.log(`System Python 3.11+: ${findPython(platform) ? "yes" : "no"}`);
831
- console.log(`uv: ${hasUv() ? "yes" : "no, will use pip fallback"}`);
832
- console.log(`Managed Python: ${fs4.existsSync(pythonPath) ? pythonVersion(pythonPath) || "present" : "missing"}`);
833
- console.log(`Vendian executable: ${fs4.existsSync(vendianPath) ? vendianPath : "missing"}`);
834
- console.log(`Vendian imports: ${fs4.existsSync(pythonPath) && verifyVendianImports(pythonPath) ? "ok" : "missing"}`);
835
- console.log(`Package access: ${registry.token ? `configured (${registry.tokenSource})` : "missing"}`);
484
+ // src/auth.js
485
+ var auth_exports = {};
486
+ __export(auth_exports, {
487
+ activateCloudProfile: () => activateCloudProfile,
488
+ activeCloudAuthStatus: () => activeCloudAuthStatus,
489
+ buildLinuxOpenCommand: () => buildLinuxOpenCommand,
490
+ buildMacOpenCommand: () => buildMacOpenCommand,
491
+ buildWindowsOpenCommand: () => buildWindowsOpenCommand,
492
+ cloudAuthStatus: () => cloudAuthStatus,
493
+ cloudConfigPath: () => cloudConfigPath,
494
+ defaultOAuthRedirectUri: () => defaultOAuthRedirectUri,
495
+ fetchPackageCredentials: () => fetchPackageCredentials,
496
+ formatOAuthError: () => formatOAuthError,
497
+ loadCloudConfig: () => loadCloudConfig,
498
+ loginWithVendianOAuth: () => loginWithVendianOAuth,
499
+ resolveApiUrl: () => resolveApiUrl,
500
+ saveCloudToken: () => saveCloudToken
501
+ });
502
+ import crypto from "node:crypto";
503
+ import http from "node:http";
504
+ import fs6 from "node:fs";
505
+ import path5 from "node:path";
506
+ import { spawnSync as spawnSync3 } from "node:child_process";
507
+ function resolveApiUrl({ backend, apiUrl, env = process.env } = {}) {
508
+ if (apiUrl) {
509
+ return apiUrl.replace(/\/$/, "");
510
+ }
511
+ if (env.VENDIAN_API_URL) {
512
+ return env.VENDIAN_API_URL.replace(/\/$/, "");
513
+ }
514
+ const target = backend || "prod";
515
+ const resolved = BACKEND_TARGETS[target];
516
+ if (!resolved) {
517
+ throw new Error(`Unknown Vendian backend '${target}'. Use local, dev, staging, prod, or --api-url.`);
518
+ }
519
+ return resolved;
836
520
  }
837
-
838
- // src/dev-server.js
839
- import http2 from "node:http";
840
- import fs11 from "node:fs";
841
- import path8 from "node:path";
842
- import { fileURLToPath } from "node:url";
843
- import { spawn as spawn3 } from "node:child_process";
844
-
845
- // src/agent-discovery.js
846
- import fs5 from "node:fs";
847
- import os2 from "node:os";
848
- import path4 from "node:path";
849
- var SKIP_DIRS = /* @__PURE__ */ new Set([
850
- ".agents",
851
- ".git",
852
- ".hg",
853
- ".mypy_cache",
854
- ".pytest_cache",
855
- ".ruff_cache",
856
- ".svn",
857
- ".venv",
858
- "__pycache__",
859
- "build",
860
- "dist",
861
- "node_modules",
862
- "venv"
863
- ]);
864
- var MANIFEST_NAMES = /* @__PURE__ */ new Set(["manifest.yaml", "manifest.yml"]);
865
- function findAgentDirectoryCandidates({
866
- cwd = process.cwd(),
867
- home = os2.homedir(),
868
- maxDepth = 6,
869
- maxDirs = 1e3,
870
- limit = 12
871
- } = {}) {
872
- const start = path4.resolve(cwd);
873
- const candidates = [];
874
- const seen = /* @__PURE__ */ new Set();
875
- function addCandidate(candidatePath) {
876
- const resolved = safeResolve(candidatePath);
877
- if (!resolved || seen.has(resolved)) {
878
- return;
521
+ async function loginWithVendianOAuth({ backend, apiUrl, noBrowser = false, env = process.env } = {}) {
522
+ const resolvedApiUrl = resolveApiUrl({ backend, apiUrl, env });
523
+ const callback = await createCallbackServer(env);
524
+ try {
525
+ const config = await getOAuthConfig(resolvedApiUrl, callback.redirectUri);
526
+ const codeVerifier = crypto.randomBytes(48).toString("base64url");
527
+ const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
528
+ const state = crypto.randomBytes(18).toString("base64url");
529
+ const authorizationUrl = authorizationUrlFor(config, { state, codeChallenge });
530
+ if (noBrowser) {
531
+ console.error(`Open this URL to authenticate the CLI: ${authorizationUrl}`);
532
+ } else {
533
+ openBrowser(authorizationUrl);
879
534
  }
880
- const count = countAgentManifests(resolved, { maxDepth, maxDirs, limit: 100 });
881
- if (count < 1) {
882
- return;
535
+ const callbackResult = await callback.wait();
536
+ if (callbackResult.state !== state) {
537
+ throw new Error("OAuth state mismatch");
883
538
  }
884
- seen.add(resolved);
885
- candidates.push({
886
- path: displayPath(resolved, start),
887
- absolutePath: resolved,
888
- agentCount: count
539
+ const clerkToken = await exchangeCodeForClerkToken(config, callbackResult.code, codeVerifier, callback.redirectUri);
540
+ const exchange = await postJson(`${resolvedApiUrl}/api/v1/cli/auth/oauth/exchange`, {
541
+ clerkToken
889
542
  });
543
+ return { apiUrl: resolvedApiUrl, ...exchange };
544
+ } catch (error) {
545
+ throw new Error(formatOAuthError(error, {
546
+ apiUrl: resolvedApiUrl,
547
+ redirectUri: callback.redirectUri
548
+ }), { cause: error });
549
+ } finally {
550
+ await callback.close();
890
551
  }
891
- addCandidate(path4.join(start, "agents"));
892
- if (hasDirectManifest(start)) {
893
- addCandidate(start);
894
- }
895
- for (const parent of parentDirs(start, 3)) {
896
- addCandidate(path4.join(parent, "agents"));
897
- }
898
- const roots = uniqueExistingDirs([
899
- start,
900
- ...commonSearchRoots(home)
901
- ]);
902
- for (const root of roots) {
903
- for (const manifest of findManifestFiles(root, { maxDepth, maxDirs, limit: 250 })) {
904
- const workspace = nearestAgentsAncestor(manifest, root);
905
- addCandidate(workspace || path4.dirname(manifest));
906
- if (candidates.length >= limit) {
907
- return candidates.slice(0, limit);
908
- }
909
- }
552
+ }
553
+ async function fetchPackageCredentials({ apiUrl, accessToken }) {
554
+ if (!apiUrl || !accessToken) {
555
+ return void 0;
910
556
  }
911
- return candidates.slice(0, limit);
557
+ const payload = await getJson(`${String(apiUrl).replace(/\/$/, "")}/api/v1/cli/package-credentials`, {
558
+ Authorization: `Bearer ${accessToken}`
559
+ });
560
+ return payload.packageCredentials;
912
561
  }
913
- function findAgentFolders(root, {
914
- cwd = process.cwd(),
915
- maxDepth = 6,
916
- maxDirs = 1e3,
917
- limit = 100
918
- } = {}) {
919
- const start = path4.resolve(cwd);
920
- const resolvedRoot = safeResolve(root || ".");
921
- if (!resolvedRoot) {
922
- return [];
562
+ function loadCloudConfig(env = process.env, platform = process.platform) {
563
+ const file = cloudConfigPath(env, platform);
564
+ try {
565
+ const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
566
+ return parsed && typeof parsed === "object" ? parsed : {};
567
+ } catch {
568
+ return {};
923
569
  }
924
- const seen = /* @__PURE__ */ new Set();
925
- const folders = [];
926
- for (const manifest of findManifestFiles(resolvedRoot, { maxDepth, maxDirs, limit })) {
927
- const folder = path4.dirname(manifest);
928
- const resolved = safeResolve(folder);
929
- if (!resolved || seen.has(resolved)) {
930
- continue;
931
- }
932
- seen.add(resolved);
933
- folders.push({
934
- path: displayPath(resolved, start),
935
- absolutePath: resolved,
936
- name: path4.basename(resolved) || displayPath(resolved, start)
937
- });
570
+ }
571
+ function cloudAuthStatus({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
572
+ const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
573
+ const config = loadCloudConfig(env, platform);
574
+ const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
575
+ const profile = profiles[targetApiUrl];
576
+ return {
577
+ apiUrl: targetApiUrl,
578
+ activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
579
+ profile: profile && typeof profile === "object" ? profile : void 0,
580
+ authenticated: Boolean(profile?.access_token),
581
+ email: typeof profile?.email === "string" ? profile.email : void 0,
582
+ expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
583
+ profiles
584
+ };
585
+ }
586
+ function activeCloudAuthStatus({ env = process.env, platform = process.platform } = {}) {
587
+ const config = loadCloudConfig(env, platform);
588
+ const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
589
+ const apiUrl = typeof config.active_api_url === "string" ? config.active_api_url : void 0;
590
+ const profile = apiUrl ? profiles[apiUrl] : void 0;
591
+ return {
592
+ apiUrl,
593
+ activeApiUrl: apiUrl,
594
+ profile: profile && typeof profile === "object" ? profile : void 0,
595
+ authenticated: Boolean(profile?.access_token),
596
+ email: typeof profile?.email === "string" ? profile.email : void 0,
597
+ expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
598
+ profiles
599
+ };
600
+ }
601
+ function activateCloudProfile({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
602
+ const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
603
+ const config = loadCloudConfig(env, platform);
604
+ const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
605
+ const profile = profiles[targetApiUrl];
606
+ if (!profile || typeof profile !== "object" || !profile.access_token) {
607
+ return {
608
+ apiUrl: targetApiUrl,
609
+ activeApiUrl: typeof config.active_api_url === "string" ? config.active_api_url : void 0,
610
+ authenticated: false,
611
+ activated: false,
612
+ profiles
613
+ };
938
614
  }
939
- return folders.sort((a, b) => a.path.localeCompare(b.path)).slice(0, limit);
615
+ const next = {
616
+ ...config,
617
+ version: config.version || 2,
618
+ profiles,
619
+ active_api_url: targetApiUrl
620
+ };
621
+ writeCloudConfig(next, { env, platform });
622
+ return {
623
+ apiUrl: targetApiUrl,
624
+ activeApiUrl: targetApiUrl,
625
+ profile,
626
+ authenticated: true,
627
+ activated: true,
628
+ email: typeof profile.email === "string" ? profile.email : void 0,
629
+ expiresAt: typeof profile.expires_at === "string" ? profile.expires_at : void 0,
630
+ profiles
631
+ };
940
632
  }
941
- function countAgentManifests(root, { maxDepth = 6, maxDirs = 1e3, limit = 100 } = {}) {
942
- let count = 0;
943
- for (const _manifest of findManifestFiles(root, { maxDepth, maxDirs, limit })) {
944
- count += 1;
945
- if (count >= limit) {
946
- return count;
947
- }
633
+ function defaultOAuthRedirectUri(env = process.env) {
634
+ const port = Number(env.VENDIAN_CLI_OAUTH_REDIRECT_PORT || DEFAULT_OAUTH_REDIRECT_PORT);
635
+ return `http://127.0.0.1:${port}/callback`;
636
+ }
637
+ function formatOAuthError(error, { apiUrl, redirectUri } = {}) {
638
+ const message = error && typeof error.message === "string" ? error.message : String(error || "OAuth login failed");
639
+ const hint = oauthRedirectHint(message, { apiUrl, redirectUri });
640
+ return hint ? `${message}
641
+ ${hint}` : message;
642
+ }
643
+ async function getOAuthConfig(apiUrl, redirectUri) {
644
+ const url = new URL(`${apiUrl}/api/v1/cli/auth/oauth/config`);
645
+ url.searchParams.set("redirectUri", redirectUri);
646
+ return getJson(url);
647
+ }
648
+ function authorizationUrlFor(config, { state, codeChallenge }) {
649
+ const url = new URL(String(config.authorizationUrl));
650
+ url.searchParams.set("response_type", "code");
651
+ url.searchParams.set("client_id", String(config.clientId));
652
+ url.searchParams.set("redirect_uri", String(config.redirectUri));
653
+ url.searchParams.set("scope", String(config.scopes || "email profile"));
654
+ url.searchParams.set("state", state);
655
+ url.searchParams.set("code_challenge", codeChallenge);
656
+ url.searchParams.set("code_challenge_method", "S256");
657
+ return url.toString();
658
+ }
659
+ async function exchangeCodeForClerkToken(config, code, codeVerifier, redirectUri) {
660
+ const body = new URLSearchParams({
661
+ grant_type: "authorization_code",
662
+ client_id: String(config.clientId),
663
+ code,
664
+ redirect_uri: redirectUri,
665
+ code_verifier: codeVerifier
666
+ });
667
+ const payload = await postForm(String(config.tokenUrl), body);
668
+ const token = payload.access_token || payload.id_token;
669
+ if (!token) {
670
+ throw new Error("OAuth token exchange did not return a Clerk token");
948
671
  }
949
- return count;
672
+ return String(token);
950
673
  }
951
- function findManifestFiles(root, { maxDepth, maxDirs, limit }) {
952
- const manifests = [];
953
- const stack = [{ dir: root, depth: 0 }];
954
- const seen = /* @__PURE__ */ new Set();
955
- while (stack.length && manifests.length < limit && seen.size < maxDirs) {
956
- const { dir, depth } = stack.pop();
957
- const resolved = safeResolve(dir);
958
- if (!resolved || seen.has(resolved)) {
959
- continue;
960
- }
961
- seen.add(resolved);
962
- let entries;
674
+ async function getJson(url, headers = {}) {
675
+ const response = await fetch(url, { headers: { Accept: "application/json", ...headers } });
676
+ return decodeResponse(response);
677
+ }
678
+ async function postJson(url, body) {
679
+ const response = await fetch(url, {
680
+ method: "POST",
681
+ headers: { Accept: "application/json", "Content-Type": "application/json" },
682
+ body: JSON.stringify(body)
683
+ });
684
+ return decodeResponse(response);
685
+ }
686
+ async function postForm(url, body) {
687
+ const response = await fetch(url, {
688
+ method: "POST",
689
+ headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
690
+ body
691
+ });
692
+ return decodeResponse(response);
693
+ }
694
+ async function decodeResponse(response) {
695
+ const text = await response.text();
696
+ let payload = {};
697
+ if (text) {
963
698
  try {
964
- entries = fs5.readdirSync(resolved, { withFileTypes: true });
699
+ payload = JSON.parse(text);
965
700
  } catch {
966
- continue;
967
- }
968
- for (const entry of entries) {
969
- if (entry.isFile() && MANIFEST_NAMES.has(entry.name)) {
970
- manifests.push(path4.join(resolved, entry.name));
971
- if (manifests.length >= limit) {
972
- break;
973
- }
974
- }
975
- }
976
- if (depth >= maxDepth) {
977
- continue;
978
- }
979
- for (const entry of entries) {
980
- if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) {
981
- continue;
982
- }
983
- stack.push({ dir: path4.join(resolved, entry.name), depth: depth + 1 });
701
+ payload = { error: text };
984
702
  }
985
703
  }
986
- return manifests;
987
- }
988
- function nearestAgentsAncestor(manifestPath, root) {
989
- let current = path4.dirname(manifestPath);
990
- const stop = path4.resolve(root);
991
- while (current.startsWith(stop)) {
992
- if (path4.basename(current).toLowerCase() === "agents") {
993
- return current;
994
- }
995
- const parent = path4.dirname(current);
996
- if (parent === current) {
997
- break;
998
- }
999
- current = parent;
704
+ if (!response.ok) {
705
+ throw new Error(payload.message || payload.error || `HTTP ${response.status}`);
1000
706
  }
1001
- return null;
707
+ return payload;
1002
708
  }
1003
- function hasDirectManifest(root) {
1004
- return [...MANIFEST_NAMES].some((name) => fs5.existsSync(path4.join(root, name)));
709
+ async function createCallbackServer(env) {
710
+ const port = Number(env.VENDIAN_CLI_OAUTH_REDIRECT_PORT || DEFAULT_OAUTH_REDIRECT_PORT);
711
+ let server;
712
+ let settled = false;
713
+ let timeout;
714
+ const waitPromise = new Promise((resolve, reject) => {
715
+ timeout = setTimeout(() => {
716
+ settled = true;
717
+ reject(new Error("OAuth login timed out before callback completed"));
718
+ }, 3e5);
719
+ server = http.createServer((req, res) => {
720
+ const url = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
721
+ const code = url.searchParams.get("code");
722
+ const state = url.searchParams.get("state");
723
+ const error = url.searchParams.get("error");
724
+ const errorDescription = url.searchParams.get("error_description");
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
+ const details = errorDescription ? `${error} (${errorDescription})` : error;
738
+ reject(new Error(`OAuth login failed: ${details}`));
739
+ return;
740
+ }
741
+ if (!code) {
742
+ reject(new Error("OAuth callback did not include an authorization code"));
743
+ return;
744
+ }
745
+ resolve({ code, state });
746
+ });
747
+ server.on("error", reject);
748
+ server.listen(port, "127.0.0.1");
749
+ });
750
+ await new Promise((resolve, reject) => {
751
+ server.once("listening", resolve);
752
+ server.once("error", reject);
753
+ });
754
+ return {
755
+ redirectUri: `http://127.0.0.1:${server.address().port}/callback`,
756
+ wait: () => waitPromise,
757
+ close: () => new Promise((resolve) => server.close(resolve))
758
+ };
1005
759
  }
1006
- function displayPath(target, cwd) {
1007
- const relative = path4.relative(cwd, target);
1008
- if (!relative) {
1009
- return ".";
760
+ function oauthRedirectHint(message, { apiUrl, redirectUri } = {}) {
761
+ if (!redirectUri) {
762
+ return "";
1010
763
  }
1011
- if (relative.startsWith("..") || path4.isAbsolute(relative)) {
1012
- return target;
764
+ const lower = String(message || "").toLowerCase();
765
+ const looksLikeRedirectMismatch = lower.includes("redirect_uri") || lower.includes("pre-registered redirect") || lower.includes("oauth login timed out before callback completed") || lower.includes("oauth callback did not include an authorization code");
766
+ if (!looksLikeRedirectMismatch) {
767
+ return "";
1013
768
  }
1014
- return relative.startsWith(".") ? relative : `.${path4.sep}${relative}`;
769
+ const target = apiUrl || "this backend";
770
+ return `If Clerk rejected the CLI callback for ${target}, add this redirect URL in Clerk: ${redirectUri}`;
1015
771
  }
1016
- function commonSearchRoots(home) {
1017
- if (!home) {
1018
- return [];
772
+ function openBrowser(url) {
773
+ const platform = process.platform;
774
+ if (platform === "win32") {
775
+ spawnSync3(...buildWindowsOpenCommand(url), { stdio: "ignore", shell: false });
776
+ return;
1019
777
  }
1020
- return [
1021
- "agents",
1022
- "Projects",
1023
- "Code",
1024
- "Developer",
1025
- "Documents",
1026
- "Desktop"
1027
- ].map((name) => path4.join(home, name));
1028
- }
1029
- function uniqueExistingDirs(paths) {
1030
- const result = [];
1031
- const seen = /* @__PURE__ */ new Set();
1032
- for (const item of paths) {
1033
- const resolved = safeResolve(item);
1034
- if (!resolved || seen.has(resolved)) {
1035
- continue;
1036
- }
1037
- try {
1038
- if (!fs5.statSync(resolved).isDirectory()) {
1039
- continue;
1040
- }
1041
- } catch {
1042
- continue;
1043
- }
1044
- seen.add(resolved);
1045
- result.push(resolved);
778
+ if (platform === "darwin") {
779
+ spawnSync3(...buildMacOpenCommand(url), { stdio: "ignore", shell: false });
780
+ return;
1046
781
  }
1047
- return result;
782
+ spawnSync3(...buildLinuxOpenCommand(url), { stdio: "ignore", shell: false });
1048
783
  }
1049
- function parentDirs(start, limit) {
1050
- const parents = [];
1051
- let current = path4.resolve(start);
1052
- for (let index = 0; index < limit; index += 1) {
1053
- const parent = path4.dirname(current);
1054
- if (!parent || parent === current) {
1055
- break;
784
+ function buildWindowsOpenCommand(url) {
785
+ return ["rundll32.exe", ["url.dll,FileProtocolHandler", String(url)]];
786
+ }
787
+ function buildMacOpenCommand(url) {
788
+ return ["open", [String(url)]];
789
+ }
790
+ function buildLinuxOpenCommand(url) {
791
+ return ["xdg-open", [String(url)]];
792
+ }
793
+ function cloudConfigPath(env = process.env, platform = process.platform) {
794
+ if (env.VENDIAN_CLOUD_CONFIG) {
795
+ return path5.resolve(env.VENDIAN_CLOUD_CONFIG);
796
+ }
797
+ if (platform === "win32") {
798
+ const root2 = env.APPDATA || path5.join(process.env.USERPROFILE || "", "AppData", "Roaming");
799
+ return path5.win32.join(root2, "Vendian", "cloud-auth.json");
800
+ }
801
+ const root = env.XDG_CONFIG_HOME || path5.join(process.env.HOME || "", ".config");
802
+ return path5.posix.join(root, "vendian", "cloud-auth.json");
803
+ }
804
+ function saveCloudToken(token, { env = process.env, platform = process.platform } = {}) {
805
+ const tokenApiUrl = String(token.apiUrl || "").replace(/\/$/, "");
806
+ let raw = { version: 2, profiles: {}, active_api_url: token.apiUrl };
807
+ try {
808
+ const existing = loadCloudConfig(env, platform);
809
+ if (existing && typeof existing === "object" && existing.profiles && typeof existing.profiles === "object") {
810
+ raw.profiles = existing.profiles;
1056
811
  }
1057
- parents.push(parent);
1058
- current = parent;
812
+ } catch {
1059
813
  }
1060
- return parents;
814
+ raw.active_api_url = tokenApiUrl;
815
+ raw.profiles[tokenApiUrl] = {
816
+ api_url: tokenApiUrl,
817
+ access_token: token.accessToken,
818
+ user_id: token.userId,
819
+ email: token.email,
820
+ expires_at: token.expiresAt,
821
+ scopes: token.scopes,
822
+ tooling_eligible: token.toolingEligible
823
+ };
824
+ return writeCloudConfig(raw, { env, platform });
1061
825
  }
1062
- function safeResolve(candidate) {
826
+ function writeCloudConfig(raw, { env = process.env, platform = process.platform } = {}) {
827
+ const file = cloudConfigPath(env, platform);
828
+ fs6.mkdirSync(path5.dirname(file), { recursive: true });
829
+ fs6.writeFileSync(file, `${JSON.stringify(raw, null, 2)}
830
+ `, { encoding: "utf8", mode: 384 });
1063
831
  try {
1064
- return path4.resolve(candidate);
832
+ fs6.chmodSync(file, 384);
1065
833
  } catch {
1066
- return null;
1067
834
  }
835
+ return file;
1068
836
  }
1069
-
1070
- // src/dev-server.js
1071
- init_auth();
1072
- init_constants();
1073
-
1074
- // src/forward.js
1075
- import fs9 from "node:fs";
837
+ var DEFAULT_OAUTH_REDIRECT_PORT;
838
+ var init_auth = __esm({
839
+ "src/auth.js"() {
840
+ init_constants();
841
+ DEFAULT_OAUTH_REDIRECT_PORT = 8765;
842
+ }
843
+ });
1076
844
 
1077
845
  // src/docs.js
1078
846
  import fs7 from "node:fs";
1079
847
  import path6 from "node:path";
1080
- var DOC_WORKSPACES_KEY = "agentDocWorkspaces";
1081
848
  function pathApi2(platform) {
1082
849
  return platform === "win32" ? path6.win32 : path6.posix;
1083
850
  }
@@ -1163,10 +930,24 @@ function refreshAgentDocsWorkspaces({
1163
930
  saveConfig(next, env, platform);
1164
931
  return { config: next, refreshed, failed };
1165
932
  }
933
+ var DOC_WORKSPACES_KEY;
934
+ var init_docs = __esm({
935
+ "src/docs.js"() {
936
+ init_config();
937
+ init_paths();
938
+ init_process();
939
+ DOC_WORKSPACES_KEY = "agentDocWorkspaces";
940
+ }
941
+ });
1166
942
 
1167
943
  // src/setup.js
1168
- init_auth();
1169
- init_constants();
944
+ var setup_exports = {};
945
+ __export(setup_exports, {
946
+ refreshPackageAccessFromCloudAuth: () => refreshPackageAccessFromCloudAuth,
947
+ savePackageCredentials: () => savePackageCredentials,
948
+ setup: () => setup,
949
+ setupCompletionLines: () => setupCompletionLines
950
+ });
1170
951
  import fs8 from "node:fs";
1171
952
  async function setup({
1172
953
  nonInteractive = false,
@@ -1219,103 +1000,381 @@ async function setup({
1219
1000
  if (!installRegistry.token) {
1220
1001
  throw new Error("Package access is missing. Run interactive `vendian login` to sign in.");
1221
1002
  }
1222
- const python = findPython(platform);
1223
- if (!python) {
1224
- throw new Error("Python 3.11+ was not found. Install Python 3.11 or newer, then rerun `vendian login`.");
1003
+ const python = findPython(platform);
1004
+ if (!python) {
1005
+ throw new Error("Python 3.11+ was not found. Install Python 3.11 or newer, then rerun `vendian login`.");
1006
+ }
1007
+ const venvPath = managedVenvPath(env, platform);
1008
+ console.log(`Managed environment: ${venvPath}`);
1009
+ const pythonPath = ensureVenv(venvPath, python, platform);
1010
+ console.log(`Python: ${pythonVersion(pythonPath) || pythonPath}`);
1011
+ saveConfig(next, env, platform);
1012
+ installVendianPackages({ pythonPath, venvPath, config: next, env, platform });
1013
+ const updated = { ...next, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() };
1014
+ refreshAgentDocsWorkspaces({ config: updated, venvPath, env, platform });
1015
+ if (!verifyVendianImports(pythonPath)) {
1016
+ throw new Error("Vendian packages installed, but import verification failed.");
1017
+ }
1018
+ const vendianPath = venvVendian(venvPath, platform);
1019
+ if (!fs8.existsSync(vendianPath)) {
1020
+ throw new Error("Vendian executable was not found after install.");
1021
+ }
1022
+ console.log("");
1023
+ for (const line of setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl })) {
1024
+ console.log(line);
1025
+ }
1026
+ }
1027
+ function savePackageCredentials(config, packageCredentials, { platform = process.platform } = {}) {
1028
+ if (!packageCredentials?.token) {
1029
+ return false;
1030
+ }
1031
+ config.gitlabHost = packageCredentials.gitlabHost || config.gitlabHost;
1032
+ config.gitlabUsername = packageCredentials.username || config.gitlabUsername;
1033
+ config.sdkPublicProjectId = packageCredentials.sdkProjectId || config.sdkPublicProjectId;
1034
+ config.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || config.sdkRuntimeProjectId;
1035
+ config.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || config.vendianAgentsVersion;
1036
+ config.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || config.vendianAgentsRuntimeVersion;
1037
+ const secret = savePackageTokenSecret(packageCredentials.token, config, platform);
1038
+ if (secret.ok) {
1039
+ Object.assign(config, secret.config || {});
1040
+ delete config.gitlabToken;
1041
+ console.log("Package access saved.");
1042
+ } else {
1043
+ config.gitlabToken = packageCredentials.token;
1044
+ console.log("Package access saved in the local Vendian CLI config file.");
1045
+ }
1046
+ return true;
1047
+ }
1048
+ async function refreshPackageAccessFromCloudAuth({
1049
+ config,
1050
+ auth,
1051
+ env = process.env,
1052
+ platform = process.platform,
1053
+ save = true
1054
+ } = {}) {
1055
+ const currentConfig = config || loadConfig(env, platform);
1056
+ const registry = registryConfig(currentConfig, env, platform);
1057
+ if (registry.token) {
1058
+ return { config: currentConfig, refreshed: false };
1059
+ }
1060
+ const status = auth || activeCloudAuthStatus({ env, platform });
1061
+ if (!status.authenticated || !status.apiUrl || !status.profile?.access_token) {
1062
+ return { config: currentConfig, refreshed: false };
1063
+ }
1064
+ const packageCredentials = await fetchPackageCredentials({
1065
+ apiUrl: status.apiUrl,
1066
+ accessToken: status.profile.access_token
1067
+ });
1068
+ if (!packageCredentials?.token) {
1069
+ return { config: currentConfig, refreshed: false };
1070
+ }
1071
+ const next = { ...currentConfig };
1072
+ const stored = savePackageCredentials(next, packageCredentials, { platform });
1073
+ if (stored && save) {
1074
+ saveConfig(next, env, platform);
1075
+ }
1076
+ return { config: next, refreshed: stored };
1077
+ }
1078
+ function setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl } = {}) {
1079
+ const lines = ["Vendian login complete."];
1080
+ if (cloudAuthApiUrl) {
1081
+ lines.push(`Connected to ${cloudAuthApiUrl}.`);
1082
+ lines.push("Next: vendian cloud local serve --agents-dir ./agents");
1083
+ return lines;
1084
+ }
1085
+ lines.push(`Next: ${cloudAuthLoginCommand({ backend, apiUrl })}`);
1086
+ lines.push("Then: vendian cloud local serve --agents-dir ./agents");
1087
+ return lines;
1088
+ }
1089
+ function cloudAuthLoginCommand({ backend, apiUrl } = {}) {
1090
+ if (apiUrl) {
1091
+ return `vendian cloud auth login --api-url ${apiUrl}`;
1092
+ }
1093
+ if (backend) {
1094
+ return `vendian cloud auth login --backend ${backend}`;
1095
+ }
1096
+ return "vendian cloud auth login";
1097
+ }
1098
+ var init_setup = __esm({
1099
+ "src/setup.js"() {
1100
+ init_auth();
1101
+ init_constants();
1102
+ init_config();
1103
+ init_docs();
1104
+ init_install();
1105
+ init_paths();
1106
+ init_python();
1107
+ init_secret_store();
1108
+ }
1109
+ });
1110
+
1111
+ // src/doctor.js
1112
+ init_config();
1113
+ init_install();
1114
+ init_paths();
1115
+ init_python();
1116
+ import fs4 from "node:fs";
1117
+ function doctor({ env = process.env, platform = process.platform } = {}) {
1118
+ const config = loadConfig(env, platform);
1119
+ const venvPath = managedVenvPath(env, platform);
1120
+ const pythonPath = venvPython(venvPath, platform);
1121
+ const vendianPath = venvVendian(venvPath, platform);
1122
+ const registry = registryConfig(config, env, platform);
1123
+ console.log("Vendian doctor");
1124
+ console.log(`Home: ${venvPath}`);
1125
+ console.log(`System Python 3.11+: ${findPython(platform) ? "yes" : "no"}`);
1126
+ console.log(`uv: ${hasUv() ? "yes" : "no, will use pip fallback"}`);
1127
+ console.log(`Managed Python: ${fs4.existsSync(pythonPath) ? pythonVersion(pythonPath) || "present" : "missing"}`);
1128
+ console.log(`Vendian executable: ${fs4.existsSync(vendianPath) ? vendianPath : "missing"}`);
1129
+ console.log(`Vendian imports: ${fs4.existsSync(pythonPath) && verifyVendianImports(pythonPath) ? "ok" : "missing"}`);
1130
+ console.log(`Package access: ${registry.token ? `configured (${registry.tokenSource})` : "missing"}`);
1131
+ }
1132
+
1133
+ // src/dev-server.js
1134
+ import http2 from "node:http";
1135
+ import fs11 from "node:fs";
1136
+ import path8 from "node:path";
1137
+ import { fileURLToPath } from "node:url";
1138
+ import { spawn as spawn3 } from "node:child_process";
1139
+
1140
+ // src/agent-discovery.js
1141
+ import fs5 from "node:fs";
1142
+ import os2 from "node:os";
1143
+ import path4 from "node:path";
1144
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
1145
+ ".agents",
1146
+ ".git",
1147
+ ".hg",
1148
+ ".mypy_cache",
1149
+ ".pytest_cache",
1150
+ ".ruff_cache",
1151
+ ".svn",
1152
+ ".venv",
1153
+ "__pycache__",
1154
+ "build",
1155
+ "dist",
1156
+ "node_modules",
1157
+ "venv"
1158
+ ]);
1159
+ var MANIFEST_NAMES = /* @__PURE__ */ new Set(["manifest.yaml", "manifest.yml"]);
1160
+ function findAgentDirectoryCandidates({
1161
+ cwd = process.cwd(),
1162
+ home = os2.homedir(),
1163
+ maxDepth = 6,
1164
+ maxDirs = 1e3,
1165
+ limit = 12
1166
+ } = {}) {
1167
+ const start = path4.resolve(cwd);
1168
+ const candidates = [];
1169
+ const seen = /* @__PURE__ */ new Set();
1170
+ function addCandidate(candidatePath) {
1171
+ const resolved = safeResolve(candidatePath);
1172
+ if (!resolved || seen.has(resolved)) {
1173
+ return;
1174
+ }
1175
+ const count = countAgentManifests(resolved, { maxDepth, maxDirs, limit: 100 });
1176
+ if (count < 1) {
1177
+ return;
1178
+ }
1179
+ seen.add(resolved);
1180
+ candidates.push({
1181
+ path: displayPath(resolved, start),
1182
+ absolutePath: resolved,
1183
+ agentCount: count
1184
+ });
1185
+ }
1186
+ addCandidate(path4.join(start, "agents"));
1187
+ if (hasDirectManifest(start)) {
1188
+ addCandidate(start);
1189
+ }
1190
+ for (const parent of parentDirs(start, 3)) {
1191
+ addCandidate(path4.join(parent, "agents"));
1225
1192
  }
1226
- const venvPath = managedVenvPath(env, platform);
1227
- console.log(`Managed environment: ${venvPath}`);
1228
- const pythonPath = ensureVenv(venvPath, python, platform);
1229
- console.log(`Python: ${pythonVersion(pythonPath) || pythonPath}`);
1230
- saveConfig(next, env, platform);
1231
- installVendianPackages({ pythonPath, venvPath, config: next, env, platform });
1232
- const updated = { ...next, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() };
1233
- refreshAgentDocsWorkspaces({ config: updated, venvPath, env, platform });
1234
- if (!verifyVendianImports(pythonPath)) {
1235
- throw new Error("Vendian packages installed, but import verification failed.");
1193
+ const roots = uniqueExistingDirs([
1194
+ start,
1195
+ ...commonSearchRoots(home)
1196
+ ]);
1197
+ for (const root of roots) {
1198
+ for (const manifest of findManifestFiles(root, { maxDepth, maxDirs, limit: 250 })) {
1199
+ const workspace = nearestAgentsAncestor(manifest, root);
1200
+ addCandidate(workspace || path4.dirname(manifest));
1201
+ if (candidates.length >= limit) {
1202
+ return candidates.slice(0, limit);
1203
+ }
1204
+ }
1236
1205
  }
1237
- const vendianPath = venvVendian(venvPath, platform);
1238
- if (!fs8.existsSync(vendianPath)) {
1239
- throw new Error("Vendian executable was not found after install.");
1206
+ return candidates.slice(0, limit);
1207
+ }
1208
+ function findAgentFolders(root, {
1209
+ cwd = process.cwd(),
1210
+ maxDepth = 6,
1211
+ maxDirs = 1e3,
1212
+ limit = 100
1213
+ } = {}) {
1214
+ const start = path4.resolve(cwd);
1215
+ const resolvedRoot = safeResolve(root || ".");
1216
+ if (!resolvedRoot) {
1217
+ return [];
1240
1218
  }
1241
- console.log("");
1242
- for (const line of setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl })) {
1243
- console.log(line);
1219
+ const seen = /* @__PURE__ */ new Set();
1220
+ const folders = [];
1221
+ for (const manifest of findManifestFiles(resolvedRoot, { maxDepth, maxDirs, limit })) {
1222
+ const folder = path4.dirname(manifest);
1223
+ const resolved = safeResolve(folder);
1224
+ if (!resolved || seen.has(resolved)) {
1225
+ continue;
1226
+ }
1227
+ seen.add(resolved);
1228
+ folders.push({
1229
+ path: displayPath(resolved, start),
1230
+ absolutePath: resolved,
1231
+ name: path4.basename(resolved) || displayPath(resolved, start)
1232
+ });
1244
1233
  }
1234
+ return folders.sort((a, b) => a.path.localeCompare(b.path)).slice(0, limit);
1245
1235
  }
1246
- function savePackageCredentials(config, packageCredentials, { platform = process.platform } = {}) {
1247
- if (!packageCredentials?.token) {
1248
- return false;
1236
+ function countAgentManifests(root, { maxDepth = 6, maxDirs = 1e3, limit = 100 } = {}) {
1237
+ let count = 0;
1238
+ for (const _manifest of findManifestFiles(root, { maxDepth, maxDirs, limit })) {
1239
+ count += 1;
1240
+ if (count >= limit) {
1241
+ return count;
1242
+ }
1249
1243
  }
1250
- config.gitlabHost = packageCredentials.gitlabHost || config.gitlabHost;
1251
- config.gitlabUsername = packageCredentials.username || config.gitlabUsername;
1252
- config.sdkPublicProjectId = packageCredentials.sdkProjectId || config.sdkPublicProjectId;
1253
- config.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || config.sdkRuntimeProjectId;
1254
- config.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || config.vendianAgentsVersion;
1255
- config.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || config.vendianAgentsRuntimeVersion;
1256
- const secret = savePackageTokenSecret(packageCredentials.token, config, platform);
1257
- if (secret.ok) {
1258
- Object.assign(config, secret.config || {});
1259
- delete config.gitlabToken;
1260
- console.log("Package access saved.");
1261
- } else {
1262
- config.gitlabToken = packageCredentials.token;
1263
- console.log("Package access saved in the local Vendian CLI config file.");
1244
+ return count;
1245
+ }
1246
+ function findManifestFiles(root, { maxDepth, maxDirs, limit }) {
1247
+ const manifests = [];
1248
+ const stack = [{ dir: root, depth: 0 }];
1249
+ const seen = /* @__PURE__ */ new Set();
1250
+ while (stack.length && manifests.length < limit && seen.size < maxDirs) {
1251
+ const { dir, depth } = stack.pop();
1252
+ const resolved = safeResolve(dir);
1253
+ if (!resolved || seen.has(resolved)) {
1254
+ continue;
1255
+ }
1256
+ seen.add(resolved);
1257
+ let entries;
1258
+ try {
1259
+ entries = fs5.readdirSync(resolved, { withFileTypes: true });
1260
+ } catch {
1261
+ continue;
1262
+ }
1263
+ for (const entry of entries) {
1264
+ if (entry.isFile() && MANIFEST_NAMES.has(entry.name)) {
1265
+ manifests.push(path4.join(resolved, entry.name));
1266
+ if (manifests.length >= limit) {
1267
+ break;
1268
+ }
1269
+ }
1270
+ }
1271
+ if (depth >= maxDepth) {
1272
+ continue;
1273
+ }
1274
+ for (const entry of entries) {
1275
+ if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) {
1276
+ continue;
1277
+ }
1278
+ stack.push({ dir: path4.join(resolved, entry.name), depth: depth + 1 });
1279
+ }
1264
1280
  }
1265
- return true;
1281
+ return manifests;
1266
1282
  }
1267
- async function refreshPackageAccessFromCloudAuth({
1268
- config,
1269
- auth,
1270
- env = process.env,
1271
- platform = process.platform,
1272
- save = true
1273
- } = {}) {
1274
- const currentConfig = config || loadConfig(env, platform);
1275
- const registry = registryConfig(currentConfig, env, platform);
1276
- if (registry.token) {
1277
- return { config: currentConfig, refreshed: false };
1283
+ function nearestAgentsAncestor(manifestPath, root) {
1284
+ let current = path4.dirname(manifestPath);
1285
+ const stop = path4.resolve(root);
1286
+ while (current.startsWith(stop)) {
1287
+ if (path4.basename(current).toLowerCase() === "agents") {
1288
+ return current;
1289
+ }
1290
+ const parent = path4.dirname(current);
1291
+ if (parent === current) {
1292
+ break;
1293
+ }
1294
+ current = parent;
1278
1295
  }
1279
- const status = auth || activeCloudAuthStatus({ env, platform });
1280
- if (!status.authenticated || !status.apiUrl || !status.profile?.access_token) {
1281
- return { config: currentConfig, refreshed: false };
1296
+ return null;
1297
+ }
1298
+ function hasDirectManifest(root) {
1299
+ return [...MANIFEST_NAMES].some((name) => fs5.existsSync(path4.join(root, name)));
1300
+ }
1301
+ function displayPath(target, cwd) {
1302
+ const relative = path4.relative(cwd, target);
1303
+ if (!relative) {
1304
+ return ".";
1282
1305
  }
1283
- const packageCredentials = await fetchPackageCredentials({
1284
- apiUrl: status.apiUrl,
1285
- accessToken: status.profile.access_token
1286
- });
1287
- if (!packageCredentials?.token) {
1288
- return { config: currentConfig, refreshed: false };
1306
+ if (relative.startsWith("..") || path4.isAbsolute(relative)) {
1307
+ return target;
1289
1308
  }
1290
- const next = { ...currentConfig };
1291
- const stored = savePackageCredentials(next, packageCredentials, { platform });
1292
- if (stored && save) {
1293
- saveConfig(next, env, platform);
1309
+ return relative.startsWith(".") ? relative : `.${path4.sep}${relative}`;
1310
+ }
1311
+ function commonSearchRoots(home) {
1312
+ if (!home) {
1313
+ return [];
1294
1314
  }
1295
- return { config: next, refreshed: stored };
1315
+ return [
1316
+ "agents",
1317
+ "Projects",
1318
+ "Code",
1319
+ "Developer",
1320
+ "Documents",
1321
+ "Desktop"
1322
+ ].map((name) => path4.join(home, name));
1296
1323
  }
1297
- function setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl } = {}) {
1298
- const lines = ["Vendian login complete."];
1299
- if (cloudAuthApiUrl) {
1300
- lines.push(`Connected to ${cloudAuthApiUrl}.`);
1301
- lines.push("Next: vendian cloud local serve --agents-dir ./agents");
1302
- return lines;
1324
+ function uniqueExistingDirs(paths) {
1325
+ const result = [];
1326
+ const seen = /* @__PURE__ */ new Set();
1327
+ for (const item of paths) {
1328
+ const resolved = safeResolve(item);
1329
+ if (!resolved || seen.has(resolved)) {
1330
+ continue;
1331
+ }
1332
+ try {
1333
+ if (!fs5.statSync(resolved).isDirectory()) {
1334
+ continue;
1335
+ }
1336
+ } catch {
1337
+ continue;
1338
+ }
1339
+ seen.add(resolved);
1340
+ result.push(resolved);
1303
1341
  }
1304
- lines.push(`Next: ${cloudAuthLoginCommand({ backend, apiUrl })}`);
1305
- lines.push("Then: vendian cloud local serve --agents-dir ./agents");
1306
- return lines;
1342
+ return result;
1307
1343
  }
1308
- function cloudAuthLoginCommand({ backend, apiUrl } = {}) {
1309
- if (apiUrl) {
1310
- return `vendian cloud auth login --api-url ${apiUrl}`;
1344
+ function parentDirs(start, limit) {
1345
+ const parents = [];
1346
+ let current = path4.resolve(start);
1347
+ for (let index = 0; index < limit; index += 1) {
1348
+ const parent = path4.dirname(current);
1349
+ if (!parent || parent === current) {
1350
+ break;
1351
+ }
1352
+ parents.push(parent);
1353
+ current = parent;
1311
1354
  }
1312
- if (backend) {
1313
- return `vendian cloud auth login --backend ${backend}`;
1355
+ return parents;
1356
+ }
1357
+ function safeResolve(candidate) {
1358
+ try {
1359
+ return path4.resolve(candidate);
1360
+ } catch {
1361
+ return null;
1314
1362
  }
1315
- return "vendian cloud auth login";
1316
1363
  }
1317
1364
 
1365
+ // src/dev-server.js
1366
+ init_auth();
1367
+ init_constants();
1368
+ init_config();
1369
+
1318
1370
  // src/forward.js
1371
+ init_config();
1372
+ init_docs();
1373
+ init_install();
1374
+ init_paths();
1375
+ init_process();
1376
+ init_setup();
1377
+ import fs9 from "node:fs";
1319
1378
  var AUTO_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1320
1379
  async function forwardToPythonVendian(args, { env = process.env, platform = process.platform } = {}) {
1321
1380
  const invocation = await preparePythonVendianInvocation(args, { env, platform });
@@ -1446,7 +1505,12 @@ function reportProgress(onProgress, message) {
1446
1505
  }
1447
1506
  }
1448
1507
 
1508
+ // src/dev-server.js
1509
+ init_process();
1510
+ init_paths();
1511
+
1449
1512
  // src/workspaces.js
1513
+ init_process();
1450
1514
  async function listCloudWorkspaces({
1451
1515
  env = process.env,
1452
1516
  platform = process.platform,
@@ -2296,6 +2360,7 @@ function formatSeconds(value) {
2296
2360
  }
2297
2361
 
2298
2362
  // src/serve-log-store.js
2363
+ init_paths();
2299
2364
  import crypto2 from "node:crypto";
2300
2365
  import fs10 from "node:fs";
2301
2366
  import path7 from "node:path";
@@ -2435,7 +2500,7 @@ function buildLocalServeEventStreamArgs({ agentsDir: agentsDir2 = "./agents", co
2435
2500
  }
2436
2501
 
2437
2502
  // src/version.js
2438
- var CLI_VERSION = true ? "0.0.41" : process.env.npm_package_version || "0.0.0-dev";
2503
+ var CLI_VERSION = true ? "0.0.42" : process.env.npm_package_version || "0.0.0-dev";
2439
2504
 
2440
2505
  // src/dev-server.js
2441
2506
  var __dirname = path8.dirname(fileURLToPath(import.meta.url));
@@ -2572,6 +2637,8 @@ async function handleApi(req, res, pathname, parsed) {
2572
2637
  return apiServeStop(req, res);
2573
2638
  case "/api/create":
2574
2639
  return await apiCreate(req, res, body);
2640
+ case "/api/auth/login":
2641
+ return await apiAuthLogin(req, res, body);
2575
2642
  case "/api/auth/switch":
2576
2643
  return await apiAuthSwitch(req, res, body);
2577
2644
  default:
@@ -2738,6 +2805,36 @@ async function apiAuthSwitch(req, res, body) {
2738
2805
  jsonResponse(res, { ok: false, error: err.message });
2739
2806
  }
2740
2807
  }
2808
+ async function loginOrActivateBackend({ backend, env, setupFn, statusFn, activateFn } = {}) {
2809
+ if (!backend || !BACKEND_TARGETS[backend]) {
2810
+ return { ok: false, error: `Unknown backend: ${backend}` };
2811
+ }
2812
+ const authEnv = { ...env || process.env, VENDIAN_API_URL: "" };
2813
+ const readStatus = statusFn || cloudAuthStatus;
2814
+ const activate = activateFn || activateCloudProfile;
2815
+ const status = readStatus({ backend, env: authEnv });
2816
+ if (status.authenticated) {
2817
+ const result = activate({ backend, env: authEnv });
2818
+ if (result?.activated) {
2819
+ return { ok: true, switched: true, backend };
2820
+ }
2821
+ }
2822
+ const runSetup = setupFn || (async (options) => {
2823
+ const { setup: setup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
2824
+ return setup2(options);
2825
+ });
2826
+ await runSetup({ backend, forceAuth: true });
2827
+ return { ok: true, loggedIn: true, backend };
2828
+ }
2829
+ async function apiAuthLogin(req, res, body) {
2830
+ try {
2831
+ const result = await loginOrActivateBackend({ backend: body.backend });
2832
+ const status = result.ok ? 200 : 400;
2833
+ jsonResponse(res, result, status);
2834
+ } catch (err) {
2835
+ jsonResponse(res, { ok: false, error: err.message || "Login failed" }, 500);
2836
+ }
2837
+ }
2741
2838
  async function apiValidate(req, res, body) {
2742
2839
  const targetPath = body.path || agentsDir;
2743
2840
  const invocation = await preparePythonVendianInvocation(
@@ -3026,16 +3123,27 @@ function openUrl(url) {
3026
3123
  spawn3(shell || cmd, shell ? args : [url], { detached: true, stdio: "ignore" }).unref();
3027
3124
  }
3028
3125
 
3126
+ // src/main.js
3127
+ init_setup();
3128
+
3029
3129
  // src/tui.js
3030
3130
  init_constants();
3031
3131
  init_auth();
3132
+ init_config();
3032
3133
  import fs12 from "node:fs";
3033
3134
  import path9 from "node:path";
3034
3135
  import readline from "node:readline";
3136
+ init_install();
3035
3137
 
3036
3138
  // src/npm-update.js
3139
+ init_config();
3037
3140
  var NPM_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
3038
3141
 
3142
+ // src/tui.js
3143
+ init_paths();
3144
+ init_setup();
3145
+ init_process();
3146
+
3039
3147
  // src/ui/figures.js
3040
3148
  var supportsUnicode = process.platform !== "win32" || Boolean(
3041
3149
  process.env.WT_SESSION || // Windows Terminal