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