@vellumai/cli 0.11.2 → 0.11.3-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/knip.json +1 -0
  2. package/node_modules/@vellumai/environments/package.json +2 -1
  3. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  4. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  5. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  6. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  7. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  8. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +18 -0
  13. package/node_modules/@vellumai/ipc-server-utils/src/ipc-framing.ts +295 -0
  14. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  15. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +444 -0
  16. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +236 -0
  17. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  18. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/package.json +2 -1
  19. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  20. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  21. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  22. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/index.ts +17 -6
  23. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  24. package/node_modules/@vellumai/local-mode/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  25. package/node_modules/@vellumai/local-mode/node_modules/nanoid/LICENSE +20 -0
  26. package/node_modules/@vellumai/local-mode/node_modules/nanoid/README.md +38 -0
  27. package/node_modules/@vellumai/local-mode/node_modules/nanoid/bin/nanoid.js +55 -0
  28. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.browser.js +29 -0
  29. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.d.ts +106 -0
  30. package/node_modules/@vellumai/local-mode/node_modules/nanoid/index.js +47 -0
  31. package/node_modules/@vellumai/local-mode/node_modules/nanoid/nanoid.js +1 -0
  32. package/node_modules/@vellumai/local-mode/node_modules/nanoid/non-secure/index.d.ts +48 -0
  33. package/node_modules/@vellumai/local-mode/node_modules/nanoid/non-secure/index.js +21 -0
  34. package/node_modules/@vellumai/local-mode/node_modules/nanoid/package.json +46 -0
  35. package/node_modules/@vellumai/local-mode/node_modules/nanoid/url-alphabet/index.js +2 -0
  36. package/node_modules/@vellumai/local-mode/package.json +1 -0
  37. package/node_modules/@vellumai/local-mode/src/__tests__/environment.test.ts +63 -1
  38. package/node_modules/@vellumai/local-mode/src/__tests__/gateway-proxy.test.ts +503 -0
  39. package/node_modules/@vellumai/local-mode/src/__tests__/guardian-token.test.ts +203 -0
  40. package/node_modules/@vellumai/local-mode/src/__tests__/loopback-auth.test.ts +46 -1
  41. package/node_modules/@vellumai/local-mode/src/__tests__/package-boundary.test.ts +6 -4
  42. package/node_modules/@vellumai/local-mode/src/__tests__/pair.test.ts +621 -0
  43. package/node_modules/@vellumai/local-mode/src/__tests__/status.test.ts +5 -0
  44. package/node_modules/@vellumai/local-mode/src/__tests__/unpair.test.ts +232 -0
  45. package/node_modules/@vellumai/local-mode/src/config.ts +126 -20
  46. package/node_modules/@vellumai/local-mode/src/environment.ts +36 -15
  47. package/node_modules/@vellumai/local-mode/src/gateway-proxy.ts +313 -25
  48. package/node_modules/@vellumai/local-mode/src/guardian-token.ts +222 -13
  49. package/node_modules/@vellumai/local-mode/src/index.ts +35 -3
  50. package/node_modules/@vellumai/local-mode/src/lockfile-contract.test.ts +18 -0
  51. package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +72 -0
  52. package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +130 -0
  53. package/node_modules/@vellumai/local-mode/src/lockfile.ts +134 -45
  54. package/node_modules/@vellumai/local-mode/src/pair.ts +354 -0
  55. package/node_modules/@vellumai/local-mode/src/paths.ts +49 -0
  56. package/node_modules/@vellumai/local-mode/src/status.ts +10 -8
  57. package/node_modules/@vellumai/local-mode/src/unpair.ts +101 -0
  58. package/node_modules/@vellumai/local-mode/src/util.ts +34 -0
  59. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  60. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  61. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  62. package/package.json +3 -1
  63. package/src/__tests__/client-token.test.ts +26 -1
  64. package/src/__tests__/guardian-token.test.ts +4 -0
  65. package/src/__tests__/nginx-ingress-command.test.ts +176 -16
  66. package/src/__tests__/nginx-ingress.test.ts +1821 -101
  67. package/src/__tests__/pair.test.ts +104 -2
  68. package/src/__tests__/sleep.test.ts +7 -3
  69. package/src/__tests__/tailscale-tunnel.test.ts +0 -1
  70. package/src/__tests__/tunnel.test.ts +1099 -55
  71. package/src/__tests__/wake.test.ts +166 -99
  72. package/src/commands/client.ts +182 -27
  73. package/src/commands/connect/import.ts +35 -156
  74. package/src/commands/nginx-ingress.ts +87 -110
  75. package/src/commands/pair.ts +84 -21
  76. package/src/commands/tunnel.ts +196 -85
  77. package/src/commands/upgrade.ts +3 -2
  78. package/src/commands/wake.ts +5 -163
  79. package/src/index.ts +1 -1
  80. package/src/lib/__tests__/local-ces.test.ts +10 -1
  81. package/src/lib/cloudflare-tunnel.ts +3 -16
  82. package/src/lib/environments/__tests__/paths.test.ts +20 -1
  83. package/src/lib/environments/paths.ts +29 -41
  84. package/src/lib/environments/resolve.ts +7 -5
  85. package/src/lib/guardian-token.ts +22 -62
  86. package/src/lib/ingress-config.ts +25 -0
  87. package/src/lib/local.ts +77 -20
  88. package/src/lib/nginx-ingress.ts +523 -76
  89. package/src/lib/ngrok.ts +154 -61
  90. package/src/lib/orphan-detection.test.ts +167 -0
  91. package/src/lib/orphan-detection.ts +40 -0
  92. package/src/lib/pair.test.ts +87 -0
  93. package/src/lib/pair.ts +55 -0
  94. package/src/lib/platform-client.ts +8 -5
  95. package/src/lib/tailscale-tunnel.ts +7 -18
  96. package/src/lib/tunnel-edge.ts +114 -0
  97. package/src/lib/xdg-log.ts +2 -2
package/src/lib/local.ts CHANGED
@@ -8,9 +8,19 @@ import {
8
8
  writeFileSync,
9
9
  } from "fs";
10
10
  import { createRequire } from "module";
11
+ import { Socket } from "net";
11
12
  import { homedir, networkInterfaces, platform, tmpdir } from "os";
12
13
  import { basename, dirname, join } from "path";
13
14
 
15
+ import {
16
+ findAssistantCommand,
17
+ isRepoCheckoutPath,
18
+ } from "@vellumai/environments";
19
+ import {
20
+ isNamedPipePath,
21
+ removeIpcEndpointFile,
22
+ resolveIpcEndpoint,
23
+ } from "@vellumai/ipc-server-utils";
14
24
  import { isValidReleaseVersion } from "@vellumai/local-mode";
15
25
 
16
26
  import {
@@ -134,11 +144,13 @@ function resolveBunExecutable(): string {
134
144
 
135
145
  function envWithBunPath(
136
146
  env: Record<string, string | undefined>,
147
+ commandDirs: string[] = [],
137
148
  ): Record<string, string | undefined> {
138
149
  const bunPath = resolveBunExecutable();
139
150
  const basePath = env.PATH || "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
140
151
  const extraDirs = [
141
152
  bunPath.includes("/") ? dirname(bunPath) : "",
153
+ ...commandDirs,
142
154
  join(homedir(), ".bun", "bin"),
143
155
  join(homedir(), ".local", "bin"),
144
156
  ].filter((dir) => dir && !basePath.split(":").includes(dir));
@@ -675,9 +687,15 @@ async function startDaemonFromSource(
675
687
  ...process.env,
676
688
  RUNTIME_HTTP_PORT: process.env.RUNTIME_HTTP_PORT || "7821",
677
689
  VELLUM_CLOUD: "local",
678
- VELLUM_DEV: "1",
679
690
  VELLUM_ENVIRONMENT: process.env.VELLUM_ENVIRONMENT || "local",
680
691
  };
692
+ // "From source" covers both a developer's checkout and the npm-installed
693
+ // runtime the desktop app runs. Only the former is a dev run: marking an
694
+ // installed runtime as dev suppresses its telemetry and skips the
695
+ // `assistant` command install. An inherited VELLUM_DEV is left alone.
696
+ if (isRepoCheckoutPath(assistantIndex)) {
697
+ env.VELLUM_DEV = "1";
698
+ }
681
699
  applyDaemonEnvOverrides(env, resources, options);
682
700
 
683
701
  // Write a sentinel PID file before spawning so concurrent hatch() calls
@@ -685,7 +703,11 @@ async function startDaemonFromSource(
685
703
  writeFileSync(pidFile, "starting", "utf-8");
686
704
 
687
705
  const bunPath = resolveBunExecutable();
688
- const spawnEnv = envWithBunPath(env);
706
+ const assistantCommand = findAssistantCommand(assistantIndex);
707
+ const spawnEnv = envWithBunPath(
708
+ env,
709
+ assistantCommand ? [dirname(assistantCommand)] : [],
710
+ );
689
711
  const child = foreground
690
712
  ? spawn(bunPath, ["run", daemonMainPath], {
691
713
  stdio: "inherit",
@@ -855,26 +877,55 @@ function resolveCesDir(resources?: LocalInstanceResources): string {
855
877
  }
856
878
 
857
879
  /**
858
- * Resolve the Unix socket path the CLI-launched CES sibling binds and the
859
- * daemon connects to. Both sides read `CES_LOCAL_SOCKET`, which the CLI sets to
860
- * this exact path so they agree. On macOS, long workspace paths are relocated
861
- * to a short tmpdir override (the same one the IPC sockets use) to stay under
862
- * the AF_UNIX path limit.
880
+ * Resolve the local IPC endpoint shared by the CLI-launched CES sibling and
881
+ * assistant. Windows uses a named pipe. POSIX uses a Unix socket, including
882
+ * the existing short macOS fallback.
863
883
  */
864
- function resolveCesSocketPath(resources?: LocalInstanceResources): string {
884
+ export function resolveCesSocketPath(
885
+ resources?: LocalInstanceResources,
886
+ hostPlatform: NodeJS.Platform = platform(),
887
+ ): string {
865
888
  const workspaceDir = resources
866
889
  ? join(resources.instanceDir, ".vellum", "workspace")
867
890
  : join(homedir(), ".vellum", "workspace");
891
+ if (hostPlatform === "win32") {
892
+ return resolveIpcEndpoint("ces", {
893
+ workspaceDir,
894
+ platform: hostPlatform,
895
+ }).path;
896
+ }
868
897
  const override = computeIpcSocketDirOverride(workspaceDir);
869
898
  const socketDir = override ?? workspaceDir;
870
899
  mkdirSync(socketDir, { recursive: true });
871
900
  return join(socketDir, "ces.sock");
872
901
  }
873
902
 
903
+ async function isIpcEndpointReady(endpointPath: string): Promise<boolean> {
904
+ if (!isNamedPipePath(endpointPath)) {
905
+ return existsSync(endpointPath);
906
+ }
907
+ return new Promise((resolve) => {
908
+ const socket = new Socket();
909
+ let settled = false;
910
+ const finish = (ready: boolean): void => {
911
+ if (settled) {
912
+ return;
913
+ }
914
+ settled = true;
915
+ clearTimeout(timer);
916
+ socket.destroy();
917
+ resolve(ready);
918
+ };
919
+ socket.once("connect", () => finish(true));
920
+ socket.once("error", () => finish(false));
921
+ const timer = setTimeout(() => finish(false), 100);
922
+ socket.connect(endpointPath);
923
+ });
924
+ }
925
+
874
926
  /**
875
- * Launch the local CES sibling over a Unix socket. The sibling model is now
876
- * the default topology for local (non-containerized) instances, matching how
877
- * containerized homes already run CES.
927
+ * Launch the local CES sibling over a local IPC endpoint. This is the default
928
+ * topology for local instances and matches the containerized CES layout.
878
929
  *
879
930
  * The sibling runs as an independent process with its lifecycle anchored to
880
931
  * SIGTERM, mirroring the gateway: a CLI-owned process with a PID file under
@@ -898,11 +949,7 @@ export async function startCes(
898
949
  const socketPath = resolveCesSocketPath(resources);
899
950
  // A stale socket file from an unclean shutdown blocks re-bind; CES unlinks it
900
951
  // on startup, but remove it here too so a leftover never masks a launch bug.
901
- try {
902
- unlinkSync(socketPath);
903
- } catch {
904
- /* no stale socket — fine */
905
- }
952
+ removeIpcEndpointFile(socketPath);
906
953
 
907
954
  const securityDir = resources
908
955
  ? join(resources.instanceDir, ".vellum", "protected")
@@ -957,14 +1004,18 @@ export async function startCes(
957
1004
  writeFileSync(cesPidFile, String(ces.pid), "utf-8");
958
1005
  }
959
1006
 
960
- // Wait for the socket to appear so the daemon's discovery finds it on the
1007
+ // Wait for the endpoint so the daemon's discovery finds it on the
961
1008
  // first probe rather than burning its retry budget.
962
1009
  const deadline = Date.now() + 10_000;
1010
+ let endpointReady = false;
963
1011
  while (Date.now() < deadline) {
964
- if (existsSync(socketPath)) break;
1012
+ endpointReady = await isIpcEndpointReady(socketPath);
1013
+ if (endpointReady) {
1014
+ break;
1015
+ }
965
1016
  await new Promise((r) => setTimeout(r, 50));
966
1017
  }
967
- if (!existsSync(socketPath)) {
1018
+ if (!endpointReady) {
968
1019
  console.warn(
969
1020
  "⚠ credential-executor started but its socket did not appear within 10s",
970
1021
  );
@@ -1394,7 +1445,13 @@ export async function startLocalDaemon(
1394
1445
  const localBinDir = join(home, ".local", "bin");
1395
1446
  const basePath =
1396
1447
  process.env.PATH || "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
1397
- const extraDirs = [bunBinDir, localBinDir].filter(
1448
+ // The compiled `assistant` ships beside the daemon binary, so its
1449
+ // directory is what puts `assistant …` on PATH for agent-run commands.
1450
+ const daemonBinaryDir = dirname(daemonBinary);
1451
+ const assistantBinaryDir = existsSync(join(daemonBinaryDir, "assistant"))
1452
+ ? [daemonBinaryDir]
1453
+ : [];
1454
+ const extraDirs = [...assistantBinaryDir, bunBinDir, localBinDir].filter(
1398
1455
  (d) => !basePath.split(":").includes(d),
1399
1456
  );
1400
1457
  const daemonEnv: Record<string, string> = {