@langchain/langgraph-cli 1.5.1-rc.0 → 1.5.2-dev.0

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.
@@ -1,6 +1,5 @@
1
1
  import { getDockerCapabilities } from "../docker/compose.mjs";
2
2
  import { assembleLocalDeps, configToDocker, getBaseImage, } from "../docker/docker.mjs";
3
- import { getExecaOptions } from "../docker/shell.mjs";
4
3
  import { getConfig } from "../utils/config.mjs";
5
4
  import { builder } from "./utils/builder.mjs";
6
5
  import { getProjectPath } from "./utils/project.mjs";
@@ -33,11 +32,11 @@ builder
33
32
  await getDockerCapabilities();
34
33
  const projectDir = path.dirname(configPath);
35
34
  const config = getConfig(await fs.readFile(configPath, "utf-8"));
36
- const opts = await getExecaOptions({
35
+ const opts = {
37
36
  cwd: projectDir,
38
37
  stderr: "inherit",
39
38
  stdout: "inherit",
40
- });
39
+ };
41
40
  const localDeps = await assembleLocalDeps(configPath, config);
42
41
  const input = await configToDocker(configPath, config, localDeps, {
43
42
  watch: false,
@@ -18,7 +18,7 @@ import { withAnalytics } from "./utils/analytics.mjs";
18
18
  import { getProjectPath } from "./utils/project.mjs";
19
19
  import { getConfig } from "../utils/config.mjs";
20
20
  import { assembleLocalDeps, configToDocker, getBaseImage, } from "../docker/docker.mjs";
21
- import { getExecaOptions } from "../docker/shell.mjs";
21
+ import { DOCKER_NOT_INSTALLED, DOCKER_NOT_RUNNING, isBinaryNotFound, } from "../docker/errors.mjs";
22
22
  import { HostBackendClient, HostBackendError, } from "./utils/host-backend.mjs";
23
23
  import { Emitter, Spinner } from "./utils/deploy-output.mjs";
24
24
  import { createArchive, BYTES_PER_MIB } from "./utils/archive.mjs";
@@ -276,23 +276,18 @@ async function callWithOptionalTenant(client, operation) {
276
276
  // ---------------------------------------------------------------------------
277
277
  // Build mode resolution
278
278
  // ---------------------------------------------------------------------------
279
- const DOCKER_NOT_INSTALLED = "Docker is required but not installed.\n" +
280
- "Install Docker Desktop: https://docs.docker.com/get-docker/";
281
- const DOCKER_NOT_RUNNING = "Docker is installed but not running.\nStart Docker and try again.";
282
279
  async function canBuildLocally() {
283
- let opts;
284
- try {
285
- opts = await getExecaOptions({ reject: false });
286
- }
287
- catch {
288
- return [false, DOCKER_NOT_INSTALLED];
289
- }
280
+ const opts = { reject: false };
290
281
  try {
291
282
  const info = await $(opts) `docker info`;
283
+ if (isBinaryNotFound(info))
284
+ return [false, DOCKER_NOT_INSTALLED];
292
285
  if (info.exitCode !== 0)
293
286
  return [false, DOCKER_NOT_RUNNING];
294
287
  }
295
- catch {
288
+ catch (error) {
289
+ if (isBinaryNotFound(error))
290
+ return [false, DOCKER_NOT_INSTALLED];
296
291
  return [false, DOCKER_NOT_RUNNING];
297
292
  }
298
293
  if (os.arch() !== "x64") {
@@ -331,15 +326,21 @@ async function resolveBuildMode(remoteBuildFlag) {
331
326
  }
332
327
  return [!supported, localBuildError];
333
328
  }
334
- async function resolveDeployment(client, step, deploymentId, name, notFoundMessage) {
329
+ async function resolveDeployment(client, step, deploymentId, name, notFoundMessage, agent) {
335
330
  if (deploymentId) {
336
331
  emitter.step(step, `Using deployment ${deploymentId}`);
337
332
  await callWithOptionalTenant(client, (c) => c.getDeployment(deploymentId));
338
333
  return { deploymentId, needsCreation: false, step: step + 1 };
339
334
  }
340
- emitter.step(step, `Looking up deployment '${name}'`);
341
- const response = await callWithOptionalTenant(client, (c) => c.listDeployments(name));
342
- const foundId = findDeploymentIdByName(response, name);
335
+ emitter.step(step, agent
336
+ ? `Looking up agent '${agent.agent_id}' in ${agent.environment}`
337
+ : `Looking up deployment '${name}'`);
338
+ const response = await callWithOptionalTenant(client, (c) => c.listDeployments(name, agent?.agent_id, agent?.environment));
339
+ let foundId = findDeploymentIdByName(response, name);
340
+ if (agent && Array.isArray(response.resources)) {
341
+ const found = response.resources.find((dep) => rec(dep).id && !rec(dep).is_preview);
342
+ foundId = found ? String(rec(found).id) : null;
343
+ }
343
344
  if (foundId) {
344
345
  emitter.info(`Found existing deployment (ID: ${foundId})`);
345
346
  return { deploymentId: foundId, needsCreation: false, step: step + 1 };
@@ -348,17 +349,28 @@ async function resolveDeployment(client, step, deploymentId, name, notFoundMessa
348
349
  return { deploymentId: undefined, needsCreation: true, step: step + 1 };
349
350
  }
350
351
  async function createDeployment(client, step, args) {
351
- emitter.step(step, `Creating deployment '${args.name}'`);
352
- const created = await client.createDeployment({
353
- name: args.name,
354
- deploymentType: args.deploymentType,
355
- source: args.source,
356
- secrets: args.secrets,
357
- });
352
+ emitter.step(step, args.agent
353
+ ? `Creating deployment for agent '${args.agent.agent_id}' in ${args.agent.environment}`
354
+ : `Creating deployment '${args.name}'`);
355
+ let created;
356
+ try {
357
+ created = await client.createDeployment(args);
358
+ }
359
+ catch (error) {
360
+ if (args.agent &&
361
+ error instanceof HostBackendError &&
362
+ error.statusCode === 409) {
363
+ throw new HostBackendError("This agent already has a deployment in this environment.", 409);
364
+ }
365
+ throw error;
366
+ }
358
367
  const id = typeof created.id === "string" ? created.id : undefined;
359
368
  if (!id) {
360
369
  throw new HostBackendError("POST /v2/deployments succeeded but response missing a valid 'id'");
361
370
  }
371
+ if (args.agent && created.name) {
372
+ emitter.info(`Deployment name: ${created.name}`);
373
+ }
362
374
  emitter.info(`Deployment ID: ${id}`, { deployment_id: id });
363
375
  return { id, step: step + 1 };
364
376
  }
@@ -583,7 +595,7 @@ async function runLocalBuild(args) {
583
595
  const dockerfile = generatedDockerfile.replace(/^FROM .*$/m, `FROM ${baseImageRef}`);
584
596
  const needsBuildx = os.arch() !== "x64";
585
597
  const localTag = `langgraph-deploy-tmp:${Math.floor(Date.now() / 1000)}`;
586
- const baseOpts = await getExecaOptions({ cwd: projectDir });
598
+ const baseOpts = { cwd: projectDir };
587
599
  const stdio = args.verbose
588
600
  ? { stdout: "inherit", stderr: "inherit" }
589
601
  : { stdout: "ignore", stderr: "ignore" };
@@ -807,12 +819,42 @@ async function runRemoteBuild(args) {
807
819
  await archive.cleanup();
808
820
  }
809
821
  }
822
+ // ---------------------------------------------------------------------------
823
+ // Main deploy orchestration
824
+ // ---------------------------------------------------------------------------
825
+ function resolveAgentOptions(opts) {
826
+ const agentId = opts.agentId ?? (process.env.LANGSMITH_AGENT_ID || undefined);
827
+ const agentEnvironment = opts.agentEnvironment ??
828
+ (process.env.LANGSMITH_AGENT_ENVIRONMENT || undefined);
829
+ if (agentId !== undefined || agentEnvironment !== undefined) {
830
+ emitter.note("Note: --agent-id and --agent-environment flags are in private beta");
831
+ }
832
+ if (agentId !== undefined && !agentId.trim()) {
833
+ throw new Error("--agent-id must not be empty.");
834
+ }
835
+ if (agentEnvironment !== undefined &&
836
+ !["development", "staging", "production"].includes(agentEnvironment)) {
837
+ throw new Error("--agent-environment must be development, staging, or production.");
838
+ }
839
+ return { agentId, agentEnvironment };
840
+ }
810
841
  async function runDeploy(dockerBuildArgs, opts, remoteBuildFlag) {
811
842
  emitter = new Emitter(opts.json);
812
843
  noInput = !opts.input;
813
844
  emitter.note("Note: 'langgraphjs deploy' is in beta. Expect frequent updates and improvements.");
814
845
  if (!opts.json)
815
846
  process.stdout.write("\n");
847
+ const { agentId, agentEnvironment } = resolveAgentOptions(opts);
848
+ let agent;
849
+ if (agentId !== undefined || agentEnvironment !== undefined) {
850
+ if (!agentId || !agentEnvironment) {
851
+ throw new Error("--agent-id and --agent-environment are required together.");
852
+ }
853
+ if (opts.name !== undefined || opts.deploymentId !== undefined) {
854
+ throw new Error("--agent-id and --agent-environment cannot be combined with --name or --deployment-id.");
855
+ }
856
+ agent = { agent_id: agentId, environment: agentEnvironment };
857
+ }
816
858
  if (!["dev", "prod"].includes(opts.deploymentType)) {
817
859
  throw new Error(`Invalid --deployment-type '${opts.deploymentType}' (expected 'dev' or 'prod').`);
818
860
  }
@@ -825,16 +867,16 @@ async function runDeploy(dockerBuildArgs, opts, remoteBuildFlag) {
825
867
  const envVars = await parseEnvFromConfig(rawConfig, configPath);
826
868
  let name = opts.name;
827
869
  let deploymentId = opts.deploymentId;
828
- if (!deploymentId && !name) {
870
+ if (!agent && !deploymentId && !name) {
829
871
  name = envVars[DEPLOYMENT_NAME_ENV] ?? process.env[DEPLOYMENT_NAME_ENV];
830
872
  }
831
- if (!deploymentId && !name) {
873
+ if (!agent && !deploymentId && !name) {
832
874
  const defaultName = normalizeName(path.basename(process.cwd()));
833
875
  name = noInput
834
876
  ? defaultName
835
877
  : await promptText("Deployment name", defaultName);
836
878
  }
837
- if (name && !deploymentId) {
879
+ if (!agent && name && !deploymentId) {
838
880
  name = normalizeName(name);
839
881
  if (!noInput) {
840
882
  const envPath = await resolveEnvPath(rawConfig, configPath);
@@ -856,12 +898,13 @@ async function runDeploy(dockerBuildArgs, opts, remoteBuildFlag) {
856
898
  let step = 1;
857
899
  const resolved = await resolveDeployment(client, step, deploymentId, name, useRemoteBuild
858
900
  ? "No deployment found. Will create."
859
- : "No deployment found. Will create after build.");
901
+ : "No deployment found. Will create after build.", agent);
860
902
  deploymentId = resolved.deploymentId;
861
903
  step = resolved.step;
862
904
  if (resolved.needsCreation) {
863
905
  const created = await createDeployment(client, step, {
864
- name: name,
906
+ name,
907
+ agent,
865
908
  deploymentType: opts.deploymentType,
866
909
  source: useRemoteBuild ? "internal_source" : "internal_docker",
867
910
  secrets,
@@ -953,8 +996,9 @@ async function runDeploy(dockerBuildArgs, opts, remoteBuildFlag) {
953
996
  async function runList(opts) {
954
997
  emitter = new Emitter(false);
955
998
  noInput = false;
999
+ const { agentId, agentEnvironment } = resolveAgentOptions(opts);
956
1000
  const client = await createHostBackendClient(opts.hostUrl, opts.apiKey);
957
- const response = await callWithOptionalTenant(client, (c) => c.listDeployments(opts.nameContains));
1001
+ const response = await callWithOptionalTenant(client, (c) => c.listDeployments(opts.nameContains, agentId, agentEnvironment));
958
1002
  const resources = Array.isArray(rec(response).resources)
959
1003
  ? rec(response).resources
960
1004
  : [];
@@ -1112,6 +1156,8 @@ const deploy = builder
1112
1156
  .option("--api-key <key>", "API key (or LANGGRAPH_HOST_API_KEY / LANGSMITH_API_KEY / LANGCHAIN_API_KEY).")
1113
1157
  .option("--name <name>", "Deployment name (or LANGSMITH_DEPLOYMENT_NAME). Defaults to the current directory name.")
1114
1158
  .option("--deployment-id <id>", "ID of an existing deployment to update.")
1159
+ .option("--agent-id <id>", "Agent ID (or LANGSMITH_AGENT_ID).")
1160
+ .option("--agent-environment <environment>", "Agent environment: development, staging, or production (or LANGSMITH_AGENT_ENVIRONMENT).")
1115
1161
  .option("--deployment-type <type>", "Deployment type when creating: 'dev' or 'prod'.", "dev")
1116
1162
  .option("--no-wait", "Skip waiting for deployment status.")
1117
1163
  .option("--verbose", "Show more output from the server logs.", false)
@@ -1146,6 +1192,8 @@ const deploy = builder
1146
1192
  deploy
1147
1193
  .command("list")
1148
1194
  .description("[Beta] List LangSmith Deployments.")
1195
+ .option("--agent-id <id>", "Agent ID (or LANGSMITH_AGENT_ID).")
1196
+ .option("--agent-environment <environment>", "Agent environment: development, staging, or production (or LANGSMITH_AGENT_ENVIRONMENT).")
1149
1197
  .option("--api-key <key>", "API key.")
1150
1198
  .option("--host-url <url>", "Host backend URL.", DEFAULT_HOST_URL)
1151
1199
  .option("--name-contains <value>", "Only show deployments whose names contain this value.", "")
package/dist/cli/dev.mjs CHANGED
@@ -7,6 +7,7 @@ import open from "open";
7
7
  import { startCloudflareTunnel } from "./cloudflare.mjs";
8
8
  import { createIpcServer } from "./utils/ipc/server.mjs";
9
9
  import { getProjectPath } from "./utils/project.mjs";
10
+ import { DEFAULT_STUDIO_URL, resolveStudioUrl } from "./utils/studio-url.mjs";
10
11
  import { getConfig } from "../utils/config.mjs";
11
12
  import { builder } from "./utils/builder.mjs";
12
13
  import { logError, logger } from "../utils/logging.mjs";
@@ -22,6 +23,7 @@ builder
22
23
  .option("-n, --n-jobs-per-worker <number>", "number of workers to run", "10")
23
24
  .option("-c, --config <path>", "path to configuration file", process.cwd())
24
25
  .option("--tunnel", "use Cloudflare Tunnel to expose the server to the internet")
26
+ .option("--studio-url <url>", `URL of the LangGraph Studio instance to connect to (defaults to ${DEFAULT_STUDIO_URL})`)
25
27
  .allowExcessArguments()
26
28
  .allowUnknownOption()
27
29
  .exitOverride((error) => gracefulExit(error.exitCode))
@@ -31,6 +33,7 @@ builder
31
33
  host: command.opts().host !== "localhost",
32
34
  n_jobs_per_worker: command.opts().nJobsPerWorker !== "10",
33
35
  tunnel: Boolean(command.opts().tunnel),
36
+ studio_url: Boolean(command.opts().studioUrl),
34
37
  })))
35
38
  .action(async (options, { args }) => {
36
39
  try {
@@ -46,7 +49,7 @@ builder
46
49
  let hasOpenedFlag = false;
47
50
  let child = undefined;
48
51
  let tunnel = undefined;
49
- let hostUrl = "https://smith.langchain.com";
52
+ let hostUrl = DEFAULT_STUDIO_URL;
50
53
  let envNoBrowser = process.env.BROWSER === "none";
51
54
  server.on("data", async (data) => {
52
55
  const response = z.object({ queryParams: z.string() }).parse(data);
@@ -103,16 +106,13 @@ builder
103
106
  const removedTarget = oldWatch.filter((target) => !newWatch.includes(target));
104
107
  watcher.unwatch(removedTarget).add(addedTarget);
105
108
  }
106
- try {
109
+ hostUrl = await resolveStudioUrl(options.studioUrl, async () => {
107
110
  const { Client } = await import("langsmith");
108
111
  const apiUrl = env?.["LANGSMITH_ENDPOINT"] ||
109
112
  env?.["LANGCHAIN_ENDPOINT"] ||
110
113
  undefined;
111
- hostUrl = new Client({ apiUrl }).getHostUrl() || hostUrl;
112
- }
113
- catch {
114
- // pass
115
- }
114
+ return new Client({ apiUrl }).getHostUrl();
115
+ });
116
116
  return { config, env, hostUrl };
117
117
  };
118
118
  const launchServer = async () => {
@@ -121,6 +121,7 @@ export async function spawnPythonServer(args, context, options) {
121
121
  context.configPath,
122
122
  ...(args.browser ? [] : ["--no-browser"]),
123
123
  ...(args.reload ? [] : ["--no-reload"]),
124
+ ...(args.studioUrl ? ["--studio-url", args.studioUrl] : []),
124
125
  ...args.rest,
125
126
  ], {
126
127
  stdio: ["inherit", "inherit", "inherit"],
package/dist/cli/up.mjs CHANGED
@@ -6,7 +6,6 @@ import { getProjectPath } from "./utils/project.mjs";
6
6
  import { logger } from "../utils/logging.mjs";
7
7
  import { createCompose, getDockerCapabilities } from "../docker/compose.mjs";
8
8
  import { configToCompose, getBaseImage } from "../docker/docker.mjs";
9
- import { getExecaOptions } from "../docker/shell.mjs";
10
9
  import { $ } from "execa";
11
10
  import { createHash } from "node:crypto";
12
11
  import dedent from "dedent";
@@ -71,11 +70,11 @@ builder
71
70
  watch: capabilities.watchAvailable,
72
71
  });
73
72
  const name = getProjectName(configPath);
74
- const execOpts = await getExecaOptions({
73
+ const execOpts = {
75
74
  cwd,
76
75
  stdout: "inherit",
77
76
  stderr: "inherit",
78
- });
77
+ };
79
78
  const exec = $(execOpts);
80
79
  if (!config._INTERNAL_docker_tag && params.pull) {
81
80
  // pull the image
@@ -160,7 +160,7 @@ export class HostBackendClient {
160
160
  */
161
161
  createDeployment(args) {
162
162
  const payload = {
163
- name: args.name,
163
+ ...(args.agent ? { agent: args.agent } : { name: args.name }),
164
164
  source: args.source,
165
165
  source_config: { deployment_type: args.deploymentType },
166
166
  source_revision_config: {},
@@ -179,9 +179,13 @@ export class HostBackendClient {
179
179
  * @param nameContains - Optional substring filter on deployment names.
180
180
  * @returns A paginated response object with a `resources` array.
181
181
  */
182
- listDeployments(nameContains = "") {
182
+ listDeployments(nameContains = "", agentId, agentEnvironment) {
183
183
  return this.request("GET", "/v2/deployments", {
184
- params: { name_contains: nameContains },
184
+ params: {
185
+ name_contains: nameContains,
186
+ agent_id: agentId,
187
+ agent_environment: agentEnvironment,
188
+ },
185
189
  });
186
190
  }
187
191
  /**
@@ -0,0 +1,16 @@
1
+ export const DEFAULT_STUDIO_URL = "https://smith.langchain.com";
2
+ /**
3
+ * Resolves the Studio host the same way as the Python CLI: an explicit
4
+ * `--studio-url` wins, then the host derived from the LangSmith endpoint,
5
+ * then the public LangSmith host. `deriveUrl` only runs without a flag.
6
+ */
7
+ export async function resolveStudioUrl(studioUrl, deriveUrl) {
8
+ if (studioUrl)
9
+ return studioUrl;
10
+ try {
11
+ return (await deriveUrl()) || DEFAULT_STUDIO_URL;
12
+ }
13
+ catch {
14
+ return DEFAULT_STUDIO_URL;
15
+ }
16
+ }
@@ -1,7 +1,7 @@
1
1
  import { $ } from "execa";
2
2
  import * as yaml from "yaml";
3
3
  import { z } from "zod/v3";
4
- import { getExecaOptions } from "./shell.mjs";
4
+ import { DOCKER_NOT_INSTALLED, isBinaryNotFound } from "./errors.mjs";
5
5
  export const DEFAULT_POSTGRES_URI = "postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable";
6
6
  const REDIS = {
7
7
  image: "redis:6",
@@ -53,10 +53,12 @@ function compareVersion(a, b) {
53
53
  export async function getDockerCapabilities() {
54
54
  let rawInfo = null;
55
55
  try {
56
- const { stdout } = await $(await getExecaOptions()) `docker info -f json`;
56
+ const { stdout } = await $ `docker info -f json`;
57
57
  rawInfo = JSON.parse(stdout);
58
58
  }
59
59
  catch (error) {
60
+ if (isBinaryNotFound(error))
61
+ throw new Error(DOCKER_NOT_INSTALLED);
60
62
  throw new Error("Docker not installed or not running: " + error);
61
63
  }
62
64
  const info = z
@@ -84,7 +86,7 @@ export async function getDockerCapabilities() {
84
86
  }
85
87
  else {
86
88
  try {
87
- const standalone = await $(await getExecaOptions()) `docker-compose --version --short`;
89
+ const standalone = await $ `docker-compose --version --short`;
88
90
  composeRes = {
89
91
  composeType: "standalone",
90
92
  versionCompose: parseVersion(standalone.stdout),
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared diagnostics for the Docker CLI invocations made by `build`, `up` and
3
+ * `deploy`.
4
+ *
5
+ * The `docker` binary is resolved from the inherited environment, exactly as
6
+ * any other command a user runs in their shell. These helpers turn a failed
7
+ * spawn into the same two-way distinction the Python CLI makes: the binary is
8
+ * missing, or the daemon is not answering.
9
+ */
10
+ export const DOCKER_NOT_INSTALLED = "Docker is required but not installed.\n" +
11
+ "Install Docker Desktop: https://docs.docker.com/get-docker/";
12
+ export const DOCKER_NOT_RUNNING = "Docker is installed but not running.\nStart Docker and try again.";
13
+ /**
14
+ * Whether a rejected (or `reject: false`) execa call failed because the binary
15
+ * could not be found on `PATH`, as opposed to running and exiting non-zero.
16
+ */
17
+ export function isBinaryNotFound(error) {
18
+ return (typeof error === "object" &&
19
+ error != null &&
20
+ "code" in error &&
21
+ error.code === "ENOENT");
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-cli",
3
- "version": "1.5.1-rc.0",
3
+ "version": "1.5.2-dev.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": "^18.19.0 || >=20.16.0"
@@ -39,8 +39,8 @@
39
39
  "winston-console-format": "^1.0.8",
40
40
  "yaml": "^2.8.3",
41
41
  "zod": "^3.25.76 || ^4",
42
- "create-langgraph": "1.1.5",
43
- "@langchain/langgraph-api": "1.5.1-rc.0"
42
+ "@langchain/langgraph-api": "1.5.2-dev.0",
43
+ "create-langgraph": "1.1.5"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/babel__code-frame": "^7.0.6",
@@ -1,62 +0,0 @@
1
- import { $ } from "execa";
2
- import { homedir } from "node:os";
3
- let PATH = undefined;
4
- // TODO: macOS related only
5
- async function getUserShell() {
6
- const dscl = await $({
7
- shell: true,
8
- }) `dscl . -read ~/ UserShell | sed 's/UserShell: //'`;
9
- return dscl.stdout.trim();
10
- }
11
- async function verifyDockerPath(PATH) {
12
- await $({ env: { PATH } }) `which docker`;
13
- return PATH;
14
- }
15
- // TODO: macOS related only
16
- async function extractPathFromShell() {
17
- const pathToShell = await getUserShell().catch(() => "/bin/zsh");
18
- const args = pathToShell.includes("csh")
19
- ? ["-c", "echo $PATH"]
20
- : ["-lc", "echo $PATH"];
21
- const shell = await $(pathToShell, args);
22
- return shell.stdout.trim();
23
- }
24
- // TODO: macOS related only
25
- async function guessUserPath() {
26
- return [
27
- "/bin",
28
- "/usr/bin",
29
- "/sbin",
30
- "/usr/sbin",
31
- "/opt/homebrew/bin",
32
- "/opt/homebrew/sbin",
33
- `${homedir()}/.local/bin`,
34
- "/Applications/Docker.app/Contents/Resources/bin",
35
- `${homedir()}/.docker/bin`,
36
- // support for Rancher Desktop
37
- // https://github.com/langchain-ai/langgraph-studio/issues/24#issuecomment-2274046328
38
- // https://github.com/langchain-ai/langgraph-studio/issues/122
39
- `${homedir()}/.rd/bin`,
40
- `/Applications/Rancher Desktop.app/Contents/Resources/resources/darwin/bin`,
41
- ].join(":");
42
- }
43
- async function getLoginPath() {
44
- if (PATH)
45
- return { PATH };
46
- const [fromShell, fromBackup] = await Promise.allSettled([extractPathFromShell(), guessUserPath()].map((promise) => promise.then(verifyDockerPath)));
47
- if (fromShell.status === "fulfilled") {
48
- PATH = fromShell.value;
49
- }
50
- else if (fromBackup.status === "fulfilled") {
51
- PATH = fromBackup.value;
52
- }
53
- else {
54
- console.error("Failed to get PATH from shell or backup", fromShell.reason, fromBackup.reason);
55
- throw fromShell.reason || fromBackup.reason;
56
- }
57
- return { PATH };
58
- }
59
- export async function getExecaOptions(options) {
60
- const env = await getLoginPath();
61
- return { ...options, env };
62
- }