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,216 @@
|
|
|
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
|
+
"""Line-based three-way text merge (the diff3 algorithm) for config files.
|
|
16
|
+
|
|
17
|
+
``scaffold enhance`` uses it on the config files a runtime, provider, target or
|
|
18
|
+
CD change re-renders (``values-*.yaml``, ``.env.example``, Argo CD
|
|
19
|
+
Applications): the template's own change (old snapshot -> new snapshot) is
|
|
20
|
+
applied to the developer's copy when the two sets of edits touch different
|
|
21
|
+
lines. Overlapping edits are never resolved automatically: ``merge3`` returns
|
|
22
|
+
``None`` and the caller leaves the file alone and tells the developer.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import difflib
|
|
28
|
+
import re
|
|
29
|
+
from collections.abc import Callable, Sequence
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
import yaml
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _sync_regions(
|
|
36
|
+
base: Sequence[str], ours: Sequence[str], theirs: Sequence[str]
|
|
37
|
+
) -> list[tuple[int, int, int, int, int, int]]:
|
|
38
|
+
"""Base ranges left unchanged by both sides, with their positions in each side.
|
|
39
|
+
|
|
40
|
+
Each tuple is ``(base_start, base_end, ours_start, ours_end, theirs_start,
|
|
41
|
+
theirs_end)``; the list ends with an empty sentinel region at the end of
|
|
42
|
+
every sequence.
|
|
43
|
+
"""
|
|
44
|
+
ours_blocks = difflib.SequenceMatcher(None, base, ours, autojunk=False).get_matching_blocks()
|
|
45
|
+
theirs_blocks = difflib.SequenceMatcher(
|
|
46
|
+
None, base, theirs, autojunk=False
|
|
47
|
+
).get_matching_blocks()
|
|
48
|
+
regions: list[tuple[int, int, int, int, int, int]] = []
|
|
49
|
+
i = j = 0
|
|
50
|
+
while i < len(ours_blocks) and j < len(theirs_blocks):
|
|
51
|
+
o_base, o_start, o_len = ours_blocks[i]
|
|
52
|
+
t_base, t_start, t_len = theirs_blocks[j]
|
|
53
|
+
start = max(o_base, t_base)
|
|
54
|
+
end = min(o_base + o_len, t_base + t_len)
|
|
55
|
+
if start < end:
|
|
56
|
+
o_sub = o_start + (start - o_base)
|
|
57
|
+
t_sub = t_start + (start - t_base)
|
|
58
|
+
regions.append((start, end, o_sub, o_sub + end - start, t_sub, t_sub + end - start))
|
|
59
|
+
if o_base + o_len < t_base + t_len:
|
|
60
|
+
i += 1
|
|
61
|
+
else:
|
|
62
|
+
j += 1
|
|
63
|
+
regions.append((len(base), len(base), len(ours), len(ours), len(theirs), len(theirs)))
|
|
64
|
+
return regions
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def merge3(base: Sequence[str], ours: Sequence[str], theirs: Sequence[str]) -> list[str] | None:
|
|
68
|
+
"""Apply the base->theirs changes to ``ours``; ``None`` when the edits overlap.
|
|
69
|
+
|
|
70
|
+
Between two regions both sides left alone, a stretch changed on one side
|
|
71
|
+
only takes that side, a stretch changed identically on both takes either,
|
|
72
|
+
and a stretch changed differently on both is a conflict.
|
|
73
|
+
"""
|
|
74
|
+
merged: list[str] = []
|
|
75
|
+
b = o = t = 0
|
|
76
|
+
for b_start, b_end, o_start, o_end, t_start, t_end in _sync_regions(base, ours, theirs):
|
|
77
|
+
base_chunk = list(base[b:b_start])
|
|
78
|
+
ours_chunk = list(ours[o:o_start])
|
|
79
|
+
theirs_chunk = list(theirs[t:t_start])
|
|
80
|
+
if ours_chunk == theirs_chunk:
|
|
81
|
+
merged.extend(ours_chunk)
|
|
82
|
+
elif ours_chunk == base_chunk:
|
|
83
|
+
merged.extend(theirs_chunk)
|
|
84
|
+
elif theirs_chunk == base_chunk:
|
|
85
|
+
merged.extend(ours_chunk)
|
|
86
|
+
else:
|
|
87
|
+
return None
|
|
88
|
+
merged.extend(base[b_start:b_end])
|
|
89
|
+
b, o, t = b_end, o_end, t_end
|
|
90
|
+
return merged
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def merge3_text(base: str, ours: str, theirs: str) -> str | None:
|
|
94
|
+
""":func:`merge3` over the lines of three texts (line endings kept as they are)."""
|
|
95
|
+
result = merge3(
|
|
96
|
+
base.splitlines(keepends=True),
|
|
97
|
+
ours.splitlines(keepends=True),
|
|
98
|
+
theirs.splitlines(keepends=True),
|
|
99
|
+
)
|
|
100
|
+
return None if result is None else "".join(result)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class _Conflict(Exception):
|
|
104
|
+
"""Both sides changed the same value differently."""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_MISSING = object()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _merge_values(base: Any, ours: Any, theirs: Any) -> Any:
|
|
111
|
+
"""Three-way merge of parsed documents (mappings merged key by key)."""
|
|
112
|
+
if ours == theirs:
|
|
113
|
+
return ours
|
|
114
|
+
if ours == base:
|
|
115
|
+
return theirs
|
|
116
|
+
if theirs == base:
|
|
117
|
+
return ours
|
|
118
|
+
if all(isinstance(v, dict) for v in (ours, theirs)) and isinstance(base, dict | type(None)):
|
|
119
|
+
base = base or {}
|
|
120
|
+
merged: dict[Any, Any] = {}
|
|
121
|
+
for key in [*ours, *(k for k in theirs if k not in ours)]:
|
|
122
|
+
value = _merge_values(
|
|
123
|
+
base.get(key, _MISSING), ours.get(key, _MISSING), theirs.get(key, _MISSING)
|
|
124
|
+
)
|
|
125
|
+
if value is not _MISSING:
|
|
126
|
+
merged[key] = value
|
|
127
|
+
return merged
|
|
128
|
+
raise _Conflict
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class _StrictLoader(yaml.SafeLoader):
|
|
132
|
+
"""SafeLoader that refuses a repeated mapping key (a text merge can produce one)."""
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _construct_mapping(loader: _StrictLoader, node: yaml.MappingNode, deep: bool = False) -> dict:
|
|
136
|
+
loader.flatten_mapping(node)
|
|
137
|
+
seen: set[Any] = set()
|
|
138
|
+
for key_node, _value in node.value:
|
|
139
|
+
key = loader.construct_object(key_node, deep=deep)
|
|
140
|
+
if key in seen:
|
|
141
|
+
raise yaml.constructor.ConstructorError(
|
|
142
|
+
None, None, f"repeated key {key!r}", key_node.start_mark
|
|
143
|
+
)
|
|
144
|
+
seen.add(key)
|
|
145
|
+
return yaml.SafeLoader.construct_mapping(loader, node, deep=deep)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
_StrictLoader.add_constructor(
|
|
149
|
+
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
|
150
|
+
_construct_mapping, # type: ignore[arg-type]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
_ENV_ASSIGNMENT = re.compile(r"^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _parse_env(text: str) -> dict[str, str]:
|
|
157
|
+
"""The ``NAME=value`` assignments of an env file; a repeated name is an error."""
|
|
158
|
+
values: dict[str, str] = {}
|
|
159
|
+
for line in text.splitlines():
|
|
160
|
+
match = _ENV_ASSIGNMENT.match(line)
|
|
161
|
+
if not match:
|
|
162
|
+
continue
|
|
163
|
+
name, value = match.group(1), match.group(2).strip()
|
|
164
|
+
if name in values:
|
|
165
|
+
raise ValueError(f"repeated variable {name}")
|
|
166
|
+
values[name] = value
|
|
167
|
+
return values
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _parse_for(path: str) -> Callable[[str], Any] | None:
|
|
171
|
+
name = path.rsplit("/", 1)[-1]
|
|
172
|
+
if name.endswith((".yaml", ".yml")):
|
|
173
|
+
return lambda text: yaml.load(text, Loader=_StrictLoader)
|
|
174
|
+
if name.startswith(".env"):
|
|
175
|
+
return _parse_env
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def merge3_checked(base: str, ours: str, theirs: str, path: str) -> str | None:
|
|
180
|
+
""":func:`merge3_text` plus a check that the result means what both sides meant.
|
|
181
|
+
|
|
182
|
+
A line merge can succeed while producing something neither side wrote: two
|
|
183
|
+
insertions at almost the same place may land on either side of a shared
|
|
184
|
+
line (a ``redis:`` block added by the template next to one the developer
|
|
185
|
+
added gives two ``redis:`` keys). For YAML and env files the merged text is
|
|
186
|
+
parsed (repeated keys refused) and must equal a key-by-key three-way merge
|
|
187
|
+
of the three parsed documents; anything else is reported as a conflict.
|
|
188
|
+
"""
|
|
189
|
+
merged = merge3_text(base, ours, theirs)
|
|
190
|
+
if merged is None:
|
|
191
|
+
return None
|
|
192
|
+
parse = _parse_for(path)
|
|
193
|
+
if parse is None:
|
|
194
|
+
return merged
|
|
195
|
+
try:
|
|
196
|
+
expected = _merge_values(parse(base), parse(ours), parse(theirs))
|
|
197
|
+
if parse(merged) != expected:
|
|
198
|
+
return None
|
|
199
|
+
except (_Conflict, ValueError, yaml.YAMLError):
|
|
200
|
+
return None
|
|
201
|
+
return merged
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def template_diff(old: str, new: str, path: str, *, limit: int = 40) -> str:
|
|
205
|
+
"""The template's own change to ``path`` as a unified diff, for a manual merge."""
|
|
206
|
+
lines = list(
|
|
207
|
+
difflib.unified_diff(
|
|
208
|
+
old.splitlines(keepends=True),
|
|
209
|
+
new.splitlines(keepends=True),
|
|
210
|
+
fromfile=f"{path} (template before)",
|
|
211
|
+
tofile=f"{path} (template after)",
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
if len(lines) > limit:
|
|
215
|
+
lines = [*lines[:limit], f"... ({len(lines) - limit} more diff lines)\n"]
|
|
216
|
+
return "".join(line if line.endswith("\n") else line + "\n" for line in lines)
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
"""Carry the OpenAPI specs a seed ``--api-policy`` references into the new project.
|
|
16
|
+
|
|
17
|
+
``lint`` resolves a relative ``openapi:`` path against the project root, while
|
|
18
|
+
``create`` reads the seed policy next to the file the developer passed. Copying
|
|
19
|
+
only ``api-policy.yaml`` would leave every ``openapi:`` reference dangling, and
|
|
20
|
+
the first ``lint`` (the pr_checks gate) would fail on a project that was just
|
|
21
|
+
created. So each referenced spec is copied too:
|
|
22
|
+
|
|
23
|
+
* a relative path that stays inside the seed policy's directory keeps its path
|
|
24
|
+
(``specs/orders.yaml`` lands at ``<project>/specs/orders.yaml``);
|
|
25
|
+
* anything else (an absolute path, ``..``, a hidden directory, or a path the
|
|
26
|
+
template already uses) is copied to ``openapi/<api name>/<file name>`` and the
|
|
27
|
+
``openapi:`` value in the project's ``api-policy.yaml`` is rewritten in place,
|
|
28
|
+
keeping every comment and the rest of the file byte for byte.
|
|
29
|
+
|
|
30
|
+
A reference that does not name a readable file is a configuration error
|
|
31
|
+
(exit 3) raised before anything is rendered.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import json
|
|
37
|
+
import logging
|
|
38
|
+
import pathlib
|
|
39
|
+
import shutil
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
|
|
42
|
+
import yaml
|
|
43
|
+
|
|
44
|
+
from graph_agents_cli._api_policy import POLICY_FILENAME, ApiPolicyFileError
|
|
45
|
+
|
|
46
|
+
FALLBACK_DIR = "openapi"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class SpecCopy:
|
|
51
|
+
"""One ``openapi:`` reference of the seed policy and where it goes in the project."""
|
|
52
|
+
|
|
53
|
+
api: str
|
|
54
|
+
reference: str
|
|
55
|
+
source: pathlib.Path
|
|
56
|
+
# Project-relative POSIX path the reference keeps, or None when it cannot
|
|
57
|
+
# be kept (the spec then goes under openapi/<api>/ and the value is rewritten).
|
|
58
|
+
kept_path: str | None
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def fallback_path(self) -> str:
|
|
62
|
+
return f"{FALLBACK_DIR}/{self.api}/{self.source.name}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def plan_spec_copies(document: dict, policy_path: str | pathlib.Path) -> list[SpecCopy]:
|
|
66
|
+
"""The specs to copy for ``document`` (read from ``policy_path``); exit 3 when one is missing."""
|
|
67
|
+
policy_file = pathlib.Path(policy_path)
|
|
68
|
+
policy_dir = policy_file.resolve().parent
|
|
69
|
+
copies: list[SpecCopy] = []
|
|
70
|
+
errors: list[str] = []
|
|
71
|
+
for name, api in (document.get("apis") or {}).items():
|
|
72
|
+
reference = api.get("openapi") if isinstance(api, dict) else None
|
|
73
|
+
if not reference:
|
|
74
|
+
continue
|
|
75
|
+
raw = pathlib.Path(str(reference).strip())
|
|
76
|
+
source = raw if raw.is_absolute() else policy_dir / raw
|
|
77
|
+
if not source.is_file():
|
|
78
|
+
errors.append(
|
|
79
|
+
f"apis.{name}.openapi: {reference} is not a readable file (a relative path "
|
|
80
|
+
f"is read next to {policy_file.name}); the new project needs a local copy "
|
|
81
|
+
"for `lint` to check calls against it"
|
|
82
|
+
)
|
|
83
|
+
continue
|
|
84
|
+
copies.append(
|
|
85
|
+
SpecCopy(
|
|
86
|
+
api=str(name),
|
|
87
|
+
reference=str(reference),
|
|
88
|
+
source=source.resolve(),
|
|
89
|
+
kept_path=_keepable(raw),
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
if errors:
|
|
93
|
+
raise ApiPolicyFileError(policy_file, errors)
|
|
94
|
+
return copies
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _keepable(raw: pathlib.Path) -> str | None:
|
|
98
|
+
"""The reference as a project path when it can stay as written, else None."""
|
|
99
|
+
if raw.is_absolute() or "\\" in str(raw):
|
|
100
|
+
return None
|
|
101
|
+
parts = raw.parts
|
|
102
|
+
if not parts or any(part in ("", ".", "..") or part.startswith(".") for part in parts):
|
|
103
|
+
return None
|
|
104
|
+
return pathlib.PurePosixPath(*parts).as_posix()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def install_spec_copies(project_dir: pathlib.Path, copies: list[SpecCopy]) -> list[str]:
|
|
108
|
+
"""Copy the planned specs into ``project_dir``; return one line per spec for the user.
|
|
109
|
+
|
|
110
|
+
Must run after the template is rendered and the seed policy copied to
|
|
111
|
+
``api-policy.yaml``: a kept path the template already uses is not
|
|
112
|
+
overwritten, the fallback location is used instead.
|
|
113
|
+
"""
|
|
114
|
+
lines: list[str] = []
|
|
115
|
+
policy_path = project_dir / POLICY_FILENAME
|
|
116
|
+
rewrites: dict[str, str] = {}
|
|
117
|
+
for copy in copies:
|
|
118
|
+
target = copy.kept_path
|
|
119
|
+
if target is None or _occupied(project_dir / target, copy.source):
|
|
120
|
+
target = copy.fallback_path
|
|
121
|
+
rewrites[copy.api] = target
|
|
122
|
+
destination = project_dir / target
|
|
123
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
shutil.copyfile(copy.source, destination)
|
|
125
|
+
lines.append(f"{copy.api}: {copy.reference} -> {target}")
|
|
126
|
+
if rewrites and not _rewrite_openapi_values(policy_path, rewrites):
|
|
127
|
+
for api, target in rewrites.items():
|
|
128
|
+
lines.append(
|
|
129
|
+
f"{api}: could not rewrite openapi: in {POLICY_FILENAME}; set it to {target}"
|
|
130
|
+
)
|
|
131
|
+
return lines
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _occupied(path: pathlib.Path, source: pathlib.Path) -> bool:
|
|
135
|
+
"""True when ``path`` holds something other than a copy of ``source``."""
|
|
136
|
+
if not path.exists() and not path.is_symlink():
|
|
137
|
+
return False
|
|
138
|
+
try:
|
|
139
|
+
return not path.is_file() or path.read_bytes() != source.read_bytes()
|
|
140
|
+
except OSError:
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _rewrite_openapi_values(policy_path: pathlib.Path, rewrites: dict[str, str]) -> bool:
|
|
145
|
+
"""Replace ``apis.<api>.openapi`` scalars in place; True when the result parses as intended.
|
|
146
|
+
|
|
147
|
+
The file is edited at the positions YAML reports for each value, so
|
|
148
|
+
comments and formatting survive. The result is re-read and compared: on
|
|
149
|
+
any mismatch (an alias shared by several APIs, say) the file is restored.
|
|
150
|
+
"""
|
|
151
|
+
original = policy_path.read_text(encoding="utf-8")
|
|
152
|
+
try:
|
|
153
|
+
root = yaml.compose(original, Loader=yaml.SafeLoader)
|
|
154
|
+
except yaml.YAMLError as exc:
|
|
155
|
+
logging.warning("Could not parse %s to rewrite openapi paths: %s", policy_path, exc)
|
|
156
|
+
return False
|
|
157
|
+
spans: list[tuple[int, int, str]] = []
|
|
158
|
+
for api, target in rewrites.items():
|
|
159
|
+
node = _openapi_node(root, api)
|
|
160
|
+
if node is None or node.start_mark is None or node.end_mark is None:
|
|
161
|
+
return False
|
|
162
|
+
spans.append((node.start_mark.index, node.end_mark.index, json.dumps(target)))
|
|
163
|
+
text = original
|
|
164
|
+
for start, end, value in sorted(spans, reverse=True):
|
|
165
|
+
text = text[:start] + value + text[end:]
|
|
166
|
+
try:
|
|
167
|
+
parsed = yaml.safe_load(text)
|
|
168
|
+
ok = all(parsed["apis"][api]["openapi"] == target for api, target in rewrites.items())
|
|
169
|
+
ok = ok and _without_openapi(parsed) == _without_openapi(yaml.safe_load(original))
|
|
170
|
+
except (yaml.YAMLError, KeyError, TypeError):
|
|
171
|
+
ok = False
|
|
172
|
+
if not ok:
|
|
173
|
+
return False
|
|
174
|
+
policy_path.write_text(text, encoding="utf-8")
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _openapi_node(root: yaml.Node | None, api: str) -> yaml.ScalarNode | None:
|
|
179
|
+
apis = _mapping_value(root, "apis")
|
|
180
|
+
entry = _mapping_value(apis, api)
|
|
181
|
+
value = _mapping_value(entry, "openapi")
|
|
182
|
+
return value if isinstance(value, yaml.ScalarNode) else None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _mapping_value(node: yaml.Node | None, key: str) -> yaml.Node | None:
|
|
186
|
+
if not isinstance(node, yaml.MappingNode):
|
|
187
|
+
return None
|
|
188
|
+
for key_node, value_node in node.value:
|
|
189
|
+
if isinstance(key_node, yaml.ScalarNode) and key_node.value == key:
|
|
190
|
+
return value_node
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _without_openapi(document: dict) -> dict:
|
|
195
|
+
apis = {
|
|
196
|
+
name: {k: v for k, v in (api or {}).items() if k != "openapi"}
|
|
197
|
+
for name, api in (document.get("apis") or {}).items()
|
|
198
|
+
}
|
|
199
|
+
return {**document, "apis": apis}
|