langgraph-cli 0.4.20__tar.gz → 0.4.21__tar.gz

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 (147) hide show
  1. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/PKG-INFO +1 -1
  2. langgraph_cli-0.4.21/langgraph_cli/__init__.py +1 -0
  3. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/cli.py +42 -0
  4. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/config.py +77 -17
  5. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/test_config.py +2 -2
  6. langgraph_cli-0.4.20/langgraph_cli/__init__.py +0 -1
  7. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/.gitignore +0 -0
  8. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/LICENSE +0 -0
  9. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/Makefile +0 -0
  10. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/README.md +0 -0
  11. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/.env.example +0 -0
  12. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/.gitignore +0 -0
  13. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/Makefile +0 -0
  14. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graph_prerelease_reqs/agent.py +0 -0
  15. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graph_prerelease_reqs/deps/additional_deps/pyproject.toml +0 -0
  16. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graph_prerelease_reqs/deps/zuper_deps/pyproject.toml +0 -0
  17. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graph_prerelease_reqs/langgraph.json +0 -0
  18. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graph_prerelease_reqs/pyproject.toml +0 -0
  19. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graph_prerelease_reqs_fail/agent.py +0 -0
  20. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graph_prerelease_reqs_fail/langgraph.json +0 -0
  21. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graph_prerelease_reqs_fail/pyproject.toml +0 -0
  22. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs/agent.py +0 -0
  23. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs/langgraph.json +0 -0
  24. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs/storm.py +0 -0
  25. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_a/__init__.py +0 -0
  26. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_a/graphs_submod/__init__.py +0 -0
  27. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_a/graphs_submod/agent.py +0 -0
  28. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_a/graphs_submod/subprompt.txt +0 -0
  29. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_a/hello.py +0 -0
  30. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_a/langgraph.json +0 -0
  31. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_a/prompt.txt +0 -0
  32. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_a/requirements.txt +0 -0
  33. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_b/graphs_submod/agent.py +0 -0
  34. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_b/graphs_submod/subprompt.txt +0 -0
  35. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_b/hello.py +0 -0
  36. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_b/langgraph.json +0 -0
  37. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_b/prompt.txt +0 -0
  38. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_b/requirements.txt +0 -0
  39. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_b/utils/__init__.py +0 -0
  40. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/graphs_reqs_b/utils/greeter.py +0 -0
  41. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/langgraph.json +0 -0
  42. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/my_app.py +0 -0
  43. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/pipconf.txt +0 -0
  44. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/poetry.lock +0 -0
  45. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/examples/pyproject.toml +0 -0
  46. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/generate_schema.py +0 -0
  47. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/.dockerignore +0 -0
  48. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/.editorconfig +0 -0
  49. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/.env.example +0 -0
  50. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/.eslintrc.cjs +0 -0
  51. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/.gitignore +0 -0
  52. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/LICENSE +0 -0
  53. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/README.md +0 -0
  54. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/jest.config.js +0 -0
  55. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/langgraph.json +0 -0
  56. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/package.json +0 -0
  57. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/src/agent/graph.ts +0 -0
  58. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/src/agent/state.ts +0 -0
  59. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/static/studio.png +0 -0
  60. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/tests/agent.test.ts +0 -0
  61. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/tests/graph.int.test.ts +0 -0
  62. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/tsconfig.json +0 -0
  63. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-examples/yarn.lock +0 -0
  64. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/.eslintrc.cjs +0 -0
  65. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/apps/agent/langgraph.json +0 -0
  66. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/apps/agent/package.json +0 -0
  67. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/apps/agent/src/graph.ts +0 -0
  68. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/apps/agent/src/state.ts +0 -0
  69. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/apps/agent/tsconfig.json +0 -0
  70. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/libs/shared/package.json +0 -0
  71. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/libs/shared/src/index.ts +0 -0
  72. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/libs/shared/tsconfig.json +0 -0
  73. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/package.json +0 -0
  74. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/tsconfig.json +0 -0
  75. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/turbo.json +0 -0
  76. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/js-monorepo-example/yarn.lock +0 -0
  77. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/__main__.py +0 -0
  78. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/analytics.py +0 -0
  79. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/archive.py +0 -0
  80. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/constants.py +0 -0
  81. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/deploy.py +0 -0
  82. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/docker.py +0 -0
  83. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/exec.py +0 -0
  84. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/host_backend.py +0 -0
  85. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/progress.py +0 -0
  86. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/py.typed +0 -0
  87. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/schemas.py +0 -0
  88. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/templates.py +0 -0
  89. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/util.py +0 -0
  90. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/uv_lock.py +0 -0
  91. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/langgraph_cli/version.py +0 -0
  92. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/pyproject.toml +0 -0
  93. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/apps/agent/.env.example +0 -0
  94. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/apps/agent/langgraph.json +0 -0
  95. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/apps/agent/pyproject.toml +0 -0
  96. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/apps/agent/src/agent/__init__.py +0 -0
  97. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/apps/agent/src/agent/graph.py +0 -0
  98. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/apps/agent/src/agent/state.py +0 -0
  99. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/libs/common/__init__.py +0 -0
  100. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/libs/common/helpers.py +0 -0
  101. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/libs/shared/pyproject.toml +0 -0
  102. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/libs/shared/src/shared/__init__.py +0 -0
  103. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/libs/shared/src/shared/utils.py +0 -0
  104. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/python-monorepo-example/pyproject.toml +0 -0
  105. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/schemas/schema.json +0 -0
  106. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/schemas/schema.v0.json +0 -0
  107. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/schemas/version.schema.json +0 -0
  108. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/__init__.py +0 -0
  109. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/integration_tests/__init__.py +0 -0
  110. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/integration_tests/test_cli.py +0 -0
  111. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/__init__.py +0 -0
  112. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/agent.py +0 -0
  113. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/cli/__init__.py +0 -0
  114. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/cli/langgraph.json +0 -0
  115. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/cli/pyproject.toml +0 -0
  116. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/cli/test_cli.py +0 -0
  117. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/cli/test_templates.py +0 -0
  118. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/conftest.py +0 -0
  119. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/graphs/agent.py +0 -0
  120. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/helpers.py +0 -0
  121. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/multiplatform/js.mts +0 -0
  122. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/multiplatform/python.py +0 -0
  123. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/pipconfig.txt +0 -0
  124. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/test_archive.py +0 -0
  125. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/test_config.json +0 -0
  126. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/test_deploy_helpers.py +0 -0
  127. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/test_docker.py +0 -0
  128. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/test_host_backend.py +0 -0
  129. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/test_logs_helpers.py +0 -0
  130. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/tests/unit_tests/test_util.py +0 -0
  131. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/apps/agent/.env.example +0 -0
  132. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/apps/agent/langgraph.json +0 -0
  133. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/apps/agent/pyproject.toml +0 -0
  134. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/apps/agent/src/agent/__init__.py +0 -0
  135. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/apps/agent/src/agent/graph.py +0 -0
  136. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/libs/shared/pyproject.toml +0 -0
  137. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/libs/shared/src/shared/__init__.py +0 -0
  138. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/libs/shared/src/shared/utils.py +0 -0
  139. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/pyproject.toml +0 -0
  140. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/monorepo/uv.lock +0 -0
  141. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/simple/.env.example +0 -0
  142. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/simple/langgraph.json +0 -0
  143. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/simple/pyproject.toml +0 -0
  144. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/simple/src/agent/__init__.py +0 -0
  145. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/simple/src/agent/graph.py +0 -0
  146. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv-examples/simple/uv.lock +0 -0
  147. {langgraph_cli-0.4.20 → langgraph_cli-0.4.21}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-cli
3
- Version: 0.4.20
3
+ Version: 0.4.21
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
@@ -0,0 +1 @@
1
+ __version__ = "0.4.21"
@@ -817,6 +817,48 @@ def dev(
817
817
  )
818
818
 
819
819
 
820
+ # ---------------------------------------------------------------------------
821
+ # validate command
822
+ # ---------------------------------------------------------------------------
823
+
824
+
825
+ @OPT_CONFIG
826
+ @cli.command(help="✅ Validate the LangGraph configuration file.")
827
+ @log_command
828
+ def validate(config: pathlib.Path):
829
+ import json
830
+
831
+ try:
832
+ with open(config) as f:
833
+ raw_config = json.load(f)
834
+ except json.JSONDecodeError as e:
835
+ raise click.UsageError(f"Invalid JSON in {config}: {e.args[0]}") from None
836
+
837
+ # Check for unknown keys before validation so they show alongside any error.
838
+ unknown_warnings = langgraph_cli.config.get_unknown_keys(raw_config)
839
+
840
+ try:
841
+ config_json = langgraph_cli.config.validate_config_file(config)
842
+ except (click.UsageError, ValueError) as e:
843
+ click.secho(f"Error: {e}", fg="red", err=True)
844
+ if unknown_warnings:
845
+ click.echo(err=True)
846
+ for warning in unknown_warnings:
847
+ click.secho(f" warning: {warning}", fg="yellow", err=True)
848
+ raise SystemExit(1) from None
849
+
850
+ num_graphs = len(config_json.get("graphs", {}))
851
+ click.secho(
852
+ f"Configuration file {config} is valid. "
853
+ f"({num_graphs} graph{'s' if num_graphs != 1 else ''} found)",
854
+ fg="green",
855
+ )
856
+ if unknown_warnings:
857
+ click.echo()
858
+ for warning in unknown_warnings:
859
+ click.secho(f" warning: {warning}", fg="yellow")
860
+
861
+
820
862
  # ---------------------------------------------------------------------------
821
863
  # new command
822
864
  # ---------------------------------------------------------------------------
@@ -182,7 +182,11 @@ def validate_config(config: Config) -> Config:
182
182
  "Version must be major or major.minor or major.minor.patch."
183
183
  )
184
184
  except TypeError:
185
- raise click.UsageError(f"Invalid version format: {api_version}") from None
185
+ raise click.UsageError(
186
+ f"Invalid version format: {api_version}.\n\n"
187
+ "Pin to a minor version, e.g.:\n"
188
+ ' "api_version": "0.8"'
189
+ ) from None
186
190
 
187
191
  config = {
188
192
  "node_version": node_version,
@@ -220,45 +224,51 @@ def validate_config(config: Config) -> Config:
220
224
  if major < min_major:
221
225
  raise click.UsageError(
222
226
  f"Node.js version {node_version} is not supported. "
223
- f"Minimum required version is {MIN_NODE_VERSION}."
227
+ f"Minimum required version is {MIN_NODE_VERSION}.\n\n"
228
+ f"Set node_version to {MIN_NODE_VERSION} or higher:\n"
229
+ f' "node_version": "{MIN_NODE_VERSION}"'
224
230
  )
225
231
  except ValueError as e:
226
232
  raise click.UsageError(str(e)) from None
227
233
 
228
234
  if pip_installer := config.get("pip_installer"):
229
- if pip_installer == "uv_lock":
230
- raise click.UsageError(
231
- "pip_installer 'uv_lock' has been replaced. Use "
232
- '`source: {"kind": "uv", "root": "..", '
233
- '"package": "my-agent"}`.'
234
- )
235
235
  if pip_installer not in ["auto", "pip", "uv"]:
236
236
  raise click.UsageError(
237
237
  f"Invalid pip_installer: '{pip_installer}'. "
238
- "Must be 'auto', 'pip', or 'uv'."
238
+ "Consider using uv-based source management instead:\n\n"
239
+ ' "source": {"kind": "uv", "root": ".."}'
239
240
  )
240
241
 
241
242
  source = config.get("source")
242
243
  source_kind = _get_source_kind(config)
243
244
  if source is not None and not isinstance(source, dict):
244
- raise click.UsageError("`source` must be an object.")
245
+ raise click.UsageError(
246
+ "`source` must be an object, e.g.:\n"
247
+ ' "source": {"kind": "uv", "root": ".."}'
248
+ )
245
249
  if source is not None and source_kind != "uv":
246
- raise click.UsageError("Invalid source.kind. Supported values: 'uv'.")
250
+ raise click.UsageError(
251
+ "Invalid source.kind. The only supported value is 'uv':\n"
252
+ ' "source": {"kind": "uv", "root": ".."}'
253
+ )
247
254
 
248
255
  if config.get("python_version"):
249
256
  pyversion = config["python_version"]
250
257
  if not pyversion.count(".") == 1 or not all(
251
258
  part.isdigit() for part in pyversion.split("-")[0].split(".")
252
259
  ):
260
+ parts = pyversion.split("-")[0].split(".")
261
+ fix = f"{parts[0]}.{parts[1]}" if len(parts) >= 2 else MIN_PYTHON_VERSION
253
262
  raise click.UsageError(
254
263
  f"Invalid Python version format: {pyversion}. "
255
- "Use 'major.minor' format (e.g., '3.11'). "
256
- "Patch version cannot be specified."
264
+ "Use 'major.minor' format patch version cannot be specified.\n\n"
265
+ f' "python_version": "{fix}"'
257
266
  )
258
267
  if _parse_version(pyversion) < _parse_version(MIN_PYTHON_VERSION):
259
268
  raise click.UsageError(
260
269
  f"Python version {pyversion} is not supported. "
261
- f"Minimum required version is {MIN_PYTHON_VERSION}."
270
+ f"Minimum required version is {MIN_PYTHON_VERSION}.\n\n"
271
+ f' "python_version": "{MIN_PYTHON_VERSION}"'
262
272
  )
263
273
  if "bullseye" in pyversion:
264
274
  raise click.UsageError(
@@ -269,12 +279,16 @@ def validate_config(config: Config) -> Config:
269
279
  if source_kind != "uv" and not config["dependencies"]:
270
280
  raise click.UsageError(
271
281
  "No dependencies found in config. "
272
- "Add at least one dependency to 'dependencies' list."
282
+ "Consider using uv-based source management:\n\n"
283
+ ' "source": {"kind": "uv", "root": ".."}'
273
284
  )
274
285
 
275
286
  if not config.get("graphs"):
276
287
  raise click.UsageError(
277
- "No graphs found in config. Add at least one graph to 'graphs' dictionary."
288
+ "No graphs found in config. Add at least one graph, e.g.:\n"
289
+ ' "graphs": {\n'
290
+ ' "agent": "./my_agent/graph.py:graph"\n'
291
+ " }"
278
292
  )
279
293
 
280
294
  # Validate image_distro config
@@ -287,7 +301,8 @@ def validate_config(config: Config) -> Config:
287
301
  if image_distro not in Distros.__args__:
288
302
  raise click.UsageError(
289
303
  f"Invalid image_distro: '{image_distro}'. "
290
- "Must be one of 'debian', 'wolfi', or 'bookworm'."
304
+ f"Must be one of: {', '.join(repr(d) for d in Distros.__args__)}.\n\n"
305
+ ' "image_distro": "wolfi" (recommended)'
291
306
  )
292
307
 
293
308
  if source_kind == "uv":
@@ -369,6 +384,51 @@ def validate_config(config: Config) -> Config:
369
384
  return config
370
385
 
371
386
 
387
+ # Keys recognized by validate_config (used to detect unknown fields).
388
+ _KNOWN_CONFIG_KEYS = {
389
+ "python_version",
390
+ "node_version",
391
+ "api_version",
392
+ "base_image",
393
+ "image_distro",
394
+ "pip_config_file",
395
+ "pip_installer",
396
+ "source",
397
+ "dependencies",
398
+ "dockerfile_lines",
399
+ "graphs",
400
+ "env",
401
+ "store",
402
+ "auth",
403
+ "encryption",
404
+ "http",
405
+ "webhooks",
406
+ "checkpointer",
407
+ "ui",
408
+ "ui_config",
409
+ "keep_pkg_tools",
410
+ # Internal / legacy (still recognized, may error separately)
411
+ "_INTERNAL_docker_tag",
412
+ "project_root",
413
+ "package",
414
+ }
415
+
416
+
417
+ def get_unknown_keys(raw_config: dict) -> list[str]:
418
+ """Return warnings for unrecognized top-level keys (typos, etc.)."""
419
+ import difflib
420
+
421
+ unknown = set(raw_config) - _KNOWN_CONFIG_KEYS
422
+ warnings: list[str] = []
423
+ for key in sorted(unknown):
424
+ close = difflib.get_close_matches(key, _KNOWN_CONFIG_KEYS, n=1)
425
+ if close:
426
+ warnings.append(f"Unknown key '{key}' — did you mean '{close[0]}'?")
427
+ else:
428
+ warnings.append(f"Unknown key '{key}' is not a recognized config field.")
429
+ return warnings
430
+
431
+
372
432
  def validate_config_file(config_path: pathlib.Path) -> Config:
373
433
  """Load and validate a configuration file."""
374
434
  with open(config_path) as f:
@@ -404,7 +404,7 @@ def test_validate_config_pip_installer():
404
404
  }
405
405
  )
406
406
  assert "Invalid pip_installer: 'conda'" in str(exc_info.value)
407
- assert "Must be 'auto', 'pip', or 'uv'" in str(exc_info.value)
407
+ assert "uv-based source management" in str(exc_info.value)
408
408
 
409
409
  with pytest.raises(click.UsageError) as exc_info:
410
410
  validate_config(
@@ -417,7 +417,7 @@ def test_validate_config_pip_installer():
417
417
  )
418
418
  assert "Invalid pip_installer: 'invalid'" in str(exc_info.value)
419
419
 
420
- with pytest.raises(click.UsageError, match="has been replaced"):
420
+ with pytest.raises(click.UsageError, match="Invalid pip_installer: 'uv_lock'"):
421
421
  validate_config(
422
422
  {
423
423
  "python_version": "3.11",
@@ -1 +0,0 @@
1
- __version__ = "0.4.20"
File without changes
File without changes
File without changes
File without changes