@vendian/cli 0.0.6 → 0.0.7

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/README.md CHANGED
@@ -6,6 +6,8 @@ Command-line tools for signing in to Vendian and running local agent workflows.
6
6
  npm install -g @vendian/cli
7
7
  vendian
8
8
  vendian login
9
+ vendian init --output-dir ./agents
10
+ vendian create "My Agent" --output-dir ./agents
9
11
  vendian cloud local serve --agents-dir ./agents
10
12
  ```
11
13
 
@@ -16,6 +18,11 @@ vendian
16
18
  vendian login
17
19
  vendian doctor
18
20
  vendian update
21
+ vendian init --output-dir ./agents
22
+ vendian create "My Agent" --output-dir ./agents
23
+ vendian validate ./agents/my-agent --runtime
24
+ vendian test ./agents/my-agent --dry-run --mock-credentials
25
+ vendian models
19
26
  vendian cloud local serve --agents-dir ./agents
20
27
  ```
21
28
 
@@ -32,6 +39,13 @@ managed runtime without changing your agents.
32
39
  After login, regular `vendian cloud ...` commands are forwarded to the managed
33
40
  runtime installed by the CLI.
34
41
 
42
+ Agent authoring commands are also forwarded to the managed Python SDK CLI.
43
+ `vendian init` writes the current SDK-owned authoring docs into `.vendian-docs/`,
44
+ and `vendian create` scaffolds agents from the SDK templates. Updating the
45
+ managed runtime refreshes these docs and templates with the Python SDK. The CLI
46
+ remembers workspaces initialized with `vendian init` and refreshes their
47
+ `.vendian-docs/` automatically after `vendian update` or a managed auto-update.
48
+
35
49
  ## Local Data
36
50
 
37
51
  Vendian stores its managed runtime and CLI state under your user profile:
package/bin/vendian.cjs CHANGED
File without changes
package/cli-wrapper.cjs CHANGED
@@ -52,15 +52,21 @@ var import_node_path = __toESM(require("node:path"), 1);
52
52
  function pathApi(platform) {
53
53
  return platform === "win32" ? import_node_path.default.win32 : import_node_path.default.posix;
54
54
  }
55
+ function homeDir(env, platform) {
56
+ if (platform === "win32") {
57
+ return env.USERPROFILE || import_node_os.default.homedir();
58
+ }
59
+ return env.HOME || import_node_os.default.homedir();
60
+ }
55
61
  function vendianHome(env = process.env, platform = process.platform) {
56
62
  if (env.VENDIAN_CLI_HOME) {
57
63
  return pathApi(platform).resolve(env.VENDIAN_CLI_HOME);
58
64
  }
59
65
  if (platform === "win32") {
60
- const root = env.LOCALAPPDATA || import_node_path.default.join(import_node_os.default.homedir(), "AppData", "Local");
66
+ const root = env.LOCALAPPDATA || import_node_path.default.win32.join(homeDir(env, platform), "AppData", "Local");
61
67
  return import_node_path.default.win32.join(root, "Vendian", "cli");
62
68
  }
63
- return import_node_path.default.posix.join(import_node_os.default.homedir(), ".vendian", "cli");
69
+ return import_node_path.default.posix.join(homeDir(env, platform), ".vendian", "cli");
64
70
  }
65
71
  function configPath(env = process.env, platform = process.platform) {
66
72
  if (env.VENDIAN_CLI_CONFIG) {
@@ -239,12 +245,10 @@ function run(command, args, options = {}) {
239
245
  ...options
240
246
  });
241
247
  }
242
- function savePackageTokenSecret(token, config = {}, platform = process.platform) {
243
- if (!token) {
244
- return { ok: false };
245
- }
246
- if (platform === "darwin") {
247
- const result = run("security", [
248
+ function buildMacKeychainSaveCommand(token) {
249
+ return [
250
+ "security",
251
+ [
248
252
  "add-generic-password",
249
253
  "-a",
250
254
  PACKAGE_TOKEN_ACCOUNT,
@@ -253,7 +257,28 @@ function savePackageTokenSecret(token, config = {}, platform = process.platform)
253
257
  "-w",
254
258
  token,
255
259
  "-U"
256
- ]);
260
+ ]
261
+ ];
262
+ }
263
+ function buildMacKeychainLoadCommand() {
264
+ return [
265
+ "security",
266
+ [
267
+ "find-generic-password",
268
+ "-a",
269
+ PACKAGE_TOKEN_ACCOUNT,
270
+ "-s",
271
+ SERVICE,
272
+ "-w"
273
+ ]
274
+ ];
275
+ }
276
+ function savePackageTokenSecret(token, config = {}, platform = process.platform) {
277
+ if (!token) {
278
+ return { ok: false };
279
+ }
280
+ if (platform === "darwin") {
281
+ const result = run(...buildMacKeychainSaveCommand(token));
257
282
  return { ok: result.status === 0, provider: "macos-keychain" };
258
283
  }
259
284
  if (platform === "win32") {
@@ -286,14 +311,7 @@ function savePackageTokenSecret(token, config = {}, platform = process.platform)
286
311
  }
287
312
  function loadPackageTokenSecret(config = {}, platform = process.platform) {
288
313
  if (platform === "darwin") {
289
- const result = run("security", [
290
- "find-generic-password",
291
- "-a",
292
- PACKAGE_TOKEN_ACCOUNT,
293
- "-s",
294
- SERVICE,
295
- "-w"
296
- ]);
314
+ const result = run(...buildMacKeychainLoadCommand());
297
315
  return result.status === 0 ? result.stdout.trim() : "";
298
316
  }
299
317
  if (platform === "win32") {
@@ -417,77 +435,96 @@ function doctor({ env = process.env, platform = process.platform } = {}) {
417
435
  }
418
436
 
419
437
  // src/forward.js
438
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
439
+
440
+ // src/docs.js
420
441
  var import_node_fs5 = __toESM(require("node:fs"), 1);
421
- var AUTO_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
422
- async function forwardToPythonVendian(args, { env = process.env, platform = process.platform } = {}) {
423
- const venvPath = managedVenvPath(env, platform);
424
- const vendianPath = venvVendian(venvPath, platform);
425
- if (!import_node_fs5.default.existsSync(vendianPath)) {
426
- throw new Error("Vendian is not set up yet. Run `vendian login` first.");
427
- }
428
- maybeAutoUpdateManagedEnv({ env, platform, venvPath });
429
- const config = loadConfig(env, platform);
430
- const code = await spawnForward(vendianPath, args, {
431
- env: {
432
- ...env,
433
- ...packageIndexEnv(config, env, platform)
434
- }
435
- });
436
- process.exitCode = code;
442
+ var import_node_path4 = __toESM(require("node:path"), 1);
443
+ var DOC_WORKSPACES_KEY = "agentDocWorkspaces";
444
+ function pathApi2(platform) {
445
+ return platform === "win32" ? import_node_path4.default.win32 : import_node_path4.default.posix;
437
446
  }
438
- function packageIndexEnv(config = {}, env = process.env, platform = process.platform) {
439
- const registry = registryConfig(config, env, platform);
440
- const indexes = buildIndexUrls(registry);
441
- if (!indexes) {
442
- return {};
447
+ function commandWritesAgentDocs(args = []) {
448
+ return args[0] === "init";
449
+ }
450
+ function initOutputDir(args = []) {
451
+ for (let index = 1; index < args.length; index += 1) {
452
+ const arg = args[index];
453
+ if (arg === "--output-dir" || arg === "-o") {
454
+ return args[index + 1] || ".";
455
+ }
456
+ if (arg.startsWith("--output-dir=")) {
457
+ return arg.slice("--output-dir=".length) || ".";
458
+ }
443
459
  }
444
- const extraIndexUrls = joinIndexUrls([
445
- indexes.runtimeIndexUrl,
446
- "https://pypi.org/simple",
447
- env.VENDIAN_PIP_EXTRA_INDEX_URL
448
- ]);
449
- const uvExtraIndexUrls = joinIndexUrls([
450
- indexes.runtimeIndexUrl,
451
- "https://pypi.org/simple",
452
- env.VENDIAN_UV_EXTRA_INDEX_URL
453
- ]);
454
- return {
455
- VENDIAN_PIP_INDEX_URL: indexes.sdkIndexUrl,
456
- VENDIAN_PIP_EXTRA_INDEX_URL: extraIndexUrls,
457
- VENDIAN_UV_INDEX_URL: indexes.sdkIndexUrl,
458
- VENDIAN_UV_EXTRA_INDEX_URL: uvExtraIndexUrls
459
- };
460
+ return ".";
460
461
  }
461
- function joinIndexUrls(urls) {
462
- return urls.flatMap((value) => String(value || "").split(/\s+/)).map((value) => value.trim()).filter(Boolean).filter((value, index, all) => all.indexOf(value) === index).join(" ");
462
+ function resolveWorkspacePath(outputDir, { cwd = process.cwd(), platform = process.platform } = {}) {
463
+ const api = pathApi2(platform);
464
+ const raw = outputDir || ".";
465
+ return api.isAbsolute(raw) ? api.normalize(raw) : api.resolve(cwd, raw);
463
466
  }
464
- function maybeAutoUpdateManagedEnv({ env = process.env, platform = process.platform, venvPath = managedVenvPath(env, platform) } = {}) {
465
- if (env.VENDIAN_SKIP_AUTO_UPDATE === "1") {
466
- return false;
467
- }
467
+ function recordAgentDocsWorkspace(outputDir, {
468
+ env = process.env,
469
+ platform = process.platform,
470
+ cwd = process.cwd(),
471
+ now = /* @__PURE__ */ new Date()
472
+ } = {}) {
473
+ const workspacePath = resolveWorkspacePath(outputDir, { cwd, platform });
468
474
  const config = loadConfig(env, platform);
469
- const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
470
- if (Number.isFinite(lastUpdate) && Date.now() - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
471
- return false;
472
- }
473
- const registry = registryConfig(config, env);
474
- if (!registry.token) {
475
+ const existing = Array.isArray(config[DOC_WORKSPACES_KEY]) ? config[DOC_WORKSPACES_KEY] : [];
476
+ const workspaces = [workspacePath, ...existing.filter((entry) => entry !== workspacePath)];
477
+ const next = {
478
+ ...config,
479
+ [DOC_WORKSPACES_KEY]: workspaces,
480
+ lastAgentDocsInitAt: now.toISOString()
481
+ };
482
+ saveConfig(next, env, platform);
483
+ return next;
484
+ }
485
+ function recordForwardedDocsCommand(args, code, options = {}) {
486
+ if (code !== 0 || !commandWritesAgentDocs(args)) {
475
487
  return false;
476
488
  }
477
- const pythonPath = venvPython(venvPath, platform);
478
- if (!import_node_fs5.default.existsSync(pythonPath)) {
479
- return false;
489
+ recordAgentDocsWorkspace(initOutputDir(args), options);
490
+ return true;
491
+ }
492
+ function refreshAgentDocsWorkspaces({
493
+ config,
494
+ venvPath,
495
+ env = process.env,
496
+ platform = process.platform,
497
+ run: run2 = runInherit,
498
+ now = /* @__PURE__ */ new Date()
499
+ } = {}) {
500
+ const currentConfig = config || loadConfig(env, platform);
501
+ const workspaces = Array.isArray(currentConfig[DOC_WORKSPACES_KEY]) ? [...new Set(currentConfig[DOC_WORKSPACES_KEY].filter((entry) => typeof entry === "string" && entry.trim()))] : [];
502
+ if (workspaces.length === 0) {
503
+ saveConfig(currentConfig, env, platform);
504
+ return { config: currentConfig, refreshed: 0, failed: 0 };
480
505
  }
481
- try {
482
- console.error("[vendian] Checking managed CLI/runtime updates...");
483
- installVendianPackages({ pythonPath, venvPath, config, env });
484
- saveConfig({ ...config, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() }, env, platform);
485
- return true;
486
- } catch (error) {
487
- const message = error && typeof error.message === "string" ? error.message : String(error);
488
- console.error(`[vendian] Update check failed; continuing with installed CLI. ${message}`);
489
- return false;
506
+ const vendianPath = venvVendian(venvPath, platform);
507
+ let refreshed = 0;
508
+ let failed = 0;
509
+ for (const workspace of workspaces) {
510
+ if (!import_node_fs5.default.existsSync(workspace)) {
511
+ continue;
512
+ }
513
+ const status = run2(vendianPath, ["init", "--output-dir", workspace, "--yes"]);
514
+ if (status === 0) {
515
+ refreshed += 1;
516
+ } else {
517
+ failed += 1;
518
+ console.error(`[vendian] Could not refresh SDK docs in ${workspace}`);
519
+ }
490
520
  }
521
+ const next = {
522
+ ...currentConfig,
523
+ [DOC_WORKSPACES_KEY]: workspaces,
524
+ lastAgentDocsRefreshAt: now.toISOString()
525
+ };
526
+ saveConfig(next, env, platform);
527
+ return { config: next, refreshed, failed };
491
528
  }
492
529
 
493
530
  // src/setup.js
@@ -497,7 +534,7 @@ var import_node_fs7 = __toESM(require("node:fs"), 1);
497
534
  var import_node_crypto = __toESM(require("node:crypto"), 1);
498
535
  var import_node_http = __toESM(require("node:http"), 1);
499
536
  var import_node_fs6 = __toESM(require("node:fs"), 1);
500
- var import_node_path4 = __toESM(require("node:path"), 1);
537
+ var import_node_path5 = __toESM(require("node:path"), 1);
501
538
  var import_node_child_process3 = require("node:child_process");
502
539
  var DEFAULT_OAUTH_REDIRECT_PORT = 8765;
503
540
  function resolveApiUrl({ backend, apiUrl, env = process.env } = {}) {
@@ -541,6 +578,15 @@ async function loginWithVendianOAuth({ backend, apiUrl, noBrowser = false, env =
541
578
  await callback.close();
542
579
  }
543
580
  }
581
+ async function fetchPackageCredentials({ apiUrl, accessToken }) {
582
+ if (!apiUrl || !accessToken) {
583
+ return void 0;
584
+ }
585
+ const payload = await getJson(`${String(apiUrl).replace(/\/$/, "")}/api/v1/cli/package-credentials`, {
586
+ Authorization: `Bearer ${accessToken}`
587
+ });
588
+ return payload.packageCredentials;
589
+ }
544
590
  function loadCloudConfig(env = process.env, platform = process.platform) {
545
591
  const file = cloudConfigPath(env, platform);
546
592
  try {
@@ -565,6 +611,21 @@ function cloudAuthStatus({ backend, apiUrl, env = process.env, platform = proces
565
611
  profiles
566
612
  };
567
613
  }
614
+ function activeCloudAuthStatus({ env = process.env, platform = process.platform } = {}) {
615
+ const config = loadCloudConfig(env, platform);
616
+ const profiles = config.profiles && typeof config.profiles === "object" ? config.profiles : {};
617
+ const apiUrl = typeof config.active_api_url === "string" ? config.active_api_url : void 0;
618
+ const profile = apiUrl ? profiles[apiUrl] : void 0;
619
+ return {
620
+ apiUrl,
621
+ activeApiUrl: apiUrl,
622
+ profile: profile && typeof profile === "object" ? profile : void 0,
623
+ authenticated: Boolean(profile?.access_token),
624
+ email: typeof profile?.email === "string" ? profile.email : void 0,
625
+ expiresAt: typeof profile?.expires_at === "string" ? profile.expires_at : void 0,
626
+ profiles
627
+ };
628
+ }
568
629
  async function getOAuthConfig(apiUrl, redirectUri) {
569
630
  const url = new URL(`${apiUrl}/api/v1/cli/auth/oauth/config`);
570
631
  url.searchParams.set("redirectUri", redirectUri);
@@ -596,8 +657,8 @@ async function exchangeCodeForClerkToken(config, code, codeVerifier, redirectUri
596
657
  }
597
658
  return String(token);
598
659
  }
599
- async function getJson(url) {
600
- const response = await fetch(url, { headers: { Accept: "application/json" } });
660
+ async function getJson(url, headers = {}) {
661
+ const response = await fetch(url, { headers: { Accept: "application/json", ...headers } });
601
662
  return decodeResponse(response);
602
663
  }
603
664
  async function postJson(url, body) {
@@ -687,24 +748,30 @@ function openBrowser(url) {
687
748
  return;
688
749
  }
689
750
  if (platform === "darwin") {
690
- (0, import_node_child_process3.spawnSync)("open", [url], { stdio: "ignore", shell: false });
751
+ (0, import_node_child_process3.spawnSync)(...buildMacOpenCommand(url), { stdio: "ignore", shell: false });
691
752
  return;
692
753
  }
693
- (0, import_node_child_process3.spawnSync)("xdg-open", [url], { stdio: "ignore", shell: false });
754
+ (0, import_node_child_process3.spawnSync)(...buildLinuxOpenCommand(url), { stdio: "ignore", shell: false });
694
755
  }
695
756
  function buildWindowsOpenCommand(url) {
696
757
  return ["rundll32.exe", ["url.dll,FileProtocolHandler", String(url)]];
697
758
  }
759
+ function buildMacOpenCommand(url) {
760
+ return ["open", [String(url)]];
761
+ }
762
+ function buildLinuxOpenCommand(url) {
763
+ return ["xdg-open", [String(url)]];
764
+ }
698
765
  function cloudConfigPath(env = process.env, platform = process.platform) {
699
766
  if (env.VENDIAN_CLOUD_CONFIG) {
700
- return import_node_path4.default.resolve(env.VENDIAN_CLOUD_CONFIG);
767
+ return import_node_path5.default.resolve(env.VENDIAN_CLOUD_CONFIG);
701
768
  }
702
769
  if (platform === "win32") {
703
- const root2 = env.APPDATA || import_node_path4.default.join(process.env.USERPROFILE || "", "AppData", "Roaming");
704
- return import_node_path4.default.win32.join(root2, "Vendian", "cloud-auth.json");
770
+ const root2 = env.APPDATA || import_node_path5.default.join(process.env.USERPROFILE || "", "AppData", "Roaming");
771
+ return import_node_path5.default.win32.join(root2, "Vendian", "cloud-auth.json");
705
772
  }
706
- const root = env.XDG_CONFIG_HOME || import_node_path4.default.join(process.env.HOME || "", ".config");
707
- return import_node_path4.default.posix.join(root, "vendian", "cloud-auth.json");
773
+ const root = env.XDG_CONFIG_HOME || import_node_path5.default.join(process.env.HOME || "", ".config");
774
+ return import_node_path5.default.posix.join(root, "vendian", "cloud-auth.json");
708
775
  }
709
776
  function saveCloudToken(token, { env = process.env, platform = process.platform } = {}) {
710
777
  const file = cloudConfigPath(env, platform);
@@ -726,7 +793,7 @@ function saveCloudToken(token, { env = process.env, platform = process.platform
726
793
  scopes: token.scopes,
727
794
  tooling_eligible: token.toolingEligible
728
795
  };
729
- import_node_fs6.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
796
+ import_node_fs6.default.mkdirSync(import_node_path5.default.dirname(file), { recursive: true });
730
797
  import_node_fs6.default.writeFileSync(file, `${JSON.stringify(raw, null, 2)}
731
798
  `, { encoding: "utf8", mode: 384 });
732
799
  try {
@@ -771,24 +838,12 @@ async function setup({
771
838
  throw new Error("Vendian login succeeded, but the backend did not return package credentials. Configure CLI_PACKAGE_REGISTRY_TOKEN on the backend.");
772
839
  }
773
840
  if (packageCredentials) {
774
- next.gitlabHost = packageCredentials.gitlabHost || next.gitlabHost;
775
- next.gitlabUsername = packageCredentials.username || next.gitlabUsername;
776
- next.sdkPublicProjectId = packageCredentials.sdkProjectId || next.sdkPublicProjectId;
777
- next.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || next.sdkRuntimeProjectId;
778
- next.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || next.vendianAgentsVersion;
779
- next.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || next.vendianAgentsRuntimeVersion;
780
- const secret = savePackageTokenSecret(packageCredentials.token, next, platform);
781
- if (secret.ok) {
782
- Object.assign(next, secret.config || {});
783
- delete next.gitlabToken;
784
- console.log("Package access saved.");
785
- } else {
786
- next.gitlabToken = packageCredentials.token;
787
- console.log("Package access saved in the local Vendian CLI config file.");
788
- }
841
+ savePackageCredentials(next, packageCredentials, { platform });
789
842
  }
790
843
  } else if (auth.authenticated) {
791
844
  console.log(`Cloud authentication already saved for ${auth.apiUrl}${auth.email ? ` (${auth.email})` : ""}.`);
845
+ const refreshed = await refreshPackageAccessFromCloudAuth({ config: next, auth, env, platform });
846
+ Object.assign(next, refreshed.config);
792
847
  } else if (registry.token) {
793
848
  if (registry.tokenSource !== "secret-store") {
794
849
  next.gitlabToken = registry.token;
@@ -809,7 +864,8 @@ async function setup({
809
864
  console.log(`Python: ${pythonVersion(pythonPath) || pythonPath}`);
810
865
  saveConfig(next, env, platform);
811
866
  installVendianPackages({ pythonPath, venvPath, config: next, env, platform });
812
- saveConfig({ ...next, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() }, env, platform);
867
+ const updated = { ...next, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() };
868
+ refreshAgentDocsWorkspaces({ config: updated, venvPath, env, platform });
813
869
  if (!verifyVendianImports(pythonPath)) {
814
870
  throw new Error("Vendian packages installed, but import verification failed.");
815
871
  }
@@ -822,6 +878,57 @@ async function setup({
822
878
  console.log(line);
823
879
  }
824
880
  }
881
+ function savePackageCredentials(config, packageCredentials, { platform = process.platform } = {}) {
882
+ if (!packageCredentials?.token) {
883
+ return false;
884
+ }
885
+ config.gitlabHost = packageCredentials.gitlabHost || config.gitlabHost;
886
+ config.gitlabUsername = packageCredentials.username || config.gitlabUsername;
887
+ config.sdkPublicProjectId = packageCredentials.sdkProjectId || config.sdkPublicProjectId;
888
+ config.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || config.sdkRuntimeProjectId;
889
+ config.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || config.vendianAgentsVersion;
890
+ config.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || config.vendianAgentsRuntimeVersion;
891
+ const secret = savePackageTokenSecret(packageCredentials.token, config, platform);
892
+ if (secret.ok) {
893
+ Object.assign(config, secret.config || {});
894
+ delete config.gitlabToken;
895
+ console.log("Package access saved.");
896
+ } else {
897
+ config.gitlabToken = packageCredentials.token;
898
+ console.log("Package access saved in the local Vendian CLI config file.");
899
+ }
900
+ return true;
901
+ }
902
+ async function refreshPackageAccessFromCloudAuth({
903
+ config,
904
+ auth,
905
+ env = process.env,
906
+ platform = process.platform,
907
+ save = true
908
+ } = {}) {
909
+ const currentConfig = config || loadConfig(env, platform);
910
+ const registry = registryConfig(currentConfig, env, platform);
911
+ if (registry.token) {
912
+ return { config: currentConfig, refreshed: false };
913
+ }
914
+ const status = auth || activeCloudAuthStatus({ env, platform });
915
+ if (!status.authenticated || !status.apiUrl || !status.profile?.access_token) {
916
+ return { config: currentConfig, refreshed: false };
917
+ }
918
+ const packageCredentials = await fetchPackageCredentials({
919
+ apiUrl: status.apiUrl,
920
+ accessToken: status.profile.access_token
921
+ });
922
+ if (!packageCredentials?.token) {
923
+ return { config: currentConfig, refreshed: false };
924
+ }
925
+ const next = { ...currentConfig };
926
+ const stored = savePackageCredentials(next, packageCredentials, { platform });
927
+ if (stored && save) {
928
+ saveConfig(next, env, platform);
929
+ }
930
+ return { config: next, refreshed: stored };
931
+ }
825
932
  function setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl } = {}) {
826
933
  const lines = ["Vendian login complete."];
827
934
  if (cloudAuthApiUrl) {
@@ -843,13 +950,98 @@ function cloudAuthLoginCommand({ backend, apiUrl } = {}) {
843
950
  return "vendian cloud auth login";
844
951
  }
845
952
 
953
+ // src/forward.js
954
+ var AUTO_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
955
+ async function forwardToPythonVendian(args, { env = process.env, platform = process.platform } = {}) {
956
+ const venvPath = managedVenvPath(env, platform);
957
+ const vendianPath = venvVendian(venvPath, platform);
958
+ if (!import_node_fs8.default.existsSync(vendianPath)) {
959
+ throw new Error("Vendian is not set up yet. Run `vendian login` first.");
960
+ }
961
+ const loadedConfig = loadConfig(env, platform);
962
+ const requiresPackageAccess = commandNeedsPackageAccess(args);
963
+ const refreshed = requiresPackageAccess ? await refreshPackageAccessFromCloudAuth({ config: loadedConfig, env, platform }) : { config: loadedConfig };
964
+ maybeAutoUpdateManagedEnv({ env, platform, venvPath });
965
+ const config = refreshed.config;
966
+ const registry = registryConfig(config, env, platform);
967
+ if (requiresPackageAccess && !registry.token) {
968
+ throw new Error("Package access is missing. Run `vendian login` or `vendian update` before starting local agents.");
969
+ }
970
+ const code = await spawnForward(vendianPath, args, {
971
+ env: {
972
+ ...env,
973
+ ...packageIndexEnv(config, env, platform)
974
+ }
975
+ });
976
+ recordForwardedDocsCommand(args, code, { env, platform });
977
+ process.exitCode = code;
978
+ }
979
+ function commandNeedsPackageAccess(args = []) {
980
+ return args[0] === "cloud" && args[1] === "local" && ["serve", "run"].includes(args[2]);
981
+ }
982
+ function packageIndexEnv(config = {}, env = process.env, platform = process.platform) {
983
+ const registry = registryConfig(config, env, platform);
984
+ const indexes = buildIndexUrls(registry);
985
+ if (!indexes) {
986
+ return {};
987
+ }
988
+ const extraIndexUrls = joinIndexUrls([
989
+ indexes.runtimeIndexUrl,
990
+ "https://pypi.org/simple",
991
+ env.VENDIAN_PIP_EXTRA_INDEX_URL
992
+ ]);
993
+ const uvExtraIndexUrls = joinIndexUrls([
994
+ indexes.runtimeIndexUrl,
995
+ "https://pypi.org/simple",
996
+ env.VENDIAN_UV_EXTRA_INDEX_URL
997
+ ]);
998
+ return {
999
+ VENDIAN_PIP_INDEX_URL: indexes.sdkIndexUrl,
1000
+ VENDIAN_PIP_EXTRA_INDEX_URL: extraIndexUrls,
1001
+ VENDIAN_UV_INDEX_URL: indexes.sdkIndexUrl,
1002
+ VENDIAN_UV_EXTRA_INDEX_URL: uvExtraIndexUrls
1003
+ };
1004
+ }
1005
+ function joinIndexUrls(urls) {
1006
+ return urls.flatMap((value) => String(value || "").split(/\s+/)).map((value) => value.trim()).filter(Boolean).filter((value, index, all) => all.indexOf(value) === index).join(" ");
1007
+ }
1008
+ function maybeAutoUpdateManagedEnv({ env = process.env, platform = process.platform, venvPath = managedVenvPath(env, platform) } = {}) {
1009
+ if (env.VENDIAN_SKIP_AUTO_UPDATE === "1") {
1010
+ return false;
1011
+ }
1012
+ const config = loadConfig(env, platform);
1013
+ const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
1014
+ if (Number.isFinite(lastUpdate) && Date.now() - lastUpdate < AUTO_UPDATE_INTERVAL_MS) {
1015
+ return false;
1016
+ }
1017
+ const registry = registryConfig(config, env);
1018
+ if (!registry.token) {
1019
+ return false;
1020
+ }
1021
+ const pythonPath = venvPython(venvPath, platform);
1022
+ if (!import_node_fs8.default.existsSync(pythonPath)) {
1023
+ return false;
1024
+ }
1025
+ try {
1026
+ console.error("[vendian] Checking managed CLI/runtime updates...");
1027
+ installVendianPackages({ pythonPath, venvPath, config, env });
1028
+ const next = { ...config, lastManagedUpdateAt: (/* @__PURE__ */ new Date()).toISOString() };
1029
+ refreshAgentDocsWorkspaces({ config: next, venvPath, env, platform });
1030
+ return true;
1031
+ } catch (error) {
1032
+ const message = error && typeof error.message === "string" ? error.message : String(error);
1033
+ console.error(`[vendian] Update check failed; continuing with installed CLI. ${message}`);
1034
+ return false;
1035
+ }
1036
+ }
1037
+
846
1038
  // src/tui.js
847
- var import_node_fs8 = __toESM(require("node:fs"), 1);
1039
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
848
1040
  var import_node_readline = __toESM(require("node:readline"), 1);
849
1041
  var import_promises = __toESM(require("node:readline/promises"), 1);
850
1042
 
851
1043
  // src/version.js
852
- var CLI_VERSION = true ? "0.0.6" : process.env.npm_package_version || "0.0.0-dev";
1044
+ var CLI_VERSION = true ? "0.0.7" : process.env.npm_package_version || "0.0.0-dev";
853
1045
 
854
1046
  // src/tui.js
855
1047
  var RESET = "\x1B[0m";
@@ -871,6 +1063,8 @@ var ENDPOINTS = [
871
1063
  ];
872
1064
  var ACTIONS = [
873
1065
  { id: "connect", label: "Connect / switch endpoint", detail: "Sign in to local, dev, staging, production, or a custom API" },
1066
+ { id: "init", label: "Initialize agent docs", detail: "Write SDK-owned authoring docs into a workspace" },
1067
+ { id: "create", label: "Create agent", detail: "Scaffold a new agent through the Python SDK CLI" },
874
1068
  { id: "serve", label: "Start local agent server", detail: "Run agents from ./agents through the managed runtime" },
875
1069
  { id: "doctor", label: "Run doctor", detail: "Check Python, runtime, package access, and local paths" },
876
1070
  { id: "update", label: "Update managed runtime", detail: "Refresh the Vendian runtime packages" },
@@ -955,7 +1149,7 @@ function runtimeSummary({ env = process.env, platform = process.platform, now =
955
1149
  const config = loadConfig(env, platform);
956
1150
  const venvPath = managedVenvPath(env, platform);
957
1151
  const vendianPath = venvVendian(venvPath, platform);
958
- const installed = import_node_fs8.default.existsSync(vendianPath);
1152
+ const installed = import_node_fs9.default.existsSync(vendianPath);
959
1153
  const lastUpdate = Date.parse(config.lastManagedUpdateAt || "");
960
1154
  const stale = !Number.isFinite(lastUpdate) || now - lastUpdate > 24 * 60 * 60 * 1e3;
961
1155
  return {
@@ -969,6 +1163,30 @@ async function runAction(action, { env, platform, input, output }) {
969
1163
  await connectEndpoint({ env, platform, input, output });
970
1164
  return false;
971
1165
  }
1166
+ if (action === "init") {
1167
+ const outputDir = await prompt(input, output, "Workspace directory", ".");
1168
+ output.write(`${CLEAR}${SHOW_CURSOR}`);
1169
+ output.write(`[vendian] Initializing agent docs in ${outputDir || "."}
1170
+
1171
+ `);
1172
+ await forwardToPythonVendian(["init", "--output-dir", outputDir || "."], { env, platform });
1173
+ return true;
1174
+ }
1175
+ if (action === "create") {
1176
+ const name = await prompt(input, output, "Agent name");
1177
+ if (!name) {
1178
+ output.write("Agent name is required.\n");
1179
+ await prompt(input, output, "Press Enter to return");
1180
+ return false;
1181
+ }
1182
+ const outputDir = await prompt(input, output, "Output directory", "./agents");
1183
+ output.write(`${CLEAR}${SHOW_CURSOR}`);
1184
+ output.write(`[vendian] Creating agent ${name} in ${outputDir || "./agents"}
1185
+
1186
+ `);
1187
+ await forwardToPythonVendian(["create", name, "--output-dir", outputDir || "./agents"], { env, platform });
1188
+ return true;
1189
+ }
972
1190
  if (action === "serve") {
973
1191
  const agentsDir = await prompt(input, output, "Agents directory", "./agents");
974
1192
  output.write(`${CLEAR}${SHOW_CURSOR}`);
@@ -1037,6 +1255,11 @@ function helpText() {
1037
1255
  "Common commands:",
1038
1256
  " vendian",
1039
1257
  " vendian login",
1258
+ " vendian init --output-dir ./agents",
1259
+ ' vendian create "My Agent" --output-dir ./agents',
1260
+ " vendian validate ./agents/my-agent --runtime",
1261
+ " vendian test ./agents/my-agent --dry-run --mock-credentials",
1262
+ " vendian models",
1040
1263
  " vendian login --backend staging",
1041
1264
  " vendian login --api-url http://localhost:3000",
1042
1265
  " vendian cloud local serve --agents-dir ./agents",
@@ -1101,10 +1324,14 @@ Usage:
1101
1324
  vendian setup Alias for vendian login
1102
1325
  vendian doctor Check local bootstrap health
1103
1326
  vendian update Update the managed Vendian CLI/runtime
1104
- vendian <command> Run a Vendian cloud command
1327
+ vendian init Write current SDK agent docs into a workspace
1328
+ vendian create "My Agent" Scaffold a new agent from SDK templates
1329
+ vendian <command> Run a managed Python SDK/cloud command
1105
1330
 
1106
1331
  Examples:
1107
1332
  vendian login
1333
+ vendian init --output-dir ./agents
1334
+ vendian create "Google Drive Processor" --output-dir ./agents
1108
1335
  vendian login --backend staging
1109
1336
  vendian cloud local serve --agents-dir ./agents
1110
1337
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vendian/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Public Vendian CLI bootstrapper and launcher",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,