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,215 @@
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 deploy``: ``graph-agents-cli deploy --env ENV`` in every project, callees first.
16
+
17
+ The projects deploy in waves: a wave holds the agents whose callees are all
18
+ deployed, so a caller's first card check finds its peers up (a cycle is broken
19
+ at its first agent in file order, with a warning). At most ``--parallel``
20
+ deploys run at once (default ``deploy.parallel`` of the file, else 3): round 1
21
+ of the experiments deployed seven agents at once and the hub took 158 s where
22
+ 27-38 s was usual. A failed wave stops the run unless ``--keep-going``. Each
23
+ deploy is a child process in its project's directory with no terminal input,
24
+ its output kept in a log file; the per-agent build, image load and rollout
25
+ times are read from the commands it prints.
26
+
27
+ Outside ``dev`` every project must record its kube context
28
+ (``environments.<env>.context``): a deploy would otherwise ask to confirm the
29
+ kubeconfig's current context, and ``system deploy`` never answers that for it.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import os
35
+ import subprocess
36
+ import sys
37
+ import tempfile
38
+ import threading
39
+ import time
40
+ from collections.abc import Callable
41
+ from concurrent.futures import ThreadPoolExecutor
42
+ from dataclasses import dataclass, field
43
+ from pathlib import Path
44
+
45
+ from graph_agents_cli.system._system import Node, System
46
+
47
+ BUILD = "build"
48
+ LOAD = "load"
49
+ ROLL = "roll"
50
+ # The commands `deploy` echoes (`▸ cmd` or `[dry-run] cmd`) that start each phase.
51
+ PHASE_MARKERS: tuple[tuple[str, tuple[str, ...]], ...] = (
52
+ (BUILD, ("docker build",)),
53
+ (
54
+ LOAD,
55
+ ("kind load", "k3d image import", "minikube image load", "k3s ctr", "docker push"),
56
+ ),
57
+ (ROLL, ("helm upgrade", "helm template")),
58
+ )
59
+ TAIL_LINES = 15
60
+
61
+
62
+ @dataclass
63
+ class Outcome:
64
+ """One project's deploy."""
65
+
66
+ agent: str
67
+ exit_code: int
68
+ seconds: float
69
+ phases: dict[str, float] = field(default_factory=dict)
70
+ log: Path | None = None
71
+ tail: list[str] = field(default_factory=list)
72
+
73
+ @property
74
+ def ok(self) -> bool:
75
+ return self.exit_code == 0
76
+
77
+ def describe(self) -> str:
78
+ phases = ", ".join(f"{name} {secs:.1f} s" for name, secs in self.phases.items())
79
+ timing = f"{self.seconds:.1f} s" + (f" ({phases})" if phases else "")
80
+ if self.ok:
81
+ return f"{self.agent}: deployed in {timing}"
82
+ return f"{self.agent}: FAILED (exit {self.exit_code}) after {timing}"
83
+
84
+
85
+ # (node, argv after `graph-agents-cli`, log path) -> (exit code, [(seconds since start, line)])
86
+ Runner = Callable[[Node, list[str], Path], tuple[int, list[tuple[float, str]]]]
87
+
88
+
89
+ def run_cli(node: Node, args: list[str], log: Path) -> tuple[int, list[tuple[float, str]]]:
90
+ """Run ``graph-agents-cli <args>`` in ``node``'s project; every line timed and logged."""
91
+ command = [sys.executable, "-m", "graph_agents_cli.main", *args]
92
+ env = {**os.environ, "PYTHONUNBUFFERED": "1"}
93
+ start = time.monotonic()
94
+ lines: list[tuple[float, str]] = []
95
+ with log.open("w", encoding="utf-8") as sink:
96
+ process = subprocess.Popen(
97
+ command,
98
+ cwd=node.root,
99
+ env=env,
100
+ stdin=subprocess.DEVNULL,
101
+ stdout=subprocess.PIPE,
102
+ stderr=subprocess.STDOUT,
103
+ text=True,
104
+ encoding="utf-8",
105
+ errors="replace",
106
+ )
107
+ assert process.stdout is not None
108
+ for line in process.stdout:
109
+ lines.append((time.monotonic() - start, line.rstrip("\n")))
110
+ sink.write(line)
111
+ sink.flush()
112
+ code = process.wait()
113
+ return code, lines
114
+
115
+
116
+ def phases(lines: list[tuple[float, str]], total: float) -> dict[str, float]:
117
+ """How long each phase took, from the first line that starts it to the next phase."""
118
+ starts: dict[str, float] = {}
119
+ for seconds, line in lines:
120
+ for phase, markers in PHASE_MARKERS:
121
+ if phase not in starts and any(marker in line for marker in markers):
122
+ starts[phase] = seconds
123
+ ordered = sorted(starts.items(), key=lambda item: item[1])
124
+ result = {}
125
+ for index, (phase, begin) in enumerate(ordered):
126
+ end = ordered[index + 1][1] if index + 1 < len(ordered) else total
127
+ result[phase] = max(0.0, end - begin)
128
+ return {phase: result[phase] for phase, _ in PHASE_MARKERS if phase in result}
129
+
130
+
131
+ def waves(system: System, names: list[str]) -> tuple[list[list[str]], list[str]]:
132
+ """The deploy waves of ``names`` (file order within a wave), and the cycle warnings."""
133
+ selected = [n for n in system.nodes if n in names]
134
+ callees = {
135
+ n: {lk.callee.name for lk in system.calls(system.nodes[n])} & set(selected)
136
+ for n in selected
137
+ }
138
+ done: set[str] = set()
139
+ remaining = list(selected)
140
+ result: list[list[str]] = []
141
+ warnings: list[str] = []
142
+ while remaining:
143
+ wave = [n for n in remaining if callees[n] <= done]
144
+ if not wave:
145
+ first = remaining[0]
146
+ waiting = sorted(callees[first] - done)
147
+ warnings.append(
148
+ f"a cycle: {first} is deployed before {', '.join(waiting)}, which it calls "
149
+ "(its first card checks may fail until they are up)"
150
+ )
151
+ wave = [first]
152
+ result.append(wave)
153
+ done.update(wave)
154
+ remaining = [n for n in remaining if n not in done]
155
+ return result, warnings
156
+
157
+
158
+ def deploy(
159
+ system: System,
160
+ env: str,
161
+ names: list[str],
162
+ *,
163
+ parallel: int,
164
+ keep_going: bool,
165
+ extra: list[str],
166
+ runner: Runner | None = None,
167
+ report: Callable[[str], None] = print,
168
+ log_dir: Path | None = None,
169
+ ) -> list[Outcome]:
170
+ """Deploy ``names`` wave by wave; the outcome of each deploy that ran (``runner``: how a
171
+ deploy runs, ``run_cli`` by default)."""
172
+ run = runner or run_cli
173
+ plan, warnings = waves(system, names)
174
+ for warning in warnings:
175
+ report(f"Warning: {warning}")
176
+ logs = log_dir or Path(tempfile.mkdtemp(prefix=f"gac-system-deploy-{env}-"))
177
+ report(f"Logs: {logs}")
178
+ outcomes: list[Outcome] = []
179
+ lock = threading.Lock()
180
+
181
+ def one(name: str) -> Outcome:
182
+ node = system.nodes[name]
183
+ log = logs / f"{name}.log"
184
+ start = time.monotonic()
185
+ try:
186
+ code, lines = run(node, ["deploy", "--env", env, *extra], log)
187
+ except OSError as exc:
188
+ code, lines = 2, [(0.0, f"could not run graph-agents-cli deploy: {exc}")]
189
+ total = time.monotonic() - start
190
+ outcome = Outcome(
191
+ agent=name,
192
+ exit_code=code,
193
+ seconds=total,
194
+ phases=phases(lines, total),
195
+ log=log,
196
+ tail=[line for _, line in lines[-TAIL_LINES:]],
197
+ )
198
+ with lock:
199
+ report(outcome.describe())
200
+ if not outcome.ok:
201
+ for line in outcome.tail:
202
+ report(f" {line}")
203
+ report(f" log: {log}")
204
+ return outcome
205
+
206
+ for number, wave in enumerate(plan, start=1):
207
+ report(f"Wave {number}/{len(plan)}: {', '.join(wave)}")
208
+ with ThreadPoolExecutor(max_workers=max(1, parallel)) as pool:
209
+ results = list(pool.map(one, wave))
210
+ outcomes.extend(results)
211
+ if any(not r.ok for r in results) and not keep_going and number < len(plan):
212
+ skipped = [n for later in plan[number:] for n in later]
213
+ report(f"Stopped after wave {number}; not deployed: {', '.join(skipped)}")
214
+ break
215
+ return outcomes
@@ -0,0 +1,363 @@
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-system.yaml``: the agents of a system, their projects and who calls whom.
16
+
17
+ The file is optional: every project stays self-contained (``peer add`` alone is
18
+ complete). It names each agent's project directory (relative to the file), the
19
+ client id other agents see it as (its actor id), and the agents it calls; the
20
+ environments the agents run in; the issuer that exchanges users' tokens; a
21
+ shared database's connection budget; and how many projects ``system deploy``
22
+ deploys at once. The models below are the schema: ``load`` validates a file with
23
+ them, and ``uv run python -m graph_agents_cli.system._model`` rewrites the
24
+ published JSON Schema (``schemas/graph-agents-system.schema.json``, which a test
25
+ keeps equal to the models).
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import json
31
+ from pathlib import Path
32
+ from typing import Annotated, Any, Literal
33
+
34
+ import click
35
+ import yaml
36
+ from pydantic import (
37
+ BaseModel,
38
+ ConfigDict,
39
+ Field,
40
+ StringConstraints,
41
+ ValidationError,
42
+ model_validator,
43
+ )
44
+
45
+ SYSTEM_FILENAME = "graph-agents-system.yaml"
46
+ EXIT_INVALID = 3
47
+
48
+ # An agent's name is the peer name its callers give it (`peer add NAME`), so it follows
49
+ # that rule: the model picks peers by it, and the API is `<name>_agent`.
50
+ AGENT_NAME_PATTERN = r"^[a-z][a-z0-9_]{0,25}$"
51
+ # A client id at the issuer: the actor id callees list in AUTH_ALLOWED_ACTORS (a comma
52
+ # list), so no comma, whitespace or control character.
53
+ CLIENT_ID_PATTERN = r"^[^\s,\x00-\x1f\x7f]{1,256}$"
54
+ # RFC 6749 scope tokens separated by single spaces.
55
+ SCOPE_PATTERN = r"^[\x21\x23-\x5b\x5d-\x7e]+(?: [\x21\x23-\x5b\x5d-\x7e]+)*$"
56
+ ENV_NAME_PATTERN = r"^[a-z][a-z0-9-]{0,30}$"
57
+ # The placeholders an environment's url template may use.
58
+ URL_PLACEHOLDERS = ("{agent}", "{project}", "{env}")
59
+
60
+ AgentName = Annotated[str, StringConstraints(pattern=AGENT_NAME_PATTERN)]
61
+ EnvName = Annotated[str, StringConstraints(pattern=ENV_NAME_PATTERN)]
62
+
63
+ CALL_KINDS = ("ask", "status", "cancel")
64
+ RELAY = "relay"
65
+ DENY = "deny"
66
+ DEFAULT_PARALLEL = 3
67
+ MAX_PARALLEL = 16
68
+
69
+
70
+ class _Strict(BaseModel):
71
+ model_config = ConfigDict(extra="forbid")
72
+
73
+
74
+ class Edge(_Strict):
75
+ """One agent this agent calls, with what `peer add` would be told about it."""
76
+
77
+ agent: AgentName = Field(description="The agent called (a key of `agents`).")
78
+ approvals: Literal["relay", "deny"] = Field(
79
+ default=RELAY,
80
+ description=(
81
+ "relay: the person's decisions on the called agent's pending approvals go through "
82
+ "a gate here (requester); deny: this agent never sends them."
83
+ ),
84
+ )
85
+ auth: Literal["exchange", "forward", "bearer"] | None = Field(
86
+ default=None,
87
+ description=(
88
+ "How the caller authenticates; default by its auth policy (jwt: exchange, custom: "
89
+ "forward, shared-bearer: bearer)."
90
+ ),
91
+ )
92
+ scope: Annotated[str, StringConstraints(pattern=SCOPE_PATTERN)] | None = Field(
93
+ default=None, description="exchange: the scopes asked for (least privilege)."
94
+ )
95
+ allow_actorless: bool = Field(
96
+ default=False,
97
+ description=(
98
+ "exchange: accept exchanged tokens that name no actor (the called agent then lists "
99
+ "client:<client_id> in AUTH_ALLOWED_ACTORS)."
100
+ ),
101
+ )
102
+ calls: list[Literal["ask", "status", "cancel"]] | None = Field(
103
+ default=None,
104
+ description="What the caller may send: ask (SendMessage), status (GetTask), cancel.",
105
+ )
106
+ description: Annotated[str, StringConstraints(max_length=300)] | None = Field(
107
+ default=None,
108
+ description=(
109
+ "What the called agent does, for the caller's model (default: the called agent's "
110
+ "A2A_DESCRIPTION)."
111
+ ),
112
+ )
113
+
114
+ @model_validator(mode="after")
115
+ def _consistent(self) -> Edge:
116
+ if self.calls is not None and "ask" not in self.calls:
117
+ raise ValueError("calls must include ask")
118
+ if self.auth not in (None, "exchange") and (self.scope or self.allow_actorless):
119
+ raise ValueError("scope and allow_actorless go with auth: exchange")
120
+ return self
121
+
122
+
123
+ class Agent(_Strict):
124
+ """One agent of the system: a graph-agents-cli project."""
125
+
126
+ project: Annotated[str, StringConstraints(min_length=1)] = Field(
127
+ description="The project directory, relative to this file."
128
+ )
129
+ client_id: Annotated[str, StringConstraints(pattern=CLIENT_ID_PATTERN)] | None = Field(
130
+ default=None,
131
+ description=(
132
+ "The client id this agent exchanges tokens with at the issuer "
133
+ "(TOKEN_EXCHANGE_CLIENT_ID; default: the agent's name)."
134
+ ),
135
+ )
136
+ actor_id: Annotated[str, StringConstraints(pattern=CLIENT_ID_PATTERN)] | None = Field(
137
+ default=None,
138
+ description=(
139
+ "The actor id the agents it calls see: the act.sub the issuer puts in this "
140
+ "agent's exchanged tokens, which their AUTH_ALLOWED_ACTORS and --relayers name "
141
+ "(default: client_id). Set it when the issuer names the client differently there."
142
+ ),
143
+ )
144
+ calls: list[AgentName | Edge] = Field(
145
+ default_factory=list, description="The agents this agent calls (names, or edges)."
146
+ )
147
+
148
+
149
+ class Identity(_Strict):
150
+ """The issuer that exchanges users' tokens (RFC 8693) for the `auth: exchange` edges."""
151
+
152
+ issuer: Annotated[str, StringConstraints(min_length=1)] = Field(
153
+ description="Compared with each called agent's AUTH_JWT_ISSUER."
154
+ )
155
+ token_url: str | dict[EnvName, str] | None = Field(
156
+ default=None,
157
+ description=(
158
+ "The issuer's token endpoint (TOKEN_EXCHANGE_URL), one for every environment or one "
159
+ "per environment."
160
+ ),
161
+ )
162
+
163
+
164
+ class Environment(_Strict):
165
+ """Where the agents run, and so the URL each is called at."""
166
+
167
+ port_base: Annotated[int, Field(ge=1024, le=65000)] | None = Field(
168
+ default=None,
169
+ description=(
170
+ "Local processes: the agent at position i of `agents` listens on "
171
+ "http://127.0.0.1:<port_base + i>."
172
+ ),
173
+ )
174
+ url: Annotated[str, StringConstraints(min_length=1)] | None = Field(
175
+ default=None,
176
+ description=(
177
+ "Each agent's URL, from a template with {agent} (and optionally {project}, {env}), "
178
+ "e.g. https://{agent}.agents.example.com. Without url or port_base, the in-cluster "
179
+ "URL of each project's chart and manifest namespace."
180
+ ),
181
+ )
182
+
183
+ @model_validator(mode="after")
184
+ def _one_kind(self) -> Environment:
185
+ if self.port_base is not None and self.url is not None:
186
+ raise ValueError("give port_base (local processes) or url, not both")
187
+ if self.url is not None and "{agent}" not in self.url and "{project}" not in self.url:
188
+ raise ValueError("url must hold {agent} or {project}: every agent needs its own URL")
189
+ return self
190
+
191
+
192
+ class Database(_Strict):
193
+ """A database server the agents share (enables check SC10)."""
194
+
195
+ max_connections: dict[EnvName, Annotated[int, Field(ge=1)]] = Field(
196
+ default_factory=dict,
197
+ description="The server's max_connections, per environment.",
198
+ )
199
+
200
+
201
+ class Deploy(_Strict):
202
+ """How `system deploy` runs."""
203
+
204
+ parallel: Annotated[int, Field(ge=1, le=MAX_PARALLEL)] = Field(
205
+ default=DEFAULT_PARALLEL,
206
+ description="How many projects deploy at once (default 3).",
207
+ )
208
+
209
+
210
+ class SystemFile(_Strict):
211
+ """A graph-agents-cli system: agent projects that call each other."""
212
+
213
+ version: Literal[1] = Field(description="The format version: 1.")
214
+ name: Annotated[str, StringConstraints(pattern=r"^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$")] = Field(
215
+ description="The system's name."
216
+ )
217
+ agents: dict[AgentName, Agent] = Field(
218
+ min_length=1, description="The agents, by name (the name their callers give them)."
219
+ )
220
+ identity: Identity | None = Field(
221
+ default=None, description="Required when an edge uses auth: exchange."
222
+ )
223
+ environments: dict[EnvName, Environment | None] = Field(
224
+ default_factory=dict, description="The environments the agents run in."
225
+ )
226
+ database: Database | None = None
227
+ deploy: Deploy = Field(default_factory=Deploy)
228
+
229
+ def edges(self, name: str) -> list[Edge]:
230
+ """The edges of agent ``name``, a bare name read as an edge with the defaults."""
231
+ return [
232
+ call if isinstance(call, Edge) else Edge(agent=call) for call in self.agents[name].calls
233
+ ]
234
+
235
+ def environment(self, name: str) -> Environment:
236
+ return self.environments.get(name) or Environment()
237
+
238
+
239
+ class SystemFileError(click.ClickException):
240
+ """The system file is unreadable or invalid, or names an unknown project (exit 3)."""
241
+
242
+ exit_code = EXIT_INVALID
243
+
244
+
245
+ def find(start: Path | None = None) -> Path | None:
246
+ """``graph-agents-system.yaml`` in ``start`` (the working directory) or a parent."""
247
+ here = (start or Path.cwd()).resolve()
248
+ for directory in [here, *here.parents]:
249
+ candidate = directory / SYSTEM_FILENAME
250
+ if candidate.is_file():
251
+ return candidate
252
+ return None
253
+
254
+
255
+ def _where(loc: tuple[Any, ...]) -> str:
256
+ parts: list[str] = []
257
+ for part in loc:
258
+ if isinstance(part, int):
259
+ parts.append(f"[{part}]")
260
+ elif part == "[key]":
261
+ parts.append(" (the name)")
262
+ elif part in ("str", "Edge", "dict[str,str]") or str(part).startswith(
263
+ ("function-", "constrained-str", "literal[")
264
+ ):
265
+ continue # a union branch, not a place in the file
266
+ else:
267
+ parts.append(("." if parts else "") + str(part))
268
+ return "".join(parts) or "(the file)"
269
+
270
+
271
+ # A value that may be a name or an edge (a mapping) fails both branches of the union; only
272
+ # the branch of the value's own kind says anything useful.
273
+ _OTHER_BRANCH = {
274
+ dict: ("Input should be a valid string",),
275
+ str: ("Input should be a valid dictionary",),
276
+ }
277
+
278
+
279
+ def validation_errors(data: Any) -> list[str]:
280
+ """The schema errors of a parsed file (empty when valid)."""
281
+ try:
282
+ SystemFile.model_validate(data)
283
+ except ValidationError as exc:
284
+ seen: list[str] = []
285
+ for error in exc.errors():
286
+ noise = _OTHER_BRANCH.get(type(error.get("input")), ())
287
+ if any(str(error["msg"]).startswith(prefix) for prefix in noise):
288
+ continue
289
+ message = str(error["msg"]).removeprefix("Value error, ")
290
+ line = f"{_where(tuple(error['loc']))}: {message}"
291
+ if line not in seen:
292
+ seen.append(line)
293
+ return seen
294
+ return []
295
+
296
+
297
+ def load(path: Path) -> SystemFile:
298
+ """Read and validate a system file; ``SystemFileError`` (exit 3) when it cannot be used."""
299
+ from graph_agents_cli._api_policy import PolicyLoader
300
+
301
+ try:
302
+ text = path.read_text(encoding="utf-8")
303
+ except (OSError, UnicodeDecodeError) as exc:
304
+ raise SystemFileError(f"cannot read {path}: {exc}") from None
305
+ try:
306
+ data = yaml.load(text, Loader=PolicyLoader) # refuses a repeated key
307
+ except yaml.YAMLError as exc:
308
+ raise SystemFileError(f"{path} is not valid YAML: {exc}") from None
309
+ errors = validation_errors(data)
310
+ if errors:
311
+ lines = "\n".join(f" - {error}" for error in errors)
312
+ raise SystemFileError(f"{path} is invalid:\n{lines}")
313
+ return SystemFile.model_validate(data)
314
+
315
+
316
+ # ---------------------------------------------------------------------------
317
+ # The published JSON Schema
318
+ # ---------------------------------------------------------------------------
319
+
320
+ SCHEMA_FILE = "graph-agents-system.schema.json"
321
+ # Repo-only, like the extension schema: for editors and the published contract.
322
+ # <repo>/src/graph_agents_cli/system/_model.py -> <repo>/schemas/<file>
323
+ SCHEMA_PATH = Path(__file__).resolve().parents[3] / "schemas" / SCHEMA_FILE
324
+ SCHEMA_ID = f"https://raw.githubusercontent.com/ss7172/graph-agents-cli/main/schemas/{SCHEMA_FILE}"
325
+
326
+
327
+ def _closed(node: Any) -> None:
328
+ """Mappings keyed by a pattern (agent and environment names) take no other key, as the
329
+ models refuse one (pydantic writes only ``patternProperties``)."""
330
+ if isinstance(node, dict):
331
+ if "patternProperties" in node and "additionalProperties" not in node:
332
+ node["additionalProperties"] = False
333
+ for value in node.values():
334
+ _closed(value)
335
+ elif isinstance(node, list):
336
+ for value in node:
337
+ _closed(value)
338
+
339
+
340
+ def build_json_schema() -> dict[str, Any]:
341
+ """The file's schema as a JSON Schema 2020-12 document."""
342
+ schema = SystemFile.model_json_schema(by_alias=True)
343
+ _closed(schema)
344
+ return {
345
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
346
+ "$id": SCHEMA_ID,
347
+ **schema,
348
+ "title": "graph-agents-cli system (graph-agents-system.yaml, version 1)",
349
+ "description": (
350
+ "Agent projects that call each other over A2A, for `graph-agents-cli system`. "
351
+ "Generated from the CLI's own models: edit those, not this file."
352
+ ),
353
+ }
354
+
355
+
356
+ def render() -> str:
357
+ """The schema as it is written to disk (trailing newline included)."""
358
+ return json.dumps(build_json_schema(), indent=2, sort_keys=False) + "\n"
359
+
360
+
361
+ if __name__ == "__main__":
362
+ SCHEMA_PATH.write_text(render(), encoding="utf-8")
363
+ print(f"wrote {SCHEMA_PATH}")