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,562 @@
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
+ """argocd-mode desired-state writer: values tag rewrite, branch, pull request.
15
+
16
+ ``deploy`` never runs helm in argocd mode (Argo CD applies the change). It takes ``values-<env>.yaml``
17
+ as ``origin/main`` holds it (the pull request's base; ``HEAD`` when that ref
18
+ is unavailable), rewrites ``image.tag`` in that text and nothing else, commits
19
+ the one file on ``deploy/<env>/<tag>`` with git plumbing (the developer's
20
+ checkout, index and working tree are left alone), pushes, and opens or
21
+ updates a pull request through ``gh``, or the GitHub REST API with
22
+ ``GITHUB_TOKEN`` when ``gh`` is not installed. Only
23
+ GitHub and GitHub Enterprise Server remotes are supported. Index paths are
24
+ relative to the repository root, so a project below the git root is handled.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import os
31
+ import re
32
+ import tempfile
33
+ from dataclasses import dataclass
34
+ from pathlib import Path
35
+
36
+ from graph_agents_cli._output import Console
37
+ from graph_agents_cli.deploy import _kube
38
+ from graph_agents_cli.deploy._kube import ConfigError, Refused, ToolFailed, run_cmd
39
+ from graph_agents_cli.deploy._values import rewrite_image_tag
40
+
41
+ DEFAULT_BASE_BRANCH = "main"
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class Remote:
46
+ host: str
47
+ owner: str
48
+ repo: str
49
+
50
+ @property
51
+ def slug(self) -> str:
52
+ return f"{self.owner}/{self.repo}"
53
+
54
+ @property
55
+ def gh_repo(self) -> str:
56
+ return f"{self.host}/{self.owner}/{self.repo}"
57
+
58
+ @property
59
+ def api_base(self) -> str:
60
+ if self.host == "github.com":
61
+ return "https://api.github.com"
62
+ return f"https://{self.host}/api/v3"
63
+
64
+
65
+ _REMOTE_RES = (
66
+ re.compile(
67
+ r"^(?:https?|ssh|git)://(?:[^@/]+@)?(?P<host>[^/:]+)(?::\d+)?/(?P<owner>[^/]+)/(?P<repo>[^/]+?)(?:\.git)?/?$"
68
+ ),
69
+ re.compile(r"^(?:[^@]+@)?(?P<host>[^:/]+):(?P<owner>[^/]+)/(?P<repo>[^/]+?)(?:\.git)?/?$"),
70
+ )
71
+
72
+
73
+ def parse_remote(url: str) -> Remote:
74
+ url = url.strip()
75
+ for pattern in _REMOTE_RES:
76
+ m = pattern.match(url)
77
+ if m:
78
+ return Remote(m.group("host").lower(), m.group("owner"), m.group("repo"))
79
+ raise Refused(f"Could not parse the git remote URL {url!r}.")
80
+
81
+
82
+ def enterprise_hosts() -> set[str]:
83
+ """Hosts the operator declared as GitHub Enterprise Server (GH_HOST / GITHUB_SERVER_URL)."""
84
+ hosts: set[str] = set()
85
+ for var in ("GH_HOST", "GITHUB_HOST"):
86
+ value = os.environ.get(var, "").strip().lower()
87
+ if value:
88
+ hosts.add(value)
89
+ server = os.environ.get("GITHUB_SERVER_URL", "").strip().lower()
90
+ if server:
91
+ hosts.add(re.sub(r"^https?://", "", server).split("/")[0])
92
+ return hosts
93
+
94
+
95
+ def ensure_github(remote: Remote) -> None:
96
+ if remote.host == "github.com" or remote.host in enterprise_hosts():
97
+ return
98
+ raise Refused(
99
+ f"The origin remote is on {remote.host!r}, which is not GitHub.\n"
100
+ " argocd mode opens pull requests only on GitHub or GitHub Enterprise Server in v1.\n"
101
+ " For a GitHub Enterprise Server, set GH_HOST=<host> (and GH_ENTERPRISE_TOKEN or GITHUB_TOKEN)."
102
+ )
103
+
104
+
105
+ def origin_url() -> str:
106
+ result = run_cmd(["git", "remote", "get-url", "origin"], check=False, quiet=True)
107
+ url = (result.stdout or "").strip()
108
+ if result.returncode != 0 or not url:
109
+ raise ConfigError("This project has no git remote named 'origin'; argocd mode needs one.")
110
+ return url
111
+
112
+
113
+ def short_sha() -> str | None:
114
+ try:
115
+ result = run_cmd(["git", "rev-parse", "--short", "HEAD"], check=False, quiet=True)
116
+ except ToolFailed:
117
+ return None
118
+ if result.returncode != 0:
119
+ return None
120
+ return (result.stdout or "").strip() or None
121
+
122
+
123
+ # Project paths the scaffold's .dockerignore keeps out of the image: changing them
124
+ # (chart values, Chart.lock written by `helm dependency build`, workflows, tests)
125
+ # does not change what a rebuild would contain.
126
+ NOT_IN_IMAGE = ("deployment", ".github", "tests", "docs")
127
+
128
+
129
+ def worktree_dirty() -> bool | None:
130
+ """Whether the image's sources in the project have uncommitted changes (``None`` outside git).
131
+
132
+ Limited to the project directory (``-- .``), so edits elsewhere in a monorepo
133
+ do not count, and to paths that reach the image (``NOT_IN_IMAGE`` excluded).
134
+ Untracked files count (a new module not yet added is part of the build
135
+ context), ignored ones do not.
136
+ """
137
+ excludes = [f":(exclude){path}" for path in NOT_IN_IMAGE]
138
+ try:
139
+ result = run_cmd(
140
+ ["git", "status", "--porcelain", "--", ".", *excludes], check=False, quiet=True
141
+ )
142
+ except ToolFailed:
143
+ return None
144
+ if result.returncode != 0:
145
+ return None
146
+ return bool((result.stdout or "").strip())
147
+
148
+
149
+ def branch_name(env: str, tag: str) -> str:
150
+ safe = re.sub(r"[^A-Za-z0-9._-]+", "-", tag).strip("-.") or "image"
151
+ return f"deploy/{env}/{safe}"
152
+
153
+
154
+ def github_token() -> str | None:
155
+ for var in ("GITHUB_TOKEN", "GH_TOKEN", "GH_ENTERPRISE_TOKEN"):
156
+ value = os.environ.get(var)
157
+ if value:
158
+ return value
159
+ return None
160
+
161
+
162
+ @dataclass
163
+ class PullRequestResult:
164
+ branch: str
165
+ url: str | None
166
+ created: bool
167
+ changed: bool
168
+
169
+
170
+ def repo_relative(path: Path) -> str:
171
+ """``path`` (project-relative, cwd = project root) as git index and tree paths need it.
172
+
173
+ ``git rev-parse --show-prefix`` is empty at the repository toplevel and
174
+ ``apps/agent/`` for a project below it; ``update-index --cacheinfo`` and
175
+ ``<ref>:<path>`` take that repo-relative form, unlike ``hash-object``.
176
+ """
177
+ result = run_cmd(["git", "rev-parse", "--show-prefix"], check=False, quiet=True)
178
+ if result.returncode != 0:
179
+ raise ConfigError("This project is not inside a git repository; argocd mode needs one.")
180
+ prefix = (result.stdout or "").strip()
181
+ if path.is_absolute():
182
+ try:
183
+ path = path.resolve().relative_to(Path.cwd().resolve())
184
+ except ValueError as e:
185
+ raise ConfigError(f"{path} is outside the project directory {Path.cwd()}.") from e
186
+ rel = path.as_posix()
187
+ if rel == ".." or rel.startswith("../") or "/../" in rel:
188
+ raise ConfigError(f"{path} is outside the project directory; argocd mode cannot commit it.")
189
+ return f"{prefix}{rel}"
190
+
191
+
192
+ def _resolve_base(base: str, *, dry_run: bool, console: Console) -> str:
193
+ """Fetch ``origin/<base>`` and return the ref to branch from (``HEAD`` when unavailable)."""
194
+ run_cmd(["git", "fetch", "origin", base], check=False, dry_run=dry_run, console=console)
195
+ base_ref = f"origin/{base}"
196
+ probe = run_cmd(["git", "rev-parse", "--verify", "--quiet", base_ref], check=False, quiet=True)
197
+ if probe.returncode != 0:
198
+ if dry_run:
199
+ console.print(
200
+ f" [dry-run] origin/{base} is not available locally; the real run would branch "
201
+ "from HEAD.",
202
+ style="yellow",
203
+ )
204
+ else:
205
+ console.print(
206
+ f" origin/{base} is not available locally; branching from HEAD instead.",
207
+ style="yellow",
208
+ )
209
+ base_ref = "HEAD"
210
+ return base_ref
211
+
212
+
213
+ def _base_values_text(base_ref: str, index_path: str, values_path: Path, *, dry_run: bool) -> str:
214
+ """The values file as ``base_ref`` holds it (the content the PR is built on)."""
215
+ result = run_cmd(
216
+ ["git", "cat-file", "blob", f"{base_ref}:{index_path}"], check=False, quiet=True
217
+ )
218
+ if result.returncode == 0 and (result.stdout or "").strip():
219
+ return result.stdout
220
+ if dry_run and values_path.is_file():
221
+ return values_path.read_text(encoding="utf-8")
222
+ raise ConfigError(
223
+ f"{index_path} is not committed on {base_ref}; commit it there first.\n"
224
+ " argocd mode rewrites image.tag in the copy the pull request is based on, "
225
+ "never the working tree."
226
+ )
227
+
228
+
229
+ def _remote_branch_sha(branch: str) -> str:
230
+ """The commit ``origin`` holds for ``refs/heads/<branch>``; ``""`` when the branch is absent."""
231
+ ref = f"refs/heads/{branch}"
232
+ result = run_cmd(["git", "ls-remote", "--heads", "origin", ref], check=False, quiet=True)
233
+ if result.returncode != 0:
234
+ raise ToolFailed(
235
+ f"git ls-remote origin {ref} failed (exit code {result.returncode}):\n"
236
+ f"{(result.stderr or result.stdout or '').strip()}"
237
+ )
238
+ for line in (result.stdout or "").splitlines():
239
+ sha, _, name = line.partition("\t")
240
+ if name.strip() == ref:
241
+ return sha.strip()
242
+ return ""
243
+
244
+
245
+ def _rev(spec: str) -> str | None:
246
+ result = run_cmd(["git", "rev-parse", "--verify", "--quiet", spec], check=False, quiet=True)
247
+ if result.returncode != 0:
248
+ return None
249
+ return (result.stdout or "").strip() or None
250
+
251
+
252
+ def _remote_has_same_change(remote_sha: str, branch: str, tree: str, base_ref: str) -> bool:
253
+ """True when ``origin/<branch>`` already is this change: same tree, parented on the base."""
254
+ if _rev(f"{remote_sha}^{{commit}}") is None:
255
+ # A fresh (shallow) CI checkout has never seen the branch: fetch just that commit.
256
+ run_cmd(
257
+ ["git", "fetch", "--no-tags", "origin", f"refs/heads/{branch}"],
258
+ check=False,
259
+ quiet=True,
260
+ )
261
+ remote_tree = _rev(f"{remote_sha}^{{tree}}")
262
+ remote_parent = _rev(f"{remote_sha}^")
263
+ base_commit = _rev(f"{base_ref}^{{commit}}")
264
+ return (
265
+ bool(remote_tree)
266
+ and remote_tree == tree
267
+ and bool(base_commit)
268
+ and (remote_parent == base_commit)
269
+ )
270
+
271
+
272
+ def _commit_single_file(
273
+ *,
274
+ index_path: str,
275
+ content: str,
276
+ branch: str,
277
+ base_ref: str,
278
+ message: str,
279
+ dry_run: bool,
280
+ console: Console,
281
+ ) -> None:
282
+ """Commit ``content`` at ``index_path`` on ``branch`` from ``base_ref`` and push it.
283
+
284
+ No checkout switch. The branch is bot-owned and deterministic (one tag, one
285
+ change), so a re-run replaces it. The push leases on the commit ``origin``
286
+ holds right now (read with ``ls-remote``, ``""`` for "must not exist"): a
287
+ bare ``--force-with-lease`` compares against a remote-tracking ref that a
288
+ fresh CI checkout does not have and is rejected as "stale info", while an
289
+ explicit lease still refuses to overwrite a push that lands in between.
290
+ When ``origin`` already holds the same change on the same base, nothing is
291
+ pushed, so a retried promotion does not reset an approved pull request.
292
+ """
293
+ blob = run_cmd(
294
+ ["git", "hash-object", "-w", "--stdin"],
295
+ input_text=content,
296
+ dry_run=dry_run,
297
+ console=console,
298
+ ).stdout.strip()
299
+ blob = blob or "<blob>"
300
+ with tempfile.TemporaryDirectory(prefix="graph-agents-cli-index-") as tmp:
301
+ env = {"GIT_INDEX_FILE": str(Path(tmp) / "index")}
302
+ run_cmd(["git", "read-tree", base_ref], env=env, dry_run=dry_run, console=console)
303
+ run_cmd(
304
+ ["git", "update-index", "--add", "--cacheinfo", f"100644,{blob},{index_path}"],
305
+ env=env,
306
+ dry_run=dry_run,
307
+ console=console,
308
+ )
309
+ tree = run_cmd(
310
+ ["git", "write-tree"], env=env, dry_run=dry_run, console=console
311
+ ).stdout.strip()
312
+ tree = tree or "<tree>"
313
+ remote_sha = "<sha on origin, empty when absent>" if dry_run else _remote_branch_sha(branch)
314
+ if not dry_run and remote_sha and _remote_has_same_change(remote_sha, branch, tree, base_ref):
315
+ run_cmd(["git", "update-ref", f"refs/heads/{branch}", remote_sha], console=console)
316
+ console.print(
317
+ f" origin/{branch} already holds this change on {base_ref}; nothing to push.",
318
+ markup=False,
319
+ )
320
+ return
321
+ commit = run_cmd(
322
+ ["git", "commit-tree", tree, "-p", base_ref, "-m", message],
323
+ dry_run=dry_run,
324
+ console=console,
325
+ ).stdout.strip()
326
+ commit = commit or "<commit>"
327
+ run_cmd(["git", "update-ref", f"refs/heads/{branch}", commit], dry_run=dry_run, console=console)
328
+ run_cmd(
329
+ [
330
+ "git",
331
+ "push",
332
+ f"--force-with-lease=refs/heads/{branch}:{remote_sha}",
333
+ "-u",
334
+ "origin",
335
+ f"{branch}:{branch}",
336
+ ],
337
+ dry_run=dry_run,
338
+ console=console,
339
+ capture=False,
340
+ )
341
+
342
+
343
+ def _pr_via_gh(
344
+ *,
345
+ remote: Remote,
346
+ branch: str,
347
+ base: str,
348
+ title: str,
349
+ body: str,
350
+ dry_run: bool,
351
+ console: Console,
352
+ ) -> tuple[str | None, bool]:
353
+ listing = run_cmd(
354
+ [
355
+ "gh",
356
+ "pr",
357
+ "list",
358
+ "--repo",
359
+ remote.gh_repo,
360
+ "--head",
361
+ branch,
362
+ "--base",
363
+ base,
364
+ "--state",
365
+ "open",
366
+ "--json",
367
+ "number,url",
368
+ ],
369
+ dry_run=dry_run,
370
+ console=console,
371
+ )
372
+ existing: list[dict] = []
373
+ if listing.stdout.strip():
374
+ try:
375
+ existing = json.loads(listing.stdout)
376
+ except json.JSONDecodeError:
377
+ existing = []
378
+ if existing:
379
+ number = str(existing[0].get("number"))
380
+ run_cmd(
381
+ [
382
+ "gh",
383
+ "pr",
384
+ "edit",
385
+ number,
386
+ "--repo",
387
+ remote.gh_repo,
388
+ "--title",
389
+ title,
390
+ "--body",
391
+ body,
392
+ ],
393
+ dry_run=dry_run,
394
+ console=console,
395
+ )
396
+ return existing[0].get("url"), False
397
+ created = run_cmd(
398
+ [
399
+ "gh",
400
+ "pr",
401
+ "create",
402
+ "--repo",
403
+ remote.gh_repo,
404
+ "--base",
405
+ base,
406
+ "--head",
407
+ branch,
408
+ "--title",
409
+ title,
410
+ "--body",
411
+ body,
412
+ ],
413
+ dry_run=dry_run,
414
+ console=console,
415
+ )
416
+ url = (created.stdout or "").strip().splitlines()
417
+ return (url[-1] if url else None), True
418
+
419
+
420
+ def _pr_via_rest(
421
+ *,
422
+ remote: Remote,
423
+ token: str,
424
+ branch: str,
425
+ base: str,
426
+ title: str,
427
+ body: str,
428
+ dry_run: bool,
429
+ console: Console,
430
+ ) -> tuple[str | None, bool]:
431
+ pulls = f"{remote.api_base}/repos/{remote.owner}/{remote.repo}/pulls"
432
+ headers = {
433
+ "Authorization": f"Bearer {token}",
434
+ "Accept": "application/vnd.github+json",
435
+ "X-GitHub-Api-Version": "2022-11-28",
436
+ }
437
+ console.print(
438
+ f" {'[dry-run] ' if dry_run else ''}GET {pulls}?head={remote.owner}:{branch}&base={base}",
439
+ style="cyan",
440
+ markup=False,
441
+ )
442
+ if dry_run:
443
+ console.print(
444
+ f" [dry-run] POST {pulls} (or PATCH the open pull request)", style="cyan", markup=False
445
+ )
446
+ return None, False
447
+ from graph_agents_cli import _http
448
+
449
+ # Honours the environment's proxies; one that cannot be used is a one-line error.
450
+ with _http.client(pulls, headers=headers, timeout=30) as client:
451
+ resp = client.get(
452
+ pulls, params={"head": f"{remote.owner}:{branch}", "base": base, "state": "open"}
453
+ )
454
+ if resp.status_code != 200:
455
+ raise ToolFailed(
456
+ f"GitHub API GET {pulls} returned {resp.status_code}: {resp.text[:200]}"
457
+ )
458
+ existing = resp.json()
459
+ if existing:
460
+ number = existing[0]["number"]
461
+ resp = client.patch(f"{pulls}/{number}", json={"title": title, "body": body})
462
+ if resp.status_code not in (200, 201):
463
+ raise ToolFailed(f"GitHub API PATCH {pulls}/{number} returned {resp.status_code}")
464
+ return existing[0].get("html_url"), False
465
+ resp = client.post(pulls, json={"title": title, "body": body, "head": branch, "base": base})
466
+ if resp.status_code not in (200, 201):
467
+ raise ToolFailed(
468
+ f"GitHub API POST {pulls} returned {resp.status_code}: {resp.text[:200]}"
469
+ )
470
+ return resp.json().get("html_url"), True
471
+
472
+
473
+ def write_desired_state(
474
+ *,
475
+ env: str,
476
+ values_path: Path,
477
+ image_repository: str | None,
478
+ tag: str,
479
+ project_name: str,
480
+ base: str = DEFAULT_BASE_BRANCH,
481
+ dry_run: bool = False,
482
+ console: Console | None = None,
483
+ ) -> PullRequestResult:
484
+ """Rewrite the tag on the base branch's copy, commit on ``deploy/<env>/<tag>``, push, open the PR.
485
+
486
+ The content comes from ``origin/<base>`` (``HEAD`` when unavailable), so a
487
+ checkout behind ``main`` or with uncommitted edits never leaks into the
488
+ pull request, and "nothing to change" is judged against the base branch.
489
+ The developer's working tree is not modified.
490
+ """
491
+ console = console or Console()
492
+ remote = parse_remote(origin_url())
493
+ ensure_github(remote)
494
+
495
+ index_path = repo_relative(values_path)
496
+ base_ref = _resolve_base(base, dry_run=dry_run, console=console)
497
+ base_text = _base_values_text(base_ref, index_path, values_path, dry_run=dry_run)
498
+ old, new_text, changed = rewrite_image_tag(base_text, tag, source=f"{base_ref}:{index_path}")
499
+ if not changed:
500
+ console.print(
501
+ f" {base_ref}:{index_path} already has image.tag {tag!r}; nothing to change.",
502
+ markup=False,
503
+ )
504
+ return PullRequestResult(
505
+ branch=branch_name(env, tag), url=None, created=False, changed=False
506
+ )
507
+ verb = "[dry-run] would set" if dry_run else "Setting"
508
+ console.print(
509
+ f" {verb} image.tag {old!r} -> {tag!r} in {index_path} (from {base_ref}; "
510
+ "the working tree is left unchanged).",
511
+ style="cyan" if dry_run else None,
512
+ markup=False,
513
+ )
514
+
515
+ branch = branch_name(env, tag)
516
+ image = f"{image_repository}:{tag}" if image_repository else tag
517
+ title = f"deploy({env}): {project_name} -> {tag}"
518
+ body = (
519
+ f"Set `image.tag` to `{tag}` in `{index_path}`.\n\n"
520
+ f"Image: `{image}`\n\n"
521
+ "Opened by `graph-agents-cli deploy`. Argo CD reconciles this environment from "
522
+ f"`{base}` once the pull request merges."
523
+ )
524
+ _commit_single_file(
525
+ index_path=index_path,
526
+ content=new_text,
527
+ branch=branch,
528
+ base_ref=base_ref,
529
+ message=title,
530
+ dry_run=dry_run,
531
+ console=console,
532
+ )
533
+
534
+ if _kube.tool_available("gh"):
535
+ url, created = _pr_via_gh(
536
+ remote=remote,
537
+ branch=branch,
538
+ base=base,
539
+ title=title,
540
+ body=body,
541
+ dry_run=dry_run,
542
+ console=console,
543
+ )
544
+ else:
545
+ token = github_token()
546
+ if not token:
547
+ raise ConfigError(
548
+ "Neither the gh CLI nor a GITHUB_TOKEN is available to open the pull request.\n"
549
+ f" The branch {branch} was pushed; install gh (https://cli.github.com/) or export "
550
+ "GITHUB_TOKEN and re-run, or open the pull request by hand."
551
+ )
552
+ url, created = _pr_via_rest(
553
+ remote=remote,
554
+ token=token,
555
+ branch=branch,
556
+ base=base,
557
+ title=title,
558
+ body=body,
559
+ dry_run=dry_run,
560
+ console=console,
561
+ )
562
+ return PullRequestResult(branch=branch, url=url, created=created, changed=changed)