langgraph-cli 0.4.32.dev0__py3-none-any.whl → 0.4.34__py3-none-any.whl

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.
langgraph_cli/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.4.32.dev0"
1
+ __version__ = "0.4.34"
langgraph_cli/config.py CHANGED
@@ -6,6 +6,7 @@ import re
6
6
  import shlex
7
7
  import textwrap
8
8
  from collections import Counter
9
+ from collections.abc import Iterable
9
10
  from typing import Literal, NamedTuple
10
11
 
11
12
  import click
@@ -36,6 +37,10 @@ DISALLOWED_BUILD_COMMAND_CHARS = [
36
37
  # This blocks background execution (cmd &) while allowing command
37
38
  # chaining (cmd1 && cmd2) which is common in build commands.
38
39
  _SINGLE_AMPERSAND_RE = re.compile(r"(?<!&)&(?:&&)*(?!&)")
40
+ _GIT_HTTP_AUTHORITY_RES = (
41
+ re.compile(r"git\+https?://(?P<authority>[^/\s\"']+)", re.I),
42
+ re.compile(r"\bgit\s*=\s*[\"']https?://(?P<authority>[^/\s\"']+)", re.I),
43
+ )
39
44
  _API_VERSION_PATTERN = re.compile(
40
45
  r"^(?P<major>\d+)"
41
46
  r"(?:\.(?P<minor>\d+))?"
@@ -78,6 +83,62 @@ def has_disallowed_build_command_content(command: str) -> bool:
78
83
  return False
79
84
 
80
85
 
86
+ def _has_git_http_url_userinfo(dependency: str) -> bool:
87
+ """Check whether a Git HTTP URL contains userinfo."""
88
+ return any(
89
+ "@" in match.group("authority")
90
+ for pattern in _GIT_HTTP_AUTHORITY_RES
91
+ for match in pattern.finditer(dependency)
92
+ )
93
+
94
+
95
+ def _validate_git_http_url_userinfo(
96
+ values: Iterable[str], *, source: pathlib.Path | None = None
97
+ ) -> None:
98
+ """Reject credential-bearing Git HTTP URLs without echoing their values."""
99
+ if not any(_has_git_http_url_userinfo(value) for value in values):
100
+ return
101
+ message = (
102
+ "Git dependency URLs must not contain credentials or other URL "
103
+ "userinfo because generated Dockerfiles and image layers can retain "
104
+ "them. Use a credential-free Git URL and provide short-lived "
105
+ "credentials through your build environment's secret-backed Git "
106
+ "credential helper."
107
+ )
108
+ if source is not None:
109
+ message += f" Found in: {source}"
110
+ raise click.UsageError(message)
111
+
112
+
113
+ def _validate_git_http_url_userinfo_files(paths: Iterable[pathlib.Path]) -> None:
114
+ """Reject credential-bearing Git HTTP URLs in dependency files."""
115
+ for path in paths:
116
+ path = path.resolve()
117
+ if not path.is_file():
118
+ continue
119
+ try:
120
+ contents = path.read_text(encoding="utf-8", errors="replace")
121
+ except OSError:
122
+ raise click.UsageError(
123
+ f"Could not inspect dependency file for embedded credentials: {path}"
124
+ ) from None
125
+ _validate_git_http_url_userinfo([contents], source=path)
126
+
127
+
128
+ def _validate_local_dependency_files(config_path: pathlib.Path, config: Config) -> None:
129
+ """Validate dependency files copied into a non-uv Python image."""
130
+ paths: list[pathlib.Path] = []
131
+ for dependency in config["dependencies"]:
132
+ if not isinstance(dependency, str) or not dependency.startswith("."):
133
+ continue
134
+ root = (config_path.parent / dependency).resolve()
135
+ paths.extend(
136
+ root / name
137
+ for name in ("requirements.txt", "pyproject.toml", "setup.py", "setup.cfg")
138
+ )
139
+ _validate_git_http_url_userinfo_files(paths)
140
+
141
+
81
142
  MIN_PYTHON_VERSION = "3.11"
82
143
  DEFAULT_PYTHON_VERSION = "3.11"
83
144
 
@@ -320,7 +381,9 @@ def _get_source_kind(config: Config) -> str | None:
320
381
  return kind if isinstance(kind, str) else None
321
382
 
322
383
 
323
- def validate_config(config: Config) -> Config:
384
+ def validate_config(
385
+ config: Config, *, source_path: pathlib.Path | None = None
386
+ ) -> Config:
324
387
  """Validate a configuration dictionary."""
325
388
 
326
389
  graphs = config.get("graphs", {})
@@ -415,6 +478,15 @@ def validate_config(config: Config) -> Config:
415
478
  ' "source": {"kind": "uv", "root": ".."}'
416
479
  )
417
480
 
481
+ _validate_git_http_url_userinfo(
482
+ (
483
+ dependency
484
+ for dependency in config["dependencies"]
485
+ if isinstance(dependency, str)
486
+ ),
487
+ source=source_path,
488
+ )
489
+
418
490
  source = config.get("source")
419
491
  source_kind = _get_source_kind(config)
420
492
  if source is not None and not isinstance(source, dict):
@@ -609,7 +681,7 @@ def validate_config_file(config_path: pathlib.Path) -> Config:
609
681
  """Load and validate a configuration file."""
610
682
  with open(config_path) as f:
611
683
  config = json.load(f)
612
- validated = validate_config(config)
684
+ validated = validate_config(config, source_path=config_path.resolve())
613
685
  # Enforce the package.json doesn't enforce an
614
686
  # incompatible Node.js version
615
687
  if validated.get("node_version"):
@@ -1280,6 +1352,7 @@ def python_config_to_docker(
1280
1352
  api_version=api_version,
1281
1353
  build_tools_to_uninstall=build_tools_to_uninstall,
1282
1354
  )
1355
+ _validate_local_dependency_files(config_path, config)
1283
1356
  if pip_installer == "auto":
1284
1357
  if _image_supports_uv(base_image):
1285
1358
  pip_installer = "uv"
@@ -1490,7 +1563,18 @@ def node_config_to_docker(
1490
1563
  ) -> tuple[str, dict[str, str]]:
1491
1564
  # Calculate paths for monorepo support
1492
1565
  install_root = (
1493
- pathlib.Path(build_context).resolve() if build_context else config_path.parent
1566
+ pathlib.Path(build_context).resolve()
1567
+ if build_context
1568
+ else config_path.parent.resolve()
1569
+ )
1570
+ config_root = config_path.parent.resolve()
1571
+ dependency_roots = (
1572
+ (install_root, config_root) if install_root != config_root else (install_root,)
1573
+ )
1574
+ _validate_git_http_url_userinfo_files(
1575
+ root / name
1576
+ for root in dependency_roots
1577
+ for name in ("package.json", "package-lock.json", "yarn.lock", "pnpm-lock.yaml")
1494
1578
  )
1495
1579
  install_cmd = install_command or _get_node_pm_install_cmd(install_root)
1496
1580
  if build_context:
langgraph_cli/deploy.py CHANGED
@@ -32,7 +32,7 @@ from langgraph_cli.host_backend import (
32
32
  HostBackendError,
33
33
  SourceName,
34
34
  )
35
- from langgraph_cli.image_reference import ImageReference
35
+ from langgraph_cli.image_reference import DIGEST_SEPARATOR, ImageReference
36
36
  from langgraph_cli.progress import Progress
37
37
  from langgraph_cli.util import warn_non_wolfi_distro
38
38
 
@@ -624,6 +624,41 @@ def format_deployments_table(deployments: Sequence[dict[str, object]]) -> str:
624
624
  return "\n".join(lines)
625
625
 
626
626
 
627
+ def _extract_listener_namespaces(listener: dict[str, object]) -> str:
628
+ compute_config = listener.get("compute_config")
629
+ namespaces = (
630
+ compute_config.get("k8s_namespaces")
631
+ if isinstance(compute_config, dict)
632
+ else None
633
+ )
634
+ if isinstance(namespaces, list) and namespaces:
635
+ return ", ".join(str(namespace) for namespace in namespaces)
636
+ return "-"
637
+
638
+
639
+ def format_listeners_table(listeners: Sequence[dict[str, object]]) -> str:
640
+ headers = ("Listener ID", "Compute ID", "Namespaces")
641
+ rows = [
642
+ (
643
+ str(listener.get("id", "-") or "-"),
644
+ str(listener.get("compute_id", "-") or "-"),
645
+ _extract_listener_namespaces(listener),
646
+ )
647
+ for listener in listeners
648
+ ]
649
+ widths = [
650
+ max(len(headers[index]), *(len(row[index]) for row in rows))
651
+ for index in range(len(headers))
652
+ ]
653
+
654
+ def format_row(row: Sequence[str]) -> str:
655
+ return " ".join(value.ljust(widths[index]) for index, value in enumerate(row))
656
+
657
+ lines = [format_row(headers), format_row(tuple("-" * width for width in widths))]
658
+ lines.extend(format_row(row) for row in rows)
659
+ return "\n".join(lines)
660
+
661
+
627
662
  def format_revisions_table(revisions: Sequence[dict[str, object]]) -> str:
628
663
  headers = ("Revision ID", "Status", "Created At")
629
664
  latest_deployed_seen = False
@@ -1544,9 +1579,9 @@ def _ensure_customer_registry_source(existing: ExistingDeployment) -> None:
1544
1579
  if existing.source != _CUSTOMER_REGISTRY_SOURCE:
1545
1580
  raise click.UsageError(
1546
1581
  f"Deployment {existing.id} was not created from an external image "
1547
- "and cannot be updated with --push-to. Run without --push-to to keep "
1548
- "its current build mode, or use a different --name to create a new "
1549
- "deployment."
1582
+ "and cannot be updated with --push-to or --image-uri. Run without "
1583
+ "either flag to keep its current build mode, or use a different "
1584
+ "--name to create a new deployment."
1550
1585
  )
1551
1586
 
1552
1587
 
@@ -1599,9 +1634,22 @@ class RemoteBuildSource:
1599
1634
 
1600
1635
 
1601
1636
  @dataclass(frozen=True, slots=True)
1602
- class CustomerRegistrySource:
1637
+ class BuildAndPush:
1603
1638
  reference: ImageReference
1604
1639
  prebuilt_image: str | None
1640
+
1641
+
1642
+ @dataclass(frozen=True, slots=True)
1643
+ class PublishedImage:
1644
+ image_uri: str
1645
+
1646
+
1647
+ ImageSource = BuildAndPush | PublishedImage
1648
+
1649
+
1650
+ @dataclass(frozen=True, slots=True)
1651
+ class CustomerRegistrySource:
1652
+ image: ImageSource
1605
1653
  requested_placement: RequestedPlacement
1606
1654
 
1607
1655
  def run(self, ctx: DeployContext) -> DeployOutcome:
@@ -1686,16 +1734,22 @@ class CustomerRegistrySource:
1686
1734
  )
1687
1735
 
1688
1736
  def _publish(self, ctx: DeployContext, step: int) -> tuple[str, int]:
1689
- image = str(self.reference)
1737
+ if isinstance(self.image, PublishedImage):
1738
+ return self.image.image_uri, step
1739
+ image = str(self.image.reference)
1690
1740
  with Runner() as runner:
1691
- if self.prebuilt_image:
1692
- _log_deploy_step(step, f"Validating image {self.prebuilt_image}")
1741
+ if self.image.prebuilt_image:
1742
+ _log_deploy_step(step, f"Validating image {self.image.prebuilt_image}")
1693
1743
  _validate_prebuilt_image(
1694
- runner, self.prebuilt_image, verbose=ctx.verbose
1744
+ runner, self.image.prebuilt_image, verbose=ctx.verbose
1695
1745
  )
1696
1746
  runner.run(
1697
1747
  subp_exec(
1698
- "docker", "tag", self.prebuilt_image, image, verbose=ctx.verbose
1748
+ "docker",
1749
+ "tag",
1750
+ self.image.prebuilt_image,
1751
+ image,
1752
+ verbose=ctx.verbose,
1699
1753
  )
1700
1754
  )
1701
1755
  else:
@@ -1733,20 +1787,35 @@ def _push_reference(push_to: str, tag: str | None) -> ImageReference:
1733
1787
  return reference.with_tag(normalize_image_tag(tag or _DEFAULT_IMAGE_TAG))
1734
1788
 
1735
1789
 
1790
+ def _validate_image_uri(image_uri: str) -> str:
1791
+ value = image_uri.strip()
1792
+ if not value:
1793
+ raise click.UsageError("--image-uri must not be empty.")
1794
+ if DIGEST_SEPARATOR not in value:
1795
+ raise click.UsageError(
1796
+ "--image-uri must pin a digest, e.g. "
1797
+ f"repository{DIGEST_SEPARATOR}<sha256 hex>. Kubernetes can cache "
1798
+ "images by tag, so redeploying a mutable tag may silently keep "
1799
+ "running the previous image."
1800
+ )
1801
+ return value
1802
+
1803
+
1736
1804
  def _select_source(
1737
1805
  *,
1738
1806
  push_to: str | None,
1739
1807
  image: str | None,
1808
+ image_uri: str | None,
1740
1809
  image_name: str | None,
1741
1810
  tag: str | None,
1742
1811
  remote_build_flag: bool | None,
1743
1812
  placement: RequestedPlacement,
1744
1813
  selector: DeploymentSelector,
1745
1814
  ) -> DeploymentSource:
1746
- if push_to is None and placement.requested:
1815
+ if push_to is None and image_uri is None and placement.requested:
1747
1816
  raise click.UsageError(
1748
1817
  "--listener-id and --k8s-namespace only apply when creating a "
1749
- "deployment with --push-to."
1818
+ "deployment with --push-to or --image-uri."
1750
1819
  )
1751
1820
  if placement.requested and isinstance(selector, ById):
1752
1821
  raise click.UsageError(
@@ -1754,6 +1823,19 @@ def _select_source(
1754
1823
  "they cannot be set for an existing --deployment-id. Drop them, or "
1755
1824
  "create a new deployment with --name."
1756
1825
  )
1826
+ if image_uri is not None:
1827
+ if push_to is not None:
1828
+ raise click.UsageError("--image-uri cannot be combined with --push-to.")
1829
+ if image is not None:
1830
+ raise click.UsageError("--image-uri cannot be combined with --image.")
1831
+ if tag is not None:
1832
+ raise click.UsageError("--image-uri cannot be combined with --tag.")
1833
+ if remote_build_flag is not None:
1834
+ raise click.UsageError("--image-uri cannot be combined with --remote.")
1835
+ return CustomerRegistrySource(
1836
+ image=PublishedImage(_validate_image_uri(image_uri)),
1837
+ requested_placement=placement,
1838
+ )
1757
1839
  if push_to is not None:
1758
1840
  if remote_build_flag is True:
1759
1841
  raise click.UsageError("--push-to cannot be combined with --remote.")
@@ -1761,8 +1843,7 @@ def _select_source(
1761
1843
  if image is None:
1762
1844
  _require_local_docker()
1763
1845
  return CustomerRegistrySource(
1764
- reference=reference,
1765
- prebuilt_image=image,
1846
+ image=BuildAndPush(reference=reference, prebuilt_image=image),
1766
1847
  requested_placement=placement,
1767
1848
  )
1768
1849
  if image and remote_build_flag is True:
@@ -1793,7 +1874,10 @@ def _create_host_backend_client(
1793
1874
  host_url: str | None,
1794
1875
  api_key: str | None,
1795
1876
  env_vars: dict[str, str] | None = None,
1877
+ *,
1878
+ no_input: bool | None = None,
1796
1879
  ) -> HostBackendClient:
1880
+ effective_no_input = _no_input if no_input is None else no_input
1797
1881
  if env_vars is None:
1798
1882
  env_vars = _parse_env_from_config({}, pathlib.Path.cwd() / DEFAULT_CONFIG)
1799
1883
  resolved_api_key = api_key
@@ -1808,7 +1892,7 @@ def _create_host_backend_client(
1808
1892
  resolved_api_key = val
1809
1893
  break
1810
1894
  if not resolved_api_key:
1811
- if _no_input:
1895
+ if effective_no_input:
1812
1896
  raise click.ClickException(
1813
1897
  "No LangSmith API key found. Set LANGSMITH_API_KEY in the "
1814
1898
  "environment or .env file."
@@ -1833,6 +1917,8 @@ def _create_host_backend_client(
1833
1917
  def _call_host_backend_with_optional_tenant(
1834
1918
  client: HostBackendClient,
1835
1919
  operation: Callable[[HostBackendClient], _T],
1920
+ *,
1921
+ no_input: bool | None = None,
1836
1922
  ) -> _T:
1837
1923
  """Run *operation*, prompting for a workspace ID on org-scoped 403s.
1838
1924
 
@@ -1841,6 +1927,7 @@ def _call_host_backend_with_optional_tenant(
1841
1927
  in-place so all subsequent calls through the same instance are
1842
1928
  tenant-aware.
1843
1929
  """
1930
+ effective_no_input = _no_input if no_input is None else no_input
1844
1931
  prompted_for_tenant = False
1845
1932
 
1846
1933
  while True:
@@ -1852,7 +1939,7 @@ def _call_host_backend_with_optional_tenant(
1852
1939
  and err.status_code == 403
1853
1940
  and "requires workspace specification" in err.message
1854
1941
  ):
1855
- if _no_input:
1942
+ if effective_no_input:
1856
1943
  raise click.ClickException(
1857
1944
  "API key is org-scoped and requires a workspace ID. "
1858
1945
  "Set LANGSMITH_TENANT_ID in your .env file or "
@@ -2075,19 +2162,30 @@ def _deploy_base_options(
2075
2162
  "Give the tag here or with --tag (default: latest)."
2076
2163
  ),
2077
2164
  ),
2165
+ click.option(
2166
+ "--image-uri",
2167
+ help=(
2168
+ "Deploy an image that's already in a registry you manage, "
2169
+ "without building, retagging, or pushing anything. For "
2170
+ "self-hosted and hybrid LangSmith. Give the full reference, "
2171
+ "e.g. 123456789.dkr.ecr.us-east-1.amazonaws.com/agents/"
2172
+ "my-agent:v1.2.3 or ...@sha256:<digest>. Cannot be combined "
2173
+ "with --push-to, --image, --tag, or --remote."
2174
+ ),
2175
+ ),
2078
2176
  click.option(
2079
2177
  "--listener-id",
2080
2178
  help=(
2081
2179
  "Listener that will run the deployment, for workspaces that "
2082
2180
  "deploy through a listener in your own cluster. Only used when "
2083
- "creating a deployment with --push-to."
2181
+ "creating a deployment with --push-to or --image-uri."
2084
2182
  ),
2085
2183
  ),
2086
2184
  click.option(
2087
2185
  "--k8s-namespace",
2088
2186
  help=(
2089
2187
  "Kubernetes namespace the listener deploys into. Only used when "
2090
- "creating a deployment with --push-to."
2188
+ "creating a deployment with --push-to or --image-uri."
2091
2189
  ),
2092
2190
  ),
2093
2191
  click.option(
@@ -2200,6 +2298,7 @@ def _deploy_cmd(
2200
2298
  image_name: str | None,
2201
2299
  image: str | None,
2202
2300
  push_to: str | None,
2301
+ image_uri: str | None,
2203
2302
  listener_id: str | None,
2204
2303
  k8s_namespace: str | None,
2205
2304
  tag: str | None,
@@ -2273,6 +2372,7 @@ def _deploy_cmd(
2273
2372
  source = _select_source(
2274
2373
  push_to=push_to,
2275
2374
  image=image,
2375
+ image_uri=image_uri,
2276
2376
  image_name=image_name,
2277
2377
  tag=tag,
2278
2378
  remote_build_flag=remote_build_flag,
@@ -2408,6 +2508,53 @@ def deploy_list(
2408
2508
  click.echo(format_deployments_table(deployments))
2409
2509
 
2410
2510
 
2511
+ # ---------------------------------------------------------------------------
2512
+ # deploy listeners
2513
+ # ---------------------------------------------------------------------------
2514
+
2515
+
2516
+ @deploy.group(
2517
+ "listeners",
2518
+ cls=NestedHelpGroup,
2519
+ help="[Beta] Inspect listeners available to this workspace.",
2520
+ )
2521
+ def deploy_listeners() -> None:
2522
+ pass
2523
+
2524
+
2525
+ @OPT_HOST_API_KEY
2526
+ @OPT_HOST_URL
2527
+ @click.option(
2528
+ "--json",
2529
+ "json_output",
2530
+ is_flag=True,
2531
+ default=False,
2532
+ help="Emit the listeners as a JSON array instead of a table.",
2533
+ )
2534
+ @deploy_listeners.command(
2535
+ "list",
2536
+ help=(
2537
+ "[Beta] List listeners available to this workspace.\n\n"
2538
+ "Pass a listener's id to `langgraph deploy --push-to ... "
2539
+ "--listener-id <id>` to deploy through it."
2540
+ ),
2541
+ )
2542
+ def deploy_listeners_list(
2543
+ api_key: str | None, host_url: str | None, json_output: bool
2544
+ ) -> None:
2545
+ client = _create_host_backend_client(host_url, api_key, no_input=json_output)
2546
+ listeners = _call_host_backend_with_optional_tenant(
2547
+ client, lambda c: c.list_listeners(), no_input=json_output
2548
+ )
2549
+ if json_output:
2550
+ click.echo(json_mod.dumps(listeners))
2551
+ return
2552
+ if not listeners:
2553
+ click.echo("No listeners found for this workspace.")
2554
+ return
2555
+ click.echo(format_listeners_table(listeners))
2556
+
2557
+
2411
2558
  # ---------------------------------------------------------------------------
2412
2559
  # deploy revisions
2413
2560
  # ---------------------------------------------------------------------------
langgraph_cli/schemas.py CHANGED
@@ -650,7 +650,8 @@ class Config(TypedDict, total=False):
650
650
 
651
651
  pip_config_file: str | None
652
652
  """Optional. Path to a pip config file (e.g., "/etc/pip.conf" or "pip.ini") for controlling
653
- package installation (custom indices, credentials, etc.).
653
+ package installation (custom indices, timeouts, etc.). The file is copied into the
654
+ generated image, so it must not contain credentials or other secrets.
654
655
 
655
656
  Only relevant if Python dependencies are installed via pip. If omitted, default pip settings are used.
656
657
  """
@@ -689,6 +690,9 @@ class Config(TypedDict, total=False):
689
690
  - "." or "./src" if you have a local Python package
690
691
  - str (aka "anthropic") for a PyPI package
691
692
  - "git+https://github.com/org/repo.git@main" for a Git-based package
693
+ Git HTTP URLs must not contain userinfo such as a username or token. For private
694
+ dependencies, provide short-lived credentials through the build environment's
695
+ secret-backed Git credential helper.
692
696
  Defaults to an empty list, meaning no additional packages installed beyond your base environment.
693
697
 
694
698
  This field is not supported when `source.kind` is `uv`.
langgraph_cli/uv_lock.py CHANGED
@@ -880,6 +880,7 @@ def python_config_to_docker_uv_lock(
880
880
  _get_node_pm_install_cmd,
881
881
  _get_pip_cleanup_lines,
882
882
  _image_supports_uv,
883
+ _validate_git_http_url_userinfo_files,
883
884
  docker_tag,
884
885
  )
885
886
 
@@ -890,11 +891,20 @@ def python_config_to_docker_uv_lock(
890
891
  )
891
892
 
892
893
  config_root = config_path.parent.resolve()
894
+ source_root = config["source"].get("root", ".")
895
+ project_root = (config_root / source_root).resolve()
896
+ _validate_git_http_url_userinfo_files(
897
+ [project_root / "pyproject.toml", project_root / "uv.lock"]
898
+ )
899
+
893
900
  install_cmd = "uv pip install --system"
894
901
  _, global_reqs_pip_install, pip_config_file_str = _build_python_install_commands(
895
902
  config, install_cmd
896
903
  )
897
904
  plan = _plan_uv_lock_workspace(config_path, config)
905
+ _validate_git_http_url_userinfo_files(
906
+ package.pyproject_path for package in plan.install_order
907
+ )
898
908
 
899
909
  _update_uv_lock_graph_paths(config_path, config, plan)
900
910
  for section, key in [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: langgraph-cli
3
- Version: 0.4.32.dev0
3
+ Version: 0.4.34
4
4
  Summary: CLI for interacting with LangGraph API
5
5
  Project-URL: Source, https://github.com/langchain-ai/langgraph/tree/main/libs/cli
6
6
  Project-URL: Twitter, https://x.com/langchain_oss
@@ -125,6 +125,8 @@ The CLI uses a `langgraph.json` configuration file with these key settings:
125
125
  }
126
126
  ```
127
127
 
128
+ Git dependencies should use credential-free URLs. The CLI conservatively scans direct `langgraph.json` dependencies, common Python package files, uv project and lock files, and common Node.js package and lock files for HTTP Git URLs with userinfo. This check is not exhaustive: generated Docker builds can copy other files, including nested requirement or constraint files, into image layers without scanning them. For private dependencies, provide short-lived credentials through your build environment's secret-backed Git credential helper. Do not store credentials in copied files such as `langgraph.json` or `pip_config_file`.
129
+
128
130
  See the [full documentation](https://reference.langchain.com/python/langgraph-cli) for detailed configuration options.
129
131
 
130
132
  ## Development
@@ -1,26 +1,26 @@
1
- langgraph_cli/__init__.py,sha256=I9TqmokZP63vuEYFZAIL7-QidDgmqJW1iaf5M72vi34,28
1
+ langgraph_cli/__init__.py,sha256=lEcP5iYKQN3Lr-tUbNBN0qjKzIg4RjGwnIQfJt42ids,23
2
2
  langgraph_cli/__main__.py,sha256=8hDtWlaFZK24KhfNq_ZKgtXqYHsDQDetukOCMlsbW0Q,59
3
3
  langgraph_cli/_ignore.py,sha256=lh1JyfPtl0CbpCGtQuZ0ba2Cedsbblc2qLp-dmqdaRs,4193
4
4
  langgraph_cli/analytics.py,sha256=z0KGKtSToqR7Q9aFoVOPAoQt6kIQsiPDk1N9i8wkT98,2706
5
5
  langgraph_cli/archive.py,sha256=wmf5yXGU17Z8w5ZfIbRquXCaZ8W9OEH_BVfsJjGnZDI,4808
6
6
  langgraph_cli/cli.py,sha256=Q4kpglojg3rkRVFIuodIUrtxoQyDv6ZKzXAV_k5UONg,34316
7
- langgraph_cli/config.py,sha256=_M2PqcQ2dmt0ud9on7T9Ub8Va-E9DUWhufFMTYOBTSY,66742
7
+ langgraph_cli/config.py,sha256=pK-du8Qb9g0Dn0ojo8Rku_kB10MPtJJ1U87QJTsCr9g,69880
8
8
  langgraph_cli/constants.py,sha256=iSE-4HZJRd4CXzOmPZPKgPLWDDw1v__5s3AnwZtMMIg,362
9
9
  langgraph_cli/dependency_tracking.py,sha256=VaE5taa7qWP4McHhKJATcAXEC1XPPDrcPdj_gg9a8V0,4544
10
- langgraph_cli/deploy.py,sha256=2GKDcmNF2IzPjHcJXYQ0hpc6MU53bcxxGyf-0salleE,92025
10
+ langgraph_cli/deploy.py,sha256=vdGJiCTCgzLM43GJA1wpsroitOQ3z1dskMFQqwYpTGM,97137
11
11
  langgraph_cli/docker.py,sha256=rVeC60lXpXQ9AoGn50LfW6bfS0bFvzWGXo_4SO2HW_8,12888
12
12
  langgraph_cli/exec.py,sha256=vDtFhf2C_5jTTOZbV6cV1z2ioa947uBQvLj80eYXWH0,5343
13
13
  langgraph_cli/host_backend.py,sha256=vyeYZxWG46xwBTXel2a7mCg3EExgzftf-PetO9Jkl-E,12134
14
14
  langgraph_cli/image_reference.py,sha256=xX2VZ6tk9RRk-QjbWnDD38kIt-jTVFAMxB0pkYkxW-g,1107
15
15
  langgraph_cli/progress.py,sha256=YBWJ2TAScNt2Jv8sd3suWu-QkySPvQb4q7HFg9iJ3wU,3412
16
16
  langgraph_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- langgraph_cli/schemas.py,sha256=w8K3xC78G_3xKrOv38FXqdEFbhKcYkgo3whz1Ignjn0,27283
17
+ langgraph_cli/schemas.py,sha256=sf2Z-X53KpuBSqHW6jUKm94OVIEn_iDcba2vqx2Ndkg,27590
18
18
  langgraph_cli/templates.py,sha256=SPu0R1DIYOiNb8JxhKJX78zpakHiqoW9ddZRqRGRcu4,6978
19
19
  langgraph_cli/util.py,sha256=tDJBts8zF-F15icCsRjX2OfpjKBF-CAADj2Z8qW-BuU,1619
20
- langgraph_cli/uv_lock.py,sha256=gJ-s8iSUhrT73FrGKsthFX87hDfTJGXAZ2Uud3rQmgU,38521
20
+ langgraph_cli/uv_lock.py,sha256=t3CnL2rY5t6ThfGidqdY046O_FkbzHFwsmbvnqIWWN0,38909
21
21
  langgraph_cli/version.py,sha256=7oakgfTwsJJz0D5Sso_XKXkUzfLdN3fyVwgMTncms-A,308
22
- langgraph_cli-0.4.32.dev0.dist-info/METADATA,sha256=so9r_8HKAbYeNzjqPTcO5tm3RxL3SlWabxfIq7386Bk,5160
23
- langgraph_cli-0.4.32.dev0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
24
- langgraph_cli-0.4.32.dev0.dist-info/entry_points.txt,sha256=qNTLVRQfXrOI4SWPyBueUpAu_6-sZFNmj1Xkum0o0yY,52
25
- langgraph_cli-0.4.32.dev0.dist-info/licenses/LICENSE,sha256=2btS8uNUDWD_UNjw9ba6ZJt_00aUjEw9CGyK-xIHY8c,1072
26
- langgraph_cli-0.4.32.dev0.dist-info/RECORD,,
22
+ langgraph_cli-0.4.34.dist-info/METADATA,sha256=t4JaKnc0pSUdtcxjVexJSOjEkrG4HmlwTYhjaQTIg-s,5787
23
+ langgraph_cli-0.4.34.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
24
+ langgraph_cli-0.4.34.dist-info/entry_points.txt,sha256=qNTLVRQfXrOI4SWPyBueUpAu_6-sZFNmj1Xkum0o0yY,52
25
+ langgraph_cli-0.4.34.dist-info/licenses/LICENSE,sha256=2btS8uNUDWD_UNjw9ba6ZJt_00aUjEw9CGyK-xIHY8c,1072
26
+ langgraph_cli-0.4.34.dist-info/RECORD,,