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,15 @@
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 deploy commands."""
@@ -0,0 +1,171 @@
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
+ """Deployment settings read from the project manifest.
15
+
16
+ Adapts ``ProjectConfig`` (the manifest's field names) to the small set of
17
+ values deploy, secrets, and infra need. Attributes are read defensively so the
18
+ commands work while ``_project.py`` is being finalised; every fallback follows
19
+ the manifest schema and the CLI's defaults.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from collections.abc import Mapping
25
+ from dataclasses import dataclass, field
26
+ from pathlib import Path
27
+ from typing import Any
28
+
29
+ from graph_agents_cli import _project
30
+ from graph_agents_cli._defaults import (
31
+ ENVIRONMENTS,
32
+ auth_policy_implemented_default,
33
+ default_secret_keys,
34
+ normalize_auth_policy,
35
+ )
36
+ from graph_agents_cli.deploy._kube import ConfigError, Target
37
+
38
+
39
+ @dataclass
40
+ class DeploySettings:
41
+ """Everything deploy/secrets/infra need from the manifest."""
42
+
43
+ project_name: str
44
+ deployment_target: str = "kubernetes"
45
+ runtime: str = "fastapi"
46
+ model_provider: str = "openai"
47
+ registry: str = ""
48
+ cd: str = "skip"
49
+ auth_policy: str = "shared-bearer"
50
+ auth_policy_implemented: bool = True
51
+ environments: dict[str, dict[str, str]] = field(default_factory=dict)
52
+ secret_keys: list[str] = field(default_factory=list)
53
+ secrets_owner: str = ""
54
+
55
+ # -- derived -------------------------------------------------------------
56
+
57
+ @property
58
+ def release(self) -> str:
59
+ return self.project_name
60
+
61
+ @property
62
+ def secret_name(self) -> str:
63
+ return f"{self.project_name}-app"
64
+
65
+ @property
66
+ def metrics_secret_name(self) -> str:
67
+ """The Secret holding only ``METRICS_TOKEN``, for the Prometheus ServiceMonitor."""
68
+ return f"{self.project_name}-metrics"
69
+
70
+ @property
71
+ def chart_dir(self) -> Path:
72
+ return Path("deployment") / "helm" / self.project_name
73
+
74
+ def values_file(self, env: str) -> Path:
75
+ return self.chart_dir / f"values-{env}.yaml"
76
+
77
+ @property
78
+ def image_repository(self) -> str:
79
+ if not self.registry:
80
+ raise ConfigError(
81
+ "No registry is configured (create_params.registry is empty).\n"
82
+ " Set it in graph-agents-cli-manifest.yaml, e.g. registry: ghcr.io/my-org"
83
+ )
84
+ return f"{self.registry.rstrip('/')}/{self.project_name}"
85
+
86
+ def environment(self, env: str) -> dict[str, str]:
87
+ if env not in self.environments:
88
+ known = ", ".join(sorted(self.environments)) or "none"
89
+ raise ConfigError(
90
+ f"Unknown environment {env!r}. Environments in the manifest: {known}."
91
+ )
92
+ return self.environments[env]
93
+
94
+ def target(self, env: str) -> Target:
95
+ spec = self.environment(env)
96
+ namespace = spec.get("namespace") or f"{self.project_name}-{env}"
97
+ return Target(context=spec.get("context") or None, namespace=namespace)
98
+
99
+ # -- construction --------------------------------------------------------
100
+
101
+ @classmethod
102
+ def from_project(cls, cfg: Any) -> DeploySettings:
103
+ cp = getattr(cfg, "create_params", None)
104
+ cp = dict(cp) if isinstance(cp, Mapping) else {}
105
+
106
+ def pick(attr: str, default: Any) -> Any:
107
+ value = getattr(cfg, attr, None)
108
+ if value is None:
109
+ value = cp.get(attr)
110
+ return default if value is None else value
111
+
112
+ name = getattr(cfg, "project_name", "") or ""
113
+ if not name:
114
+ raise ConfigError("The manifest has no project name.")
115
+ deployment_target = pick("deployment_target", "none")
116
+ if deployment_target != "kubernetes":
117
+ raise ConfigError(
118
+ f"This project has deployment_target: {deployment_target}; nothing to deploy.\n"
119
+ " Re-scaffold with --deployment-target kubernetes to add a Helm chart."
120
+ )
121
+ runtime = pick("runtime", "fastapi")
122
+ model_provider = pick("model_provider", "openai")
123
+ auth_policy = normalize_auth_policy(pick("auth_policy", "shared-bearer"), warn=False)
124
+ implemented = pick("auth_policy_implemented", None)
125
+ if implemented is None:
126
+ implemented = auth_policy_implemented_default(auth_policy)
127
+
128
+ environments: dict[str, dict[str, str]] = {}
129
+ raw_envs = getattr(cfg, "environments", None)
130
+ if isinstance(raw_envs, Mapping):
131
+ for env, spec in raw_envs.items():
132
+ spec = spec if isinstance(spec, Mapping) else {}
133
+ environments[str(env)] = {
134
+ "context": str(spec.get("context") or ""),
135
+ "namespace": str(spec.get("namespace") or f"{name}-{env}"),
136
+ }
137
+ if not environments:
138
+ environments = {
139
+ env: {"context": "", "namespace": f"{name}-{env}"} for env in ENVIRONMENTS
140
+ }
141
+
142
+ secret_keys = getattr(cfg, "secret_keys", None)
143
+ owner = ""
144
+ raw_secrets = getattr(cfg, "secrets", None)
145
+ if isinstance(raw_secrets, Mapping):
146
+ if secret_keys is None:
147
+ secret_keys = raw_secrets.get("keys")
148
+ owner = str(raw_secrets.get("owner") or "")
149
+ if secret_keys is None:
150
+ secret_keys = default_secret_keys(model_provider, runtime, auth_policy=auth_policy)
151
+ owner = getattr(cfg, "secrets_owner", None) or owner
152
+
153
+ return cls(
154
+ project_name=name,
155
+ deployment_target=deployment_target,
156
+ runtime=runtime,
157
+ model_provider=model_provider,
158
+ registry=str(pick("registry", "") or ""),
159
+ cd=str(pick("cd", "skip") or "skip"),
160
+ auth_policy=auth_policy,
161
+ auth_policy_implemented=bool(implemented),
162
+ environments=environments,
163
+ secret_keys=[str(k) for k in secret_keys],
164
+ secrets_owner=owner,
165
+ )
166
+
167
+
168
+ def load_settings() -> DeploySettings:
169
+ """chdir to the project root and read the manifest into ``DeploySettings``."""
170
+ _project.chdir_project_root()
171
+ return DeploySettings.from_project(_project.read_project_config())
@@ -0,0 +1,128 @@
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
+ """Image reference checks run before ``docker build``, ``docker push`` or ``helm``.
15
+
16
+ ``create`` writes the placeholder registry ``ghcr.io/CHANGE-ME`` when it finds
17
+ no git remote. Docker rejects it (a repository path must be lowercase) only
18
+ after the build starts, with a parser error that does not say which setting to
19
+ change. These helpers catch the placeholder and any other malformed reference
20
+ up front so ``build`` and ``deploy`` stop with a configuration error (exit 3)
21
+ that names the setting.
22
+
23
+ The grammar follows the distribution reference format Docker uses: an optional
24
+ registry host (with port), lowercase path components separated by ``/``, and a
25
+ tag of at most 128 word characters, dots and dashes.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import re
31
+
32
+ from graph_agents_cli._defaults import REGISTRY_FIX_COMMAND, REGISTRY_FIX_EFFECT
33
+
34
+ PLACEHOLDER = "CHANGE-ME"
35
+
36
+ _DOMAIN_COMPONENT = r"(?:[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])"
37
+ _DOMAIN_RE = re.compile(
38
+ rf"^(?:{_DOMAIN_COMPONENT}(?:\.{_DOMAIN_COMPONENT})*|\[[0-9A-Fa-f:.]+\])(?::[0-9]+)?$"
39
+ )
40
+ _PATH_COMPONENT_RE = re.compile(r"^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$")
41
+ _TAG_RE = re.compile(r"^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$")
42
+ MAX_NAME_LENGTH = 255
43
+
44
+
45
+ def has_placeholder(value: str | None) -> bool:
46
+ """True when ``value`` still carries the scaffold's ``CHANGE-ME`` placeholder."""
47
+ return PLACEHOLDER.lower() in (value or "").lower()
48
+
49
+
50
+ def _split_domain(name: str) -> tuple[str | None, str]:
51
+ """``(domain, path)`` using Docker's rule for what counts as a registry host.
52
+
53
+ The first component is a host when it contains ``.`` or ``:``, is
54
+ ``localhost``, or has an upper-case letter; otherwise the whole name is a
55
+ path on the default registry.
56
+ """
57
+ first, sep, rest = name.partition("/")
58
+ if not sep:
59
+ return None, name
60
+ if "." in first or ":" in first or first == "localhost" or first.lower() != first:
61
+ return first, rest
62
+ return None, name
63
+
64
+
65
+ def repository_problem(repository: str) -> str | None:
66
+ """Why ``repository`` (``[host[:port]/]path``, no tag) is not a valid image name."""
67
+ if not repository:
68
+ return "the repository is empty"
69
+ if len(repository) > MAX_NAME_LENGTH:
70
+ return f"the repository name is longer than {MAX_NAME_LENGTH} characters"
71
+ domain, path = _split_domain(repository)
72
+ if domain is not None and not _DOMAIN_RE.match(domain):
73
+ return f"{domain!r} is not a valid registry host"
74
+ for component in path.split("/"):
75
+ if not component:
76
+ return "the repository path has an empty component (a doubled or trailing '/')"
77
+ if not _PATH_COMPONENT_RE.match(component):
78
+ if component.lower() != component:
79
+ return f"the repository path component {component!r} must be lowercase"
80
+ return (
81
+ f"the repository path component {component!r} may only hold lowercase letters, "
82
+ "digits and single '.', '_' or '-' separators"
83
+ )
84
+ return None
85
+
86
+
87
+ def tag_problem(tag: str) -> str | None:
88
+ """Why ``tag`` is not a valid image tag (``None`` when it is)."""
89
+ if not _TAG_RE.match(tag or ""):
90
+ return (
91
+ f"the tag {tag!r} must start with a letter, digit or '_' and hold at most 128 "
92
+ "letters, digits, '_', '.' or '-'"
93
+ )
94
+ return None
95
+
96
+
97
+ def placeholder_message(registry: str) -> str:
98
+ return (
99
+ f"The image registry is still the placeholder {registry!r}.\n"
100
+ f" Run `{REGISTRY_FIX_COMMAND}` ({REGISTRY_FIX_EFFECT}), or set "
101
+ "create_params.registry in graph-agents-cli-manifest.yaml to your registry (for "
102
+ "example ghcr.io/<org>) by hand; `build` also takes --registry."
103
+ )
104
+
105
+
106
+ def reference_problem(
107
+ repository: str, tag: str | None, *, registry: str | None = None
108
+ ) -> str | None:
109
+ """A user-facing reason the image ``repository[:tag]`` cannot be built or pushed.
110
+
111
+ ``registry`` is the configured registry the repository was derived from; a
112
+ placeholder there is reported as such (it names the setting to change)
113
+ rather than as a grammar error.
114
+ """
115
+ if has_placeholder(registry):
116
+ return placeholder_message(registry or "")
117
+ if has_placeholder(repository):
118
+ return (
119
+ f"The image reference {repository!r} still holds the {PLACEHOLDER} placeholder; "
120
+ "pass the real image."
121
+ )
122
+ problem = repository_problem(repository)
123
+ if problem is None and tag is not None:
124
+ problem = tag_problem(tag)
125
+ if problem is None:
126
+ return None
127
+ ref = f"{repository}:{tag}" if tag is not None else repository
128
+ return f"{ref!r} is not a valid image reference: {problem}."
@@ -0,0 +1,286 @@
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
+ """kubectl and helm wrappers used by deploy, secrets, and infra check.
15
+
16
+ Every subprocess goes through ``graph_agents_cli._runner.run_resolved`` (looked
17
+ up at call time so tests can monkeypatch it). Nothing here imports a Kubernetes
18
+ client; the CLI shells out to ``kubectl`` and ``helm``.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ import shlex
26
+ import subprocess
27
+ from dataclasses import dataclass
28
+ from typing import Any
29
+
30
+ import click
31
+
32
+ from graph_agents_cli import _runner, _tools
33
+ from graph_agents_cli._output import Console
34
+
35
+
36
+ class DeployError(click.ClickException):
37
+ """Base for deploy/secrets/infra failures; ``exit_code``: 1 refused, 2 tool failed, 3 config."""
38
+
39
+ exit_code = 1
40
+
41
+
42
+ class Refused(DeployError):
43
+ """The operation was refused by policy or mode (exit 1)."""
44
+
45
+ exit_code = 1
46
+
47
+
48
+ class ToolFailed(DeployError):
49
+ """helm/kubectl/docker/git/gh returned non-zero or is missing (exit 2)."""
50
+
51
+ exit_code = 2
52
+
53
+
54
+ class ConfigError(DeployError):
55
+ """The project or environment is misconfigured (exit 3)."""
56
+
57
+ exit_code = 3
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class Target:
62
+ """Where a command is aimed: a kube context (``None`` = current) and a namespace."""
63
+
64
+ context: str | None
65
+ namespace: str
66
+
67
+
68
+ def format_cmd(cmd: list[str]) -> str:
69
+ return _runner.redact_cmd(list(cmd))
70
+
71
+
72
+ def echo_cmd(cmd: list[str], *, dry_run: bool = False, console: Console | None = None) -> None:
73
+ """Print a command the way the rest of the CLI does (``▸ cmd``)."""
74
+ prefix = "[dry-run]" if dry_run else "▸"
75
+ (console or Console()).print(
76
+ f" {prefix} {format_cmd(cmd)}", style="cyan", highlight=False, markup=False
77
+ )
78
+
79
+
80
+ def run_cmd(
81
+ cmd: list[str],
82
+ *,
83
+ capture: bool = True,
84
+ check: bool = True,
85
+ input_text: str | None = None,
86
+ env: dict[str, str] | None = None,
87
+ dry_run: bool = False,
88
+ quiet: bool = False,
89
+ console: Console | None = None,
90
+ ) -> subprocess.CompletedProcess[str]:
91
+ """Run ``cmd`` through ``run_resolved``.
92
+
93
+ ``dry_run`` prints the command and returns an empty, successful result.
94
+ A missing tool or a non-zero exit (with ``check``) raises ``ToolFailed`` (exit 2).
95
+ """
96
+ if not quiet:
97
+ echo_cmd(cmd, dry_run=dry_run, console=console)
98
+ if dry_run:
99
+ return subprocess.CompletedProcess(cmd, 0, "", "")
100
+ run_env = {**os.environ, **env} if env else None
101
+ try:
102
+ result = _runner.run_resolved(
103
+ list(cmd),
104
+ capture_output=capture,
105
+ text=True,
106
+ encoding="utf-8",
107
+ errors="replace",
108
+ input=input_text,
109
+ env=run_env,
110
+ )
111
+ except _tools.ToolNotFoundError as e:
112
+ raise ToolFailed(str(e)) from e
113
+ except OSError as e:
114
+ raise ToolFailed(f"Could not run {format_cmd(cmd)}: {e}") from e
115
+ if check and result.returncode != 0:
116
+ detail = "\n".join(
117
+ part.strip()
118
+ for part in (result.stdout, result.stderr)
119
+ if isinstance(part, str) and part.strip()
120
+ )
121
+ msg = f"Command failed (exit code {result.returncode}): {format_cmd(cmd)}"
122
+ if detail:
123
+ msg += f"\n{detail}"
124
+ raise ToolFailed(msg)
125
+ return result
126
+
127
+
128
+ def tool_available(name: str) -> bool:
129
+ """True when ``name`` resolves on PATH (via ``require_tool``, monkeypatchable)."""
130
+ try:
131
+ _tools.require_tool(name)
132
+ except _tools.ToolNotFoundError:
133
+ return False
134
+ return True
135
+
136
+
137
+ def kubectl_args(args: list[str], target: Target | None, *, namespaced: bool = True) -> list[str]:
138
+ cmd = ["kubectl", *args]
139
+ if target is not None:
140
+ if namespaced and target.namespace:
141
+ cmd += ["-n", target.namespace]
142
+ if target.context:
143
+ cmd += ["--context", target.context]
144
+ return cmd
145
+
146
+
147
+ def kubectl(
148
+ args: list[str],
149
+ target: Target | None = None,
150
+ *,
151
+ namespaced: bool = True,
152
+ **kwargs: Any,
153
+ ) -> subprocess.CompletedProcess[str]:
154
+ return run_cmd(kubectl_args(args, target, namespaced=namespaced), **kwargs)
155
+
156
+
157
+ def kubectl_json(
158
+ args: list[str], target: Target | None = None, *, namespaced: bool = True, **kwargs: Any
159
+ ) -> Any:
160
+ """Run ``kubectl ... -o json`` and parse the result (``None`` on dry-run)."""
161
+ result = kubectl([*args, "-o", "json"], target, namespaced=namespaced, **kwargs)
162
+ if not result.stdout:
163
+ return None
164
+ try:
165
+ return json.loads(result.stdout)
166
+ except json.JSONDecodeError as e:
167
+ raise ToolFailed(f"kubectl returned invalid JSON: {e}") from e
168
+
169
+
170
+ def helm_args(args: list[str], target: Target | None) -> list[str]:
171
+ cmd = ["helm", *args]
172
+ if target is not None:
173
+ if target.namespace:
174
+ cmd += ["-n", target.namespace]
175
+ if target.context:
176
+ cmd += ["--kube-context", target.context]
177
+ return cmd
178
+
179
+
180
+ def helm(
181
+ args: list[str], target: Target | None = None, **kwargs: Any
182
+ ) -> subprocess.CompletedProcess[str]:
183
+ return run_cmd(helm_args(args, target), **kwargs)
184
+
185
+
186
+ def current_context() -> str | None:
187
+ """The kubeconfig's current context, or ``None`` when kubectl is missing or unset."""
188
+ try:
189
+ result = run_cmd(["kubectl", "config", "current-context"], check=False, quiet=True)
190
+ except ToolFailed:
191
+ return None
192
+ if result.returncode != 0:
193
+ return None
194
+ return (result.stdout or "").strip() or None
195
+
196
+
197
+ def context_names() -> set[str]:
198
+ """Context names in the kubeconfig (empty when they cannot be listed; never raises)."""
199
+ try:
200
+ result = run_cmd(
201
+ ["kubectl", "config", "get-contexts", "-o", "name"], check=False, quiet=True
202
+ )
203
+ except ToolFailed:
204
+ return set()
205
+ if result.returncode != 0:
206
+ return set()
207
+ return {line.strip() for line in (result.stdout or "").splitlines() if line.strip()}
208
+
209
+
210
+ def server_url(context: str | None) -> str | None:
211
+ """The API server URL the kubeconfig records for ``context`` (read locally, never raises)."""
212
+ cmd = [
213
+ "kubectl",
214
+ "config",
215
+ "view",
216
+ "--minify",
217
+ "-o",
218
+ "jsonpath={.clusters[0].cluster.server}",
219
+ ]
220
+ if context:
221
+ cmd += ["--context", context]
222
+ try:
223
+ result = run_cmd(cmd, check=False, quiet=True)
224
+ except ToolFailed:
225
+ return None
226
+ if result.returncode != 0:
227
+ return None
228
+ return (result.stdout or "").strip() or None
229
+
230
+
231
+ def in_ci() -> bool:
232
+ """True inside a GitHub Actions job (the runner sets ``GITHUB_ACTIONS=true``)."""
233
+ return os.environ.get("GITHUB_ACTIONS", "").strip().lower() == "true"
234
+
235
+
236
+ def ensure_namespace(
237
+ target: Target, *, dry_run: bool = False, console: Console | None = None
238
+ ) -> None:
239
+ """Create ``target.namespace`` when it does not exist (idempotent).
240
+
241
+ A fresh cluster has no ``<name>-<env>`` namespace yet, and the Secret is
242
+ applied before ``helm --create-namespace`` runs (in CD modes helm never runs
243
+ from here at all). A caller without permission to read namespaces
244
+ (namespace-scoped RBAC) is assumed to work in an existing one; any other
245
+ failure is a tool failure (exit 2).
246
+ """
247
+ console = console or Console()
248
+ ns = target.namespace
249
+ create = kubectl_args(["create", "namespace", ns], target, namespaced=False)
250
+ if dry_run:
251
+ console.print(
252
+ f" [dry-run] {format_cmd(create)} (only when the namespace does not exist)",
253
+ style="cyan",
254
+ highlight=False,
255
+ markup=False,
256
+ )
257
+ return
258
+ probe = kubectl(
259
+ ["get", "namespace", ns, "-o", "name"], target, namespaced=False, check=False, quiet=True
260
+ )
261
+ if probe.returncode == 0:
262
+ return
263
+ stderr = (probe.stderr or "").strip()
264
+ if "(Forbidden)" in stderr:
265
+ console.print(
266
+ f" Cannot read namespace {ns} (RBAC); assuming it exists.", style="dim", markup=False
267
+ )
268
+ return
269
+ if "(NotFound)" not in stderr:
270
+ raise ToolFailed(
271
+ f"Command failed (exit code {probe.returncode}): "
272
+ f"{format_cmd(kubectl_args(['get', 'namespace', ns], target, namespaced=False))}"
273
+ + (f"\n{stderr}" if stderr else "")
274
+ )
275
+ result = run_cmd(create, check=False, console=console)
276
+ if result.returncode != 0 and "(AlreadyExists)" not in (result.stderr or ""):
277
+ detail = (result.stderr or result.stdout or "").strip()
278
+ raise ToolFailed(
279
+ f"Command failed (exit code {result.returncode}): {format_cmd(create)}"
280
+ + (f"\n{detail}" if detail else "")
281
+ )
282
+ console.print(f" Created namespace {ns}.")
283
+
284
+
285
+ def pipe_description(first: list[str], second: list[str]) -> str:
286
+ return f"{shlex.join(first)} | {shlex.join(second)}"