graph-agents-cli 0.3.1__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.
Files changed (291) hide show
  1. graph_agents_cli/__init__.py +26 -0
  2. graph_agents_cli/_api_policy.py +2145 -0
  3. graph_agents_cli/_approvals.py +400 -0
  4. graph_agents_cli/_build.py +186 -0
  5. graph_agents_cli/_build_info.json +7 -0
  6. graph_agents_cli/_chat_client.py +462 -0
  7. graph_agents_cli/_click.py +157 -0
  8. graph_agents_cli/_defaults.py +139 -0
  9. graph_agents_cli/_experiments.py +64 -0
  10. graph_agents_cli/_http.py +192 -0
  11. graph_agents_cli/_output.py +83 -0
  12. graph_agents_cli/_project.py +462 -0
  13. graph_agents_cli/_remote.py +220 -0
  14. graph_agents_cli/_response_schema.py +264 -0
  15. graph_agents_cli/_runner.py +319 -0
  16. graph_agents_cli/_skills_check.py +274 -0
  17. graph_agents_cli/_tools.py +189 -0
  18. graph_agents_cli/_trust.py +66 -0
  19. graph_agents_cli/api/__init__.py +15 -0
  20. graph_agents_cli/api/_changes.py +506 -0
  21. graph_agents_cli/api/_files.py +658 -0
  22. graph_agents_cli/api/cmd_api.py +2480 -0
  23. graph_agents_cli/deploy/__init__.py +15 -0
  24. graph_agents_cli/deploy/_config.py +171 -0
  25. graph_agents_cli/deploy/_image.py +128 -0
  26. graph_agents_cli/deploy/_kube.py +286 -0
  27. graph_agents_cli/deploy/_modes.py +234 -0
  28. graph_agents_cli/deploy/_preflight.py +370 -0
  29. graph_agents_cli/deploy/_values.py +168 -0
  30. graph_agents_cli/deploy/cmd_deploy.py +1866 -0
  31. graph_agents_cli/deploy/gitops.py +562 -0
  32. graph_agents_cli/deploy/local_load.py +273 -0
  33. graph_agents_cli/dev/__init__.py +13 -0
  34. graph_agents_cli/dev/cmd_build.py +131 -0
  35. graph_agents_cli/dev/cmd_install.py +78 -0
  36. graph_agents_cli/dev/cmd_lint.py +119 -0
  37. graph_agents_cli/dev/cmd_playground.py +297 -0
  38. graph_agents_cli/dev/policy_check.py +1287 -0
  39. graph_agents_cli/eval/__init__.py +22 -0
  40. graph_agents_cli/eval/_client.py +670 -0
  41. graph_agents_cli/eval/_common.py +177 -0
  42. graph_agents_cli/eval/_judge.py +168 -0
  43. graph_agents_cli/eval/_judge_runner.py +238 -0
  44. graph_agents_cli/eval/_paths.py +212 -0
  45. graph_agents_cli/eval/checks.py +581 -0
  46. graph_agents_cli/eval/cmd_analyze.py +278 -0
  47. graph_agents_cli/eval/cmd_compare.py +284 -0
  48. graph_agents_cli/eval/cmd_eval_group.py +80 -0
  49. graph_agents_cli/eval/cmd_generate.py +558 -0
  50. graph_agents_cli/eval/cmd_grade.py +466 -0
  51. graph_agents_cli/eval/cmd_metric.py +156 -0
  52. graph_agents_cli/eval/cmd_run.py +370 -0
  53. graph_agents_cli/eval/cmd_submit.py +400 -0
  54. graph_agents_cli/eval/config.py +435 -0
  55. graph_agents_cli/eval/dataset.py +350 -0
  56. graph_agents_cli/eval/gate.py +420 -0
  57. graph_agents_cli/eval/transcript.py +192 -0
  58. graph_agents_cli/extension/__init__.py +13 -0
  59. graph_agents_cli/extension/_compat.py +86 -0
  60. graph_agents_cli/extension/_loader.py +293 -0
  61. graph_agents_cli/extension/_manifest.py +135 -0
  62. graph_agents_cli/extension/_overrides.py +195 -0
  63. graph_agents_cli/extension/_paths.py +91 -0
  64. graph_agents_cli/extension/_refs.py +193 -0
  65. graph_agents_cli/extension/_resolver.py +453 -0
  66. graph_agents_cli/extension/_schema.py +106 -0
  67. graph_agents_cli/extension/_spec.py +253 -0
  68. graph_agents_cli/extension/_sync.py +102 -0
  69. graph_agents_cli/extension/_trust.py +58 -0
  70. graph_agents_cli/extension/cmd_extension_add.py +259 -0
  71. graph_agents_cli/extension/cmd_extension_group.py +57 -0
  72. graph_agents_cli/extension/cmd_extension_list.py +56 -0
  73. graph_agents_cli/extension/cmd_extension_remove.py +61 -0
  74. graph_agents_cli/extension/cmd_extension_update.py +195 -0
  75. graph_agents_cli/info/__init__.py +13 -0
  76. graph_agents_cli/info/cmd_info.py +222 -0
  77. graph_agents_cli/infra/__init__.py +15 -0
  78. graph_agents_cli/infra/checks.py +1169 -0
  79. graph_agents_cli/infra/cmd_infra.py +103 -0
  80. graph_agents_cli/main.py +591 -0
  81. graph_agents_cli/peer/__init__.py +15 -0
  82. graph_agents_cli/peer/_generate.py +254 -0
  83. graph_agents_cli/peer/cmd_peer.py +1151 -0
  84. graph_agents_cli/run/__init__.py +13 -0
  85. graph_agents_cli/run/_local_server.py +1157 -0
  86. graph_agents_cli/run/_signals.py +141 -0
  87. graph_agents_cli/run/cmd_approvals.py +530 -0
  88. graph_agents_cli/run/cmd_run.py +1421 -0
  89. graph_agents_cli/scaffold/__init__.py +19 -0
  90. graph_agents_cli/scaffold/agents/README.md +24 -0
  91. graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
  92. graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
  93. graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
  94. graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
  95. graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
  96. graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
  97. graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
  98. graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
  99. graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
  100. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
  101. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
  102. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
  103. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
  104. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
  105. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
  106. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
  107. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
  108. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
  109. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
  110. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
  111. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
  112. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
  113. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
  114. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
  115. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
  116. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
  117. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
  118. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
  119. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
  120. graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
  121. graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
  122. graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
  123. graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
  124. graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
  125. graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
  126. graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
  127. graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
  128. graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
  129. graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
  130. graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
  131. graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
  132. graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
  133. graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
  134. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
  135. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
  136. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
  137. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
  138. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
  139. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
  140. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
  141. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
  142. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
  143. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
  144. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
  145. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
  146. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
  147. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
  148. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
  149. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
  150. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
  151. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
  152. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
  153. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
  154. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
  155. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
  156. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
  157. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
  158. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
  159. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
  160. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
  161. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
  162. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
  163. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
  164. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
  165. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
  166. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
  167. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
  168. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
  169. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
  170. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
  171. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
  172. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
  173. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
  174. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
  175. graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
  176. graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
  177. graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
  178. graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
  179. graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
  180. graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
  181. graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
  182. graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
  183. graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
  184. graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
  185. graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
  186. graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
  187. graph_agents_cli/scaffold/commands/__init__.py +13 -0
  188. graph_agents_cli/scaffold/commands/create.py +1424 -0
  189. graph_agents_cli/scaffold/commands/enhance.py +1652 -0
  190. graph_agents_cli/scaffold/commands/upgrade.py +570 -0
  191. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
  192. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
  193. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
  194. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
  195. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
  196. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
  197. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
  198. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
  199. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
  200. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
  201. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
  202. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
  203. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
  204. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
  205. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
  206. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
  207. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
  208. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
  209. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
  210. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
  211. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
  212. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
  213. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
  214. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
  215. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
  216. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
  217. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
  218. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
  219. graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
  220. graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
  221. graph_agents_cli/scaffold/utils/__init__.py +13 -0
  222. graph_agents_cli/scaffold/utils/backup.py +212 -0
  223. graph_agents_cli/scaffold/utils/build_record.py +257 -0
  224. graph_agents_cli/scaffold/utils/cli_options.py +184 -0
  225. graph_agents_cli/scaffold/utils/fs.py +83 -0
  226. graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
  227. graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
  228. graph_agents_cli/scaffold/utils/keyedit.py +768 -0
  229. graph_agents_cli/scaffold/utils/keymerge.py +537 -0
  230. graph_agents_cli/scaffold/utils/language.py +138 -0
  231. graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
  232. graph_agents_cli/scaffold/utils/logging.py +77 -0
  233. graph_agents_cli/scaffold/utils/manifest.py +292 -0
  234. graph_agents_cli/scaffold/utils/merge.py +970 -0
  235. graph_agents_cli/scaffold/utils/merge3.py +216 -0
  236. graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
  237. graph_agents_cli/scaffold/utils/remote_template.py +376 -0
  238. graph_agents_cli/scaffold/utils/template.py +1352 -0
  239. graph_agents_cli/scaffold/utils/upgrade.py +894 -0
  240. graph_agents_cli/scaffold/utils/version.py +438 -0
  241. graph_agents_cli/secrets/__init__.py +15 -0
  242. graph_agents_cli/secrets/_apply.py +954 -0
  243. graph_agents_cli/secrets/_required.py +188 -0
  244. graph_agents_cli/secrets/cmd_secrets.py +211 -0
  245. graph_agents_cli/setup/__init__.py +13 -0
  246. graph_agents_cli/setup/_antigravity.py +221 -0
  247. graph_agents_cli/setup/cmd_auth.py +1030 -0
  248. graph_agents_cli/setup/cmd_dev_token.py +513 -0
  249. graph_agents_cli/setup/cmd_setup.py +428 -0
  250. graph_agents_cli/setup/cmd_update.py +140 -0
  251. graph_agents_cli/skills/__init__.py +13 -0
  252. graph_agents_cli/skills/_bundle.py +65 -0
  253. graph_agents_cli/skills/data/README.md +19 -0
  254. graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
  255. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
  256. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
  257. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
  258. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
  259. graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
  260. graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
  261. graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
  262. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
  263. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
  264. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
  265. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
  266. graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
  267. graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
  268. graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
  269. graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
  270. graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
  271. graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
  272. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
  273. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
  274. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
  275. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
  276. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
  277. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
  278. graph_agents_cli/system/__init__.py +15 -0
  279. graph_agents_cli/system/_apply.py +519 -0
  280. graph_agents_cli/system/_checks.py +1023 -0
  281. graph_agents_cli/system/_deploy.py +215 -0
  282. graph_agents_cli/system/_model.py +363 -0
  283. graph_agents_cli/system/_system.py +664 -0
  284. graph_agents_cli/system/_views.py +208 -0
  285. graph_agents_cli/system/cmd_system.py +423 -0
  286. graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
  287. graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
  288. graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
  289. graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
  290. graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
  291. graph_agents_cli-0.3.1.dist-info/licenses/NOTICE +19 -0
@@ -0,0 +1,208 @@
1
+ # Copyright 2026 graph-agents-cli contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """``system graph`` and ``system delegations``: the system drawn, and what the issuer grants.
16
+
17
+ The graph's edges carry the auth mode, whether the caller relays the person's
18
+ approvals or never sends them, and how the called agent's requester gates are
19
+ decided for that caller (``direct``: the person approves there; ``relayed``: the
20
+ caller may relay; ``mixed``; ``no gate``); its nodes carry the replicas and the
21
+ A2A task store per environment. The delegation matrix lists, for the issuer's
22
+ admin, each client and the audiences (and scopes) it must be allowed to exchange
23
+ users' tokens for, and what the issuer must guarantee (section 0.6 of the
24
+ design): the CLI cannot enforce those.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from typing import Any
30
+
31
+ from graph_agents_cli.system._system import Link, Node, System
32
+
33
+ NO_GATE = "no gate"
34
+ ISSUER_DEFAULT = "(issuer default)"
35
+ REQUIREMENTS = (
36
+ "each client may exchange only for the audiences listed here (RFC 8693 may_act, section "
37
+ "4.4, or the issuer's client policy), and no other audiences",
38
+ "an exchanged token carries act naming the calling agent (act.sub: its actor_id, by "
39
+ "default its client id), earlier agents nested inside "
40
+ "(without act, a callee reads the call as the person's own unless AUTH_JWT_DIRECT_CLIENTS "
41
+ "excludes the client)",
42
+ "no exchange of service tokens, and no exchange of a token issued to another client",
43
+ "expires_in of 300 s or less for exchanged tokens, and the narrowed scope where the issuer "
44
+ "supports it",
45
+ )
46
+
47
+
48
+ def decides(link: Link) -> str:
49
+ """How the callee's requester gates are decided for ``link``'s caller."""
50
+ gates = link.callee.requester_gates()
51
+ if not gates:
52
+ return NO_GATE
53
+ relayed = [gate.relays(link.actor) for gate in gates]
54
+ if all(relayed):
55
+ return "relayed"
56
+ return "mixed" if any(relayed) else "direct"
57
+
58
+
59
+ def _envs(system: System) -> list[str]:
60
+ return [name for name, env in system.envs.items() if env.charted]
61
+
62
+
63
+ def _replicas(node: Node, env: str) -> int | str:
64
+ count, autoscaled = node.replicas(env)
65
+ return f"hpa<={count}" if autoscaled else count
66
+
67
+
68
+ def graph_data(system: System) -> dict[str, Any]:
69
+ envs = _envs(system)
70
+ nodes = []
71
+ for node in system.nodes.values():
72
+ entry: dict[str, Any] = {
73
+ "name": node.name,
74
+ "project": node.config.project_name,
75
+ "client_id": node.client_id,
76
+ "actor_id": node.actor_id,
77
+ "auth_policy": node.auth_policy(),
78
+ "runtime": node.config.runtime,
79
+ }
80
+ if node.has_chart and envs:
81
+ entry["replicas"] = {env: _replicas(node, env) for env in envs}
82
+ entry["task_store"] = {env: node.task_store(env) for env in envs}
83
+ else:
84
+ entry["task_store"] = {"(local)": node.task_store()}
85
+ nodes.append(entry)
86
+ edges = []
87
+ for link in system.links:
88
+ edges.append(
89
+ {
90
+ "from": link.caller.name,
91
+ "to": link.callee.name,
92
+ "auth": link.auth,
93
+ "approvals": link.edge.approvals,
94
+ "callee_decides": decides(link) if link.edge.approvals == "relay" else None,
95
+ "declared": link.existing is not None,
96
+ }
97
+ )
98
+ return {"name": system.file.name, "nodes": nodes, "edges": edges}
99
+
100
+
101
+ def _node_lines(node: dict[str, Any]) -> list[str]:
102
+ lines = [str(node["name"])]
103
+ replicas = node.get("replicas") or {}
104
+ if replicas:
105
+ lines.append("replicas " + ", ".join(f"{e} {n}" for e, n in replicas.items()))
106
+ stores = sorted(set((node.get("task_store") or {}).values()))
107
+ if stores:
108
+ lines.append("tasks " + "/".join(stores))
109
+ return lines
110
+
111
+
112
+ def _edge_label(edge: dict[str, Any]) -> str:
113
+ """``exchange, relay (orders: direct)``: the auth mode, relay or deny, and for a relay how
114
+ the called agent's requester gates are decided for this caller."""
115
+ label = f"{edge['auth']}, {edge['approvals']}"
116
+ if edge.get("callee_decides"):
117
+ label += f" ({edge['to']}: {edge['callee_decides']})"
118
+ if not edge["declared"]:
119
+ label += " [not applied]"
120
+ return label
121
+
122
+
123
+ def mermaid(data: dict[str, Any]) -> str:
124
+ lines = ["flowchart LR"]
125
+ for node in data["nodes"]:
126
+ label = "<br/>".join(_node_lines(node)).replace('"', "'")
127
+ lines.append(f' a_{node["name"]}["{label}"]')
128
+ for edge in data["edges"]:
129
+ label = _edge_label(edge).replace('"', "'")
130
+ lines.append(f' a_{edge["from"]} -->|"{label}"| a_{edge["to"]}')
131
+ return "\n".join(lines) + "\n"
132
+
133
+
134
+ def _dot_quote(text: str) -> str:
135
+ return '"' + text.replace("\\", "\\\\").replace('"', '\\"') + '"'
136
+
137
+
138
+ def dot(data: dict[str, Any]) -> str:
139
+ lines = [f"digraph {_dot_quote(data['name'])} {{", " rankdir=LR;", " node [shape=box];"]
140
+ for node in data["nodes"]:
141
+ label = "\\n".join(
142
+ line.replace("\\", "\\\\").replace('"', '\\"') for line in _node_lines(node)
143
+ )
144
+ lines.append(f' {_dot_quote(node["name"])} [label="{label}"];')
145
+ for edge in data["edges"]:
146
+ style = "" if edge["declared"] else ", style=dashed"
147
+ lines.append(
148
+ f" {_dot_quote(edge['from'])} -> {_dot_quote(edge['to'])} "
149
+ f"[label={_dot_quote(_edge_label(edge))}{style}];"
150
+ )
151
+ lines.append("}")
152
+ return "\n".join(lines) + "\n"
153
+
154
+
155
+ def delegations(system: System) -> dict[str, Any]:
156
+ """The issuer's delegation matrix: one row per (client, audience, scope)."""
157
+ rows: dict[tuple[str, str, str], dict[str, Any]] = {}
158
+ for link in system.links:
159
+ if link.auth != "exchange":
160
+ continue
161
+ scope = link.edge.scope or ISSUER_DEFAULT
162
+ key = (link.caller.client_id, link.audience, scope)
163
+ because = link.describe() + (" (relay)" if link.edge.approvals == "relay" else "")
164
+ row = rows.setdefault(
165
+ key,
166
+ {
167
+ "client": link.caller.client_id,
168
+ # The act.sub its exchanged tokens must carry (None: they name no actor).
169
+ "actor": None if link.edge.allow_actorless else link.actor,
170
+ "audience": link.audience,
171
+ "scope": scope,
172
+ "because": [],
173
+ "actorless": False,
174
+ },
175
+ )
176
+ row["because"].append(because)
177
+ row["actorless"] = row["actorless"] or link.edge.allow_actorless
178
+ identity = system.file.identity
179
+ return {
180
+ "issuer": identity.issuer if identity else None,
181
+ "rows": list(rows.values()),
182
+ "requirements": list(REQUIREMENTS),
183
+ }
184
+
185
+
186
+ def delegations_table(data: dict[str, Any]) -> str:
187
+ headers = ("client", "may exchange for audience", "scope", "because")
188
+ rows = [
189
+ (
190
+ row["client"],
191
+ row["audience"],
192
+ row["scope"],
193
+ "; ".join(row["because"])
194
+ + (" [names no actor]" if row["actorless"] else "")
195
+ + (
196
+ f" [act.sub {row['actor']}]"
197
+ if row["actor"] and row["actor"] != row["client"]
198
+ else ""
199
+ ),
200
+ )
201
+ for row in data["rows"]
202
+ ]
203
+ widths = [max(len(str(r[i])) for r in [headers, *rows]) for i in range(3)]
204
+ lines = []
205
+ for row in [headers, *rows]:
206
+ cells = [str(row[i]).ljust(widths[i]) for i in range(3)]
207
+ lines.append(" ".join([*cells, str(row[3])]).rstrip())
208
+ return "\n".join(lines) + "\n"
@@ -0,0 +1,423 @@
1
+ # Copyright 2026 graph-agents-cli contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """graph-agents-cli system commands: agent projects that call each other, seen as one.
16
+
17
+ ``graph-agents-system.yaml`` names the agents, their projects and who calls whom
18
+ (``_model``). ``check`` reports what would keep them from calling each other,
19
+ ``apply`` writes each project's side of every edge, ``graph`` draws the system,
20
+ ``delegations`` prints what the token issuer must allow, and ``deploy`` deploys
21
+ every project, callees first, a few at a time. The file is optional: each
22
+ project stays complete on its own (``peer add``).
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ from typing import Any
29
+
30
+ import click
31
+ from rich.markup import escape
32
+
33
+ from graph_agents_cli._click import LazyGroup
34
+ from graph_agents_cli._output import Console
35
+ from graph_agents_cli.api._files import print_diff
36
+ from graph_agents_cli.system import _apply, _checks, _deploy, _views
37
+ from graph_agents_cli.system._model import EXIT_INVALID, MAX_PARALLEL, SystemFileError
38
+ from graph_agents_cli.system._system import LOCAL, System, locate, resolve
39
+
40
+ EXIT_FINDING = 1
41
+
42
+
43
+ @click.group("system", cls=LazyGroup)
44
+ def system_group() -> None:
45
+ """Check, wire and deploy agents that call each other, as one system.
46
+
47
+ graph-agents-system.yaml (in this directory or above, or --file) names the
48
+ agent projects that call each other over A2A: each agent's project, the
49
+ client id it exchanges tokens as, the agents it calls, and the environments
50
+ they run in. Every project stays complete on its own (`peer add`); the file
51
+ adds checks across projects, writes both sides of each edge at once, and
52
+ deploys them in order.
53
+
54
+ \b
55
+ Exit codes:
56
+ 0 ok
57
+ 1 check: a finding with an error; deploy: a deploy or the live check failed
58
+ 2 usage error
59
+ 3 the file is unreadable or invalid, names an unknown project or an
60
+ ambiguous edge, or a project cannot take its edges
61
+ """
62
+
63
+
64
+ def _file_option(function: Any) -> Any:
65
+ return click.option(
66
+ "--file",
67
+ "file",
68
+ default=None,
69
+ metavar="F",
70
+ help="The system file (default: graph-agents-system.yaml here or in a parent directory).",
71
+ )(function)
72
+
73
+
74
+ def _load(file: str | None) -> System:
75
+ return resolve(locate(file))
76
+
77
+
78
+ def _print_findings(console: Console, findings: list[_checks.Finding]) -> None:
79
+ for finding in findings:
80
+ style = "red" if finding.severity == _checks.ERROR else "yellow"
81
+ where = f" [{finding.env}]" if finding.env else ""
82
+ console.print(
83
+ escape(f"{finding.severity} {finding.check}{where} {finding.message}"),
84
+ style=style,
85
+ highlight=False,
86
+ )
87
+ if finding.fix:
88
+ console.print(f" fix: {escape(finding.fix)}", highlight=False)
89
+
90
+
91
+ @system_group.command("check")
92
+ @_file_option
93
+ @click.option("--env", "env", default=None, help="Check one environment (default: every one).")
94
+ @click.option(
95
+ "--live",
96
+ "live",
97
+ is_flag=True,
98
+ default=False,
99
+ help="Also ask the cluster and the network: Services, card URLs, the token URL, Secrets.",
100
+ )
101
+ @click.option("--json", "as_json", is_flag=True, default=False, help="Print JSON.")
102
+ def cmd_check(file: str | None, env: str | None, live: bool, as_json: bool) -> None:
103
+ """Report what would keep the agents from calling each other (SC01-SC15); exit 1 on an error.
104
+
105
+ \b
106
+ SC01 projects, 0.3 runtimes, charts SC09 cycles, delegation depth
107
+ SC02 each edge is a peer, as the file SC10 shared database connections
108
+ says; tools/a2a_peers.py in step SC11 the callers' secrets.keys, the salt
109
+ SC03 the path is the callee's A2A mount SC12 exchange/forward on langgraph-server
110
+ SC04 issuer, audience, auth policies SC13 a callee still published publicly
111
+ SC05 appUrl = the URL the caller dials SC14 (--live) Services, cards, issuer
112
+ SC06 replicas with in-memory A2A tasks SC15 (--live) the Secrets' keys (names)
113
+ SC07 relays the callee's gates refuse
114
+ SC08 AUTH_ALLOWED_ACTORS lacks a caller
115
+
116
+ --live uses each project's recorded kube context (environments.<env>.context), and
117
+ outside dev never the kubeconfig's current one. A local environment's settings are
118
+ in .env, which is never read.
119
+ """
120
+ system = _load(file)
121
+ envs = system.select([env] if env else None)
122
+ findings = _checks.run(system, envs, live=live)
123
+ errors = [f for f in findings if f.severity == _checks.ERROR]
124
+ if as_json:
125
+ click.echo(
126
+ json.dumps(
127
+ {
128
+ "file": str(system.path),
129
+ "system": system.file.name,
130
+ "environments": envs,
131
+ "live": live,
132
+ "ok": not errors,
133
+ "findings": [f.as_dict() for f in findings],
134
+ },
135
+ indent=2,
136
+ )
137
+ )
138
+ else:
139
+ console = Console()
140
+ _print_findings(console, findings)
141
+ warnings = len(findings) - len(errors)
142
+ local = [e for e in envs if system.envs[e].kind == LOCAL]
143
+ summary = (
144
+ f"{len(system.nodes)} agents, {len(system.links)} edges, environments "
145
+ f"{', '.join(envs) or '(none)'}: {len(errors)} error(s), {warnings} warning(s)"
146
+ )
147
+ console.print(summary, style="red" if errors else "green", highlight=False)
148
+ if local:
149
+ console.print(
150
+ f"Not checked for {', '.join(local)}: the settings in each project's .env.",
151
+ style="dim",
152
+ )
153
+ if errors:
154
+ raise SystemExit(EXIT_FINDING)
155
+
156
+
157
+ @system_group.command("apply")
158
+ @_file_option
159
+ @click.option(
160
+ "--env",
161
+ "envs",
162
+ multiple=True,
163
+ help="Write the settings of these environments only (repeatable; default: every one).",
164
+ )
165
+ @click.option("--dry-run", "dry_run", is_flag=True, default=False, help="Print the diffs only.")
166
+ def cmd_apply(file: str | None, envs: tuple[str, ...], dry_run: bool) -> None:
167
+ """Write each project's side of every edge; idempotent, one diff per project.
168
+
169
+ \b
170
+ In each caller, per edge: what `peer add` writes (the policy entry, the
171
+ manifest's secrets.keys, .env.example, the chart values, tools/a2a_peers.py),
172
+ with the called agent's A2A path and its name as the audience; per
173
+ environment, <PEER>_AGENT_URL, TOKEN_EXCHANGE_URL, and networkPolicy.egressTo
174
+ to the called agent's pods; TOKEN_EXCHANGE_CLIENT_ID is the client_id.
175
+ In each called agent: appUrl per environment (the URL its callers dial),
176
+ AUTH_JWT_AUDIENCE when empty, the callers' client ids in AUTH_ALLOWED_ACTORS,
177
+ and networkPolicy.ingressFrom for the callers' pods (plus the Gateway's
178
+ namespace while the route publishes paths).
179
+
180
+ Never written: approval gates (the `api approval ... --decide-with relayed`
181
+ line a relay needs is printed), secrets, .env, and local environments
182
+ (printed). A peer of an agent of the file is removed when it has left `calls`;
183
+ other peers and APIs are never touched. An existing peer keeps its limits,
184
+ timeouts, approval timeout and description.
185
+ """
186
+ system = _load(file)
187
+ selected = system.select(list(envs) or None)
188
+ results = _apply.plan_system(system, selected)
189
+ console = Console()
190
+ changed = False
191
+ for result in results:
192
+ node = result.node
193
+ plan = result.plan
194
+ header = f"== {node.name} ({escape(str(node.root))}) =="
195
+ console.print(header, style="bold", highlight=False)
196
+ if not plan.effective:
197
+ console.print("Nothing to change.")
198
+ else:
199
+ changed = True
200
+ print_diff(plan.diff())
201
+ if not dry_run:
202
+ plan.write()
203
+ written = ", ".join(change.path for change in plan.effective)
204
+ console.print(f"Wrote {escape(written)}.", style="green")
205
+ for note in result.notes:
206
+ console.print(f"Note: {escape(note)}", style="yellow", highlight=False)
207
+ if result.todos:
208
+ console.print("Left for you:", style="bold")
209
+ for item in result.todos:
210
+ console.print(f" - {escape(item)}", highlight=False)
211
+ commands = _apply.relay_commands(system, results)
212
+ if commands:
213
+ console.print(
214
+ "Relays the called agents refuse (never written here: a reviewed loosening there, "
215
+ "SC07):",
216
+ style="bold",
217
+ )
218
+ for callee, why, command in commands:
219
+ where = _checks._where(system, callee)
220
+ console.print(f" - {escape(why)}:", highlight=False)
221
+ console.print(f" cd {escape(where)} && {escape(command)}", highlight=False)
222
+ if any(link.auth == "exchange" for link in system.links):
223
+ console.print(
224
+ "At the issuer: register each client and what it may exchange for "
225
+ "(`graph-agents-cli system delegations`).",
226
+ highlight=False,
227
+ )
228
+ if dry_run and changed:
229
+ console.print("Dry run: nothing was written.", style="yellow")
230
+ elif not changed:
231
+ console.print("Every project agrees with the file.", style="green")
232
+
233
+
234
+ @system_group.command("graph")
235
+ @_file_option
236
+ @click.option(
237
+ "--format",
238
+ "fmt",
239
+ type=click.Choice(["mermaid", "dot", "json"]),
240
+ default="mermaid",
241
+ show_default=True,
242
+ )
243
+ def cmd_graph(file: str | None, fmt: str) -> None:
244
+ """Draw the system: edges with their auth, relay or deny, and how the callee decides;
245
+ agents with their replicas and A2A task store per environment."""
246
+ data = _views.graph_data(_load(file))
247
+ if fmt == "json":
248
+ click.echo(json.dumps(data, indent=2))
249
+ elif fmt == "dot":
250
+ click.echo(_views.dot(data), nl=False)
251
+ else:
252
+ click.echo(_views.mermaid(data), nl=False)
253
+
254
+
255
+ @system_group.command("delegations")
256
+ @_file_option
257
+ @click.option(
258
+ "--format",
259
+ "fmt",
260
+ type=click.Choice(["table", "json"]),
261
+ default="table",
262
+ show_default=True,
263
+ )
264
+ def cmd_delegations(file: str | None, fmt: str) -> None:
265
+ """Print what the token issuer must let each client exchange for (auth: exchange edges),
266
+ and what it must guarantee."""
267
+ data = _views.delegations(_load(file))
268
+ if fmt == "json":
269
+ click.echo(json.dumps(data, indent=2))
270
+ return
271
+ if not data["rows"]:
272
+ click.echo("No edge uses auth: exchange: the issuer grants nothing for this system.")
273
+ return
274
+ if data["issuer"]:
275
+ click.echo(f"Issuer: {data['issuer']}\n")
276
+ click.echo(_views.delegations_table(data), nl=False)
277
+ click.echo("\nThe issuer must also guarantee (graph-agents-cli cannot enforce these):")
278
+ for requirement in data["requirements"]:
279
+ click.echo(f" - {requirement}")
280
+
281
+
282
+ def _parse_only(system: System, only: str | None) -> list[str]:
283
+ if not only:
284
+ return list(system.nodes)
285
+ names = [n.strip() for n in only.split(",") if n.strip()]
286
+ unknown = [n for n in names if n not in system.nodes]
287
+ if unknown:
288
+ raise click.UsageError(
289
+ f"--only: {', '.join(unknown)} is not an agent of {system.path.name} (agents: "
290
+ f"{', '.join(system.nodes)})"
291
+ )
292
+ return [n for n in system.nodes if n in names]
293
+
294
+
295
+ @system_group.command("deploy")
296
+ @click.option("--env", "env", required=True, help="The environment to deploy.")
297
+ @_file_option
298
+ @click.option(
299
+ "--parallel",
300
+ "parallel",
301
+ type=click.IntRange(1, MAX_PARALLEL),
302
+ default=None,
303
+ help="Deploys at once (default: deploy.parallel in the file, else 3).",
304
+ )
305
+ @click.option("--only", "only", default=None, metavar="A,B", help="Deploy these agents only.")
306
+ @click.option(
307
+ "--keep-going", "keep_going", is_flag=True, default=False, help="Go on after a failed wave."
308
+ )
309
+ @click.option(
310
+ "--skip-check",
311
+ "skip_check",
312
+ is_flag=True,
313
+ default=False,
314
+ help="Skip `system check` before (static) and after (--live) the deploys.",
315
+ )
316
+ @click.option(
317
+ "--dry-run",
318
+ "dry_run",
319
+ is_flag=True,
320
+ default=False,
321
+ help="Pass --dry-run to each deploy (helm template; nothing changes); no live check.",
322
+ )
323
+ def cmd_deploy(
324
+ env: str,
325
+ file: str | None,
326
+ parallel: int | None,
327
+ only: str | None,
328
+ keep_going: bool,
329
+ skip_check: bool,
330
+ dry_run: bool,
331
+ ) -> None:
332
+ """Deploy every project (`graph-agents-cli deploy --env ENV`), callees first, a few at a time.
333
+
334
+ \b
335
+ 1. `system check --env ENV` (the files only): an error stops here.
336
+ 2. The agents deploy in waves, each once the agents it calls are deployed
337
+ (a cycle is broken in file order, with a warning), at most --parallel at
338
+ once; a failed wave stops the run unless --keep-going.
339
+ 3. Each agent's build, image load and rollout times are printed.
340
+ 4. `system check --live --env ENV`.
341
+
342
+ Projects in argocd mode deploy one at a time (each writes a commit and a pull
343
+ request, and projects may share a repository).
344
+
345
+ Outside dev every project must record its kube context
346
+ (environments.<env>.context in its manifest): the kubeconfig's current
347
+ context is never used there, and no deploy is asked to confirm one.
348
+ """
349
+ system = _load(file)
350
+ system.select([env])
351
+ spec = system.envs[env]
352
+ if spec.kind == LOCAL:
353
+ raise click.UsageError(f"{env} is a local environment (port_base): nothing to deploy")
354
+ names = _parse_only(system, only)
355
+ console = Console()
356
+ unrecorded = [
357
+ system.nodes[n].name for n in names if env != "dev" and system.nodes[n].context(env) is None
358
+ ]
359
+ if unrecorded:
360
+ raise SystemFileError(
361
+ f"{env}: no kube context recorded for {', '.join(unrecorded)} (environments.{env}."
362
+ "context in each manifest). Outside dev the kubeconfig's current context is never "
363
+ "used; nothing was deployed."
364
+ )
365
+ if not skip_check:
366
+ findings = _checks.run(system, [env])
367
+ errors = [f for f in findings if f.severity == _checks.ERROR]
368
+ if findings:
369
+ _print_findings(console, findings)
370
+ if errors:
371
+ console.print(
372
+ f"system check found {len(errors)} error(s) for {env}: nothing was deployed "
373
+ "(--skip-check deploys anyway).",
374
+ style="red",
375
+ )
376
+ raise SystemExit(EXIT_FINDING)
377
+ width = parallel or system.file.deploy.parallel
378
+ gitops = [n for n in names if system.nodes[n].config.cd == "argocd"]
379
+ if gitops and width > 1:
380
+ # argocd mode commits image.tag and opens a pull request: projects that share a
381
+ # repository would race on its branches and index.
382
+ console.print(
383
+ f" {', '.join(gitops)} deploy in argocd mode (a commit and a pull request each): "
384
+ "one at a time.",
385
+ style="yellow",
386
+ highlight=False,
387
+ )
388
+ width = 1
389
+ console.print(
390
+ f"Deploying {len(names)} agent(s) to {env}, at most {width} at once"
391
+ + (" (dry run)" if dry_run else ""),
392
+ highlight=False,
393
+ )
394
+ outcomes = _deploy.deploy(
395
+ system,
396
+ env,
397
+ names,
398
+ parallel=width,
399
+ keep_going=keep_going,
400
+ extra=["--dry-run"] if dry_run else [],
401
+ report=lambda line: console.print(escape(line), highlight=False),
402
+ )
403
+ failed = [o for o in outcomes if not o.ok]
404
+ missing = [n for n in names if n not in {o.agent for o in outcomes}]
405
+ if failed or missing:
406
+ console.print(
407
+ f"{len(failed)} deploy(s) failed"
408
+ + (f"; not deployed: {', '.join(missing)}" if missing else ""),
409
+ style="red",
410
+ )
411
+ raise SystemExit(EXIT_FINDING)
412
+ console.print(f"Deployed {len(outcomes)} agent(s) to {env}.", style="green")
413
+ if skip_check or dry_run:
414
+ return
415
+ findings = _checks.run(system, [env], live=True)
416
+ live = [f for f in findings if f.check in ("SC14", "SC15")]
417
+ _print_findings(console, live)
418
+ if any(f.severity == _checks.ERROR for f in live):
419
+ raise SystemExit(EXIT_FINDING)
420
+ console.print(f"system check --live --env {env}: the agents answer.", style="green")
421
+
422
+
423
+ __all__ = ["EXIT_INVALID", "system_group"]