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,513 @@
|
|
|
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 auth: local credentials for a project's auth policy.
|
|
16
|
+
|
|
17
|
+
``auth dev-token`` lets a ``jwt`` project run locally without an identity
|
|
18
|
+
provider. It keeps an RSA key pair under ``.graph-agents-cli/dev-jwt/`` (git
|
|
19
|
+
ignored; the private key is mode 0600), puts the public key and a dev issuer
|
|
20
|
+
and audience in ``.env`` (only where ``.env`` leaves them blank), and prints a
|
|
21
|
+
token signed with the private key for the principal and roles asked for (and, with
|
|
22
|
+
``--act``, the agents presenting it for that principal: the RFC 8693 ``act``
|
|
23
|
+
claim, to try agents calling agents locally).
|
|
24
|
+
``run`` and ``eval`` send it as the bearer when it is in
|
|
25
|
+
``GRAPH_AGENTS_CLI_API_KEY``, which keeps it out of argv and shell history.
|
|
26
|
+
|
|
27
|
+
It refuses anything but a local dev setup: the project's policy must be
|
|
28
|
+
``jwt``, the local server's ``APP_ENV`` exactly ``dev``, and ``.env`` must not
|
|
29
|
+
point at a real issuer (``AUTH_JWT_JWKS_URL``) or hold another public key.
|
|
30
|
+
Deployed environments never see the dev key: ``deploy`` and ``secrets`` read
|
|
31
|
+
the chart values and ``.env.<env>``, and ``AUTH_JWT_PUBLIC_KEY`` is not a
|
|
32
|
+
secret key. Signing runs in the project's environment (``uv run``), whose
|
|
33
|
+
``pyjwt[crypto]`` is the library the server verifies with, so the CLI itself
|
|
34
|
+
needs no crypto dependency.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import json
|
|
40
|
+
import os
|
|
41
|
+
import re
|
|
42
|
+
import shlex
|
|
43
|
+
import subprocess
|
|
44
|
+
import time
|
|
45
|
+
import uuid
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
from typing import Any
|
|
48
|
+
|
|
49
|
+
import click
|
|
50
|
+
|
|
51
|
+
from graph_agents_cli._click import LazyGroup
|
|
52
|
+
from graph_agents_cli._defaults import normalize_auth_policy
|
|
53
|
+
from graph_agents_cli._project import find_project_root, read_project_config
|
|
54
|
+
from graph_agents_cli._remote import API_KEY_ENV
|
|
55
|
+
|
|
56
|
+
EXIT_TOOL_FAILURE = 2
|
|
57
|
+
EXIT_CONFIG_ERROR = 3
|
|
58
|
+
|
|
59
|
+
DEV_KEY_DIR = Path(".graph-agents-cli") / "dev-jwt"
|
|
60
|
+
PRIVATE_KEY_FILE = "private-key.pem"
|
|
61
|
+
PUBLIC_KEY_FILE = "public-key.pem"
|
|
62
|
+
DEV_ISSUER = "graph-agents-cli-dev"
|
|
63
|
+
DEV_KEY_ID = "graph-agents-cli-dev"
|
|
64
|
+
ALGORITHM = "RS256"
|
|
65
|
+
DEFAULT_TTL = "12h"
|
|
66
|
+
MAX_TTL_S = 7 * 24 * 3600
|
|
67
|
+
SIGN_TIMEOUT_S = 300
|
|
68
|
+
|
|
69
|
+
_TTL = re.compile(r"^\s*(\d+)\s*([smhd]?)\s*$")
|
|
70
|
+
_TTL_UNITS = {"": 1, "s": 1, "m": 60, "h": 3600, "d": 86400}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class DevTokenError(click.ClickException):
|
|
74
|
+
"""A setup this command will not sign for (exit 3)."""
|
|
75
|
+
|
|
76
|
+
exit_code = EXIT_CONFIG_ERROR
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class SigningError(click.ClickException):
|
|
80
|
+
"""The project's environment could not create the key or sign (exit 2)."""
|
|
81
|
+
|
|
82
|
+
exit_code = EXIT_TOOL_FAILURE
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# The part that runs in the project's environment (`uv run python -c`): it
|
|
86
|
+
# reads {"key_path", "claims", "kid", "algorithm"} on stdin, creates the private
|
|
87
|
+
# key when absent (0600, never overwritten), and prints {"token", "public_key"}
|
|
88
|
+
# or {"error", "detail"}.
|
|
89
|
+
_SIGNER = r"""
|
|
90
|
+
import json, os, sys
|
|
91
|
+
payload = json.load(sys.stdin)
|
|
92
|
+
try:
|
|
93
|
+
import jwt
|
|
94
|
+
from cryptography.hazmat.primitives import serialization
|
|
95
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
96
|
+
except ImportError as exc:
|
|
97
|
+
print(json.dumps({"error": "missing", "detail": str(exc)}))
|
|
98
|
+
sys.exit(0)
|
|
99
|
+
try:
|
|
100
|
+
path = payload["key_path"]
|
|
101
|
+
if os.path.exists(path):
|
|
102
|
+
with open(path, "rb") as handle:
|
|
103
|
+
key = serialization.load_pem_private_key(handle.read(), password=None)
|
|
104
|
+
if not isinstance(key, rsa.RSAPrivateKey):
|
|
105
|
+
raise ValueError("not an RSA private key")
|
|
106
|
+
else:
|
|
107
|
+
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
108
|
+
pem = key.private_bytes(
|
|
109
|
+
serialization.Encoding.PEM,
|
|
110
|
+
serialization.PrivateFormat.PKCS8,
|
|
111
|
+
serialization.NoEncryption(),
|
|
112
|
+
)
|
|
113
|
+
# Written whole under a temporary name, then linked into place: the key
|
|
114
|
+
# appears complete or not at all, and never replaces one that exists.
|
|
115
|
+
tmp = f"{path}.{os.getpid()}.tmp"
|
|
116
|
+
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
117
|
+
try:
|
|
118
|
+
with os.fdopen(fd, "wb") as handle:
|
|
119
|
+
handle.write(pem)
|
|
120
|
+
os.link(tmp, path)
|
|
121
|
+
except FileExistsError:
|
|
122
|
+
# Another run created the key first: sign with that one.
|
|
123
|
+
with open(path, "rb") as handle:
|
|
124
|
+
key = serialization.load_pem_private_key(handle.read(), password=None)
|
|
125
|
+
finally:
|
|
126
|
+
os.unlink(tmp)
|
|
127
|
+
public = key.public_key().public_bytes(
|
|
128
|
+
serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo
|
|
129
|
+
).decode()
|
|
130
|
+
token = jwt.encode(
|
|
131
|
+
payload["claims"], key, algorithm=payload["algorithm"], headers={"kid": payload["kid"]}
|
|
132
|
+
)
|
|
133
|
+
print(json.dumps({"token": token, "public_key": public}))
|
|
134
|
+
except Exception as exc:
|
|
135
|
+
print(json.dumps({"error": "failed", "detail": f"{type(exc).__name__}: {exc}"}))
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def parse_ttl(raw: str) -> int:
|
|
140
|
+
"""``3600``, ``90s``, ``30m``, ``12h`` or ``2d`` -> seconds (1 s to 7 days)."""
|
|
141
|
+
match = _TTL.match(raw or "")
|
|
142
|
+
if not match:
|
|
143
|
+
raise click.BadParameter(f"{raw!r}: use seconds or a number with s, m, h or d (e.g. 12h)")
|
|
144
|
+
seconds = int(match.group(1)) * _TTL_UNITS[match.group(2)]
|
|
145
|
+
if not 1 <= seconds <= MAX_TTL_S:
|
|
146
|
+
raise click.BadParameter(f"{raw!r}: between 1 second and 7 days")
|
|
147
|
+
return seconds
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _csv(raw: str | None) -> list[str]:
|
|
151
|
+
return [part.strip() for part in (raw or "").split(",") if part.strip()]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _set_claim(claims: dict[str, Any], path: str, value: Any) -> None:
|
|
155
|
+
"""Put ``value`` at a claim name, or a dotted path (``realm_access.roles``) as the server reads it."""
|
|
156
|
+
if "." not in path:
|
|
157
|
+
claims[path] = value
|
|
158
|
+
return
|
|
159
|
+
*parents, leaf = path.split(".")
|
|
160
|
+
node = claims
|
|
161
|
+
for part in parents:
|
|
162
|
+
child = node.get(part)
|
|
163
|
+
if not isinstance(child, dict):
|
|
164
|
+
child = {}
|
|
165
|
+
node[part] = child
|
|
166
|
+
node = child
|
|
167
|
+
node[leaf] = value
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def actor_claim(actors: list[str]) -> dict[str, Any] | None:
|
|
171
|
+
"""The RFC 8693 ``act`` claim for ``actors`` (current actor first): each earlier actor
|
|
172
|
+
nested in the ``act`` of the one after it. None without actors."""
|
|
173
|
+
claim: dict[str, Any] | None = None
|
|
174
|
+
for actor in reversed(actors):
|
|
175
|
+
claim = {"sub": actor} if claim is None else {"sub": actor, "act": claim}
|
|
176
|
+
return claim
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def build_claims(
|
|
180
|
+
*,
|
|
181
|
+
sub: str,
|
|
182
|
+
roles: list[str],
|
|
183
|
+
issuer: str,
|
|
184
|
+
audience: str,
|
|
185
|
+
ttl_s: int,
|
|
186
|
+
principal_claim: str = "sub",
|
|
187
|
+
roles_claim: str = "roles",
|
|
188
|
+
now: int | None = None,
|
|
189
|
+
actors: list[str] | None = None,
|
|
190
|
+
azp: str | None = None,
|
|
191
|
+
actor_claim_name: str = "act",
|
|
192
|
+
) -> dict[str, Any]:
|
|
193
|
+
"""The token's claims: principal and roles where the server reads them, plus iss/aud/exp.
|
|
194
|
+
|
|
195
|
+
``actors`` (current first) become the ``act`` claim (``actor_claim_name``,
|
|
196
|
+
``AUTH_JWT_ACTOR_CLAIM``) and ``azp`` the authorized party.
|
|
197
|
+
"""
|
|
198
|
+
issued = int(time.time()) if now is None else now
|
|
199
|
+
claims: dict[str, Any] = {
|
|
200
|
+
"iss": issuer,
|
|
201
|
+
"aud": audience,
|
|
202
|
+
"iat": issued,
|
|
203
|
+
"nbf": issued,
|
|
204
|
+
"exp": issued + ttl_s,
|
|
205
|
+
"jti": uuid.uuid4().hex,
|
|
206
|
+
}
|
|
207
|
+
_set_claim(claims, principal_claim or "sub", sub)
|
|
208
|
+
_set_claim(claims, roles_claim or "roles", roles)
|
|
209
|
+
act = actor_claim(actors or [])
|
|
210
|
+
if act is not None:
|
|
211
|
+
_set_claim(claims, actor_claim_name or "act", act)
|
|
212
|
+
if azp:
|
|
213
|
+
claims["azp"] = azp
|
|
214
|
+
return claims
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _normalised_pem(value: str) -> str:
|
|
218
|
+
"""A PEM as the server reads it (``\\n`` escapes accepted), without whitespace."""
|
|
219
|
+
return "".join(value.replace("\\n", "\n").split())
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def env_line_value(pem: str) -> str:
|
|
223
|
+
"""``AUTH_JWT_PUBLIC_KEY`` as one ``.env`` line: single-quoted, newlines as ``\\n``."""
|
|
224
|
+
return "'" + pem.strip().replace("\n", "\\n") + "'"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _refuse(message: str) -> None:
|
|
228
|
+
raise DevTokenError(message)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _check_setup(env: Any, *, manifest_policy: str) -> None:
|
|
232
|
+
"""Refuse anything but a local jwt dev setup (``env`` is the login EnvView)."""
|
|
233
|
+
policy = normalize_auth_policy(env.get("AUTH_POLICY").strip() or manifest_policy, warn=False)
|
|
234
|
+
if policy != "jwt":
|
|
235
|
+
local = {
|
|
236
|
+
"shared-bearer": "Local runs of a shared-bearer project send the API_KEY in .env.",
|
|
237
|
+
"custom": (
|
|
238
|
+
"A custom policy reads what its CustomPolicy checks: pass it with "
|
|
239
|
+
"--header 'Name: value' or --cookie name=value."
|
|
240
|
+
),
|
|
241
|
+
}.get(policy, "")
|
|
242
|
+
_refuse(
|
|
243
|
+
f"This project's auth policy is {policy} (AUTH_POLICY): dev tokens are for jwt "
|
|
244
|
+
f"projects. {local}".rstrip()
|
|
245
|
+
)
|
|
246
|
+
app_env = env.get("APP_ENV")
|
|
247
|
+
if app_env != "dev":
|
|
248
|
+
shown = repr(app_env) if env.has("APP_ENV") else "unset"
|
|
249
|
+
_refuse(
|
|
250
|
+
f"APP_ENV is {shown}; dev tokens are only for a local server under APP_ENV=dev "
|
|
251
|
+
"(set it in .env, as .env.example does). Deployed environments take tokens from "
|
|
252
|
+
"your identity provider."
|
|
253
|
+
)
|
|
254
|
+
if env.has("AUTH_JWT_JWKS_URL"):
|
|
255
|
+
_refuse(
|
|
256
|
+
f"AUTH_JWT_JWKS_URL is set ({env.where('AUTH_JWT_JWKS_URL')}): the local server "
|
|
257
|
+
"verifies tokens from that issuer, not from a dev key. Get a token from the issuer, "
|
|
258
|
+
"or clear AUTH_JWT_JWKS_URL to use a dev key."
|
|
259
|
+
)
|
|
260
|
+
algorithms = [a.upper() for a in _csv(env.get("AUTH_JWT_ALGORITHMS"))]
|
|
261
|
+
if algorithms and ALGORITHM not in algorithms:
|
|
262
|
+
_refuse(
|
|
263
|
+
f"AUTH_JWT_ALGORITHMS ({', '.join(algorithms)}) does not allow {ALGORITHM}, "
|
|
264
|
+
"which the dev key signs with."
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _sign(root: Path, key_path: Path, claims: dict[str, Any]) -> dict[str, str]:
|
|
269
|
+
"""Create the key when absent and sign ``claims`` in the project's environment."""
|
|
270
|
+
from graph_agents_cli._runner import run_resolved
|
|
271
|
+
from graph_agents_cli._tools import ToolNotFoundError
|
|
272
|
+
|
|
273
|
+
payload = {"key_path": str(key_path), "claims": claims, "kid": DEV_KEY_ID}
|
|
274
|
+
payload["algorithm"] = ALGORITHM
|
|
275
|
+
env = dict(os.environ)
|
|
276
|
+
env.pop("VIRTUAL_ENV", None) # the project's own .venv, not the CLI's
|
|
277
|
+
try:
|
|
278
|
+
result = run_resolved(
|
|
279
|
+
["uv", "run", "--quiet", "python", "-c", _SIGNER],
|
|
280
|
+
cwd=str(root),
|
|
281
|
+
input=json.dumps(payload),
|
|
282
|
+
capture_output=True,
|
|
283
|
+
text=True,
|
|
284
|
+
encoding="utf-8",
|
|
285
|
+
errors="replace",
|
|
286
|
+
timeout=SIGN_TIMEOUT_S,
|
|
287
|
+
env=env,
|
|
288
|
+
)
|
|
289
|
+
except ToolNotFoundError as exc:
|
|
290
|
+
raise SigningError(f"{exc}") from exc
|
|
291
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
292
|
+
raise SigningError(f"Could not run the project's Python (uv run): {exc}") from exc
|
|
293
|
+
lines = [line for line in (result.stdout or "").splitlines() if line.strip()]
|
|
294
|
+
try:
|
|
295
|
+
answer = json.loads(lines[-1]) if lines else {}
|
|
296
|
+
except json.JSONDecodeError:
|
|
297
|
+
answer = {}
|
|
298
|
+
if result.returncode != 0 or not isinstance(answer, dict) or not answer:
|
|
299
|
+
detail = (result.stderr or result.stdout or "").strip()[-800:]
|
|
300
|
+
raise SigningError(
|
|
301
|
+
"The project's environment could not sign the token (uv run exited "
|
|
302
|
+
f"{result.returncode}).\n {detail}\n Run `graph-agents-cli install` first."
|
|
303
|
+
)
|
|
304
|
+
if answer.get("error") == "missing":
|
|
305
|
+
raise SigningError(
|
|
306
|
+
f"The project's environment has no PyJWT with crypto support ({answer.get('detail')}). "
|
|
307
|
+
"Run `graph-agents-cli install` (the template depends on pyjwt[crypto])."
|
|
308
|
+
)
|
|
309
|
+
if answer.get("error"):
|
|
310
|
+
raise SigningError(
|
|
311
|
+
f"Could not sign with {key_path}: {answer.get('detail')}. If that file is damaged "
|
|
312
|
+
"(not an RSA private key), delete it to create a new dev key, then clear "
|
|
313
|
+
"AUTH_JWT_PUBLIC_KEY in .env and rerun; tokens signed with the old key stop working."
|
|
314
|
+
)
|
|
315
|
+
return {"token": str(answer["token"]), "public_key": str(answer["public_key"])}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _private_dir(path: Path) -> None:
|
|
319
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
320
|
+
if os.name != "nt":
|
|
321
|
+
path.chmod(0o700)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@click.group("auth", cls=LazyGroup)
|
|
325
|
+
def auth_group() -> None:
|
|
326
|
+
"""Local credentials for the project's auth policy (dev-only JWTs).
|
|
327
|
+
|
|
328
|
+
Nothing here touches a cluster or an identity provider.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@auth_group.command("dev-token")
|
|
333
|
+
@click.option(
|
|
334
|
+
"--sub",
|
|
335
|
+
required=True,
|
|
336
|
+
help="The principal id the token carries (in AUTH_JWT_PRINCIPAL_CLAIM, default sub).",
|
|
337
|
+
)
|
|
338
|
+
@click.option(
|
|
339
|
+
"--roles",
|
|
340
|
+
default="",
|
|
341
|
+
help="Comma list of roles (in AUTH_JWT_ROLES_CLAIM, default roles), e.g. user,support.",
|
|
342
|
+
)
|
|
343
|
+
@click.option(
|
|
344
|
+
"--ttl",
|
|
345
|
+
default=DEFAULT_TTL,
|
|
346
|
+
show_default=True,
|
|
347
|
+
help="Lifetime: seconds, or a number with s, m, h or d (at most 7d).",
|
|
348
|
+
)
|
|
349
|
+
@click.option(
|
|
350
|
+
"--act",
|
|
351
|
+
"actors",
|
|
352
|
+
multiple=True,
|
|
353
|
+
metavar="ID",
|
|
354
|
+
help=(
|
|
355
|
+
"An agent presenting the token for --sub (the RFC 8693 act claim); repeat for a chain, "
|
|
356
|
+
"the current agent first. The server refuses it unless AUTH_ALLOWED_ACTORS lists it."
|
|
357
|
+
),
|
|
358
|
+
)
|
|
359
|
+
@click.option(
|
|
360
|
+
"--azp",
|
|
361
|
+
default=None,
|
|
362
|
+
metavar="ID",
|
|
363
|
+
help="The token's authorized party (client id), as AUTH_JWT_DIRECT_CLIENTS reads it.",
|
|
364
|
+
)
|
|
365
|
+
def cmd_dev_token(sub: str, roles: str, ttl: str, actors: tuple[str, ...], azp: str | None) -> None:
|
|
366
|
+
"""Mint a JWT for local runs of a jwt project (APP_ENV=dev only).
|
|
367
|
+
|
|
368
|
+
Prints the token alone on stdout, so it can go straight into the variable
|
|
369
|
+
`run` and `eval` send as the bearer, without appearing in argv or your
|
|
370
|
+
shell history:
|
|
371
|
+
|
|
372
|
+
\b
|
|
373
|
+
export GRAPH_AGENTS_CLI_API_KEY="$(graph-agents-cli auth dev-token --sub alice --roles user)"
|
|
374
|
+
graph-agents-cli run "hello"
|
|
375
|
+
graph-agents-cli eval run
|
|
376
|
+
|
|
377
|
+
\b
|
|
378
|
+
An agent calling another agent for a user presents a token naming both:
|
|
379
|
+
graph-agents-cli auth dev-token --sub alice --act concierge
|
|
380
|
+
(--act billing --act concierge: billing, called by concierge, for alice).
|
|
381
|
+
|
|
382
|
+
\b
|
|
383
|
+
The first call creates a dev RSA key pair in .graph-agents-cli/dev-jwt/
|
|
384
|
+
(git ignored) and fills blank AUTH_JWT_PUBLIC_KEY, AUTH_JWT_ISSUER and
|
|
385
|
+
AUTH_JWT_AUDIENCE in .env (kept 0600); values .env already sets are used
|
|
386
|
+
as they are. Restart a running local server afterwards
|
|
387
|
+
(`graph-agents-cli run --stop-server`). Refused unless the project's
|
|
388
|
+
policy is jwt, APP_ENV is exactly dev, and .env names no JWKS URL and no
|
|
389
|
+
other public key. Never deploy the dev key.
|
|
390
|
+
|
|
391
|
+
\b
|
|
392
|
+
Exit codes:
|
|
393
|
+
0 token printed
|
|
394
|
+
2 the project's environment could not sign (run `graph-agents-cli install`)
|
|
395
|
+
3 not a jwt project under APP_ENV=dev, or another key or issuer is configured
|
|
396
|
+
"""
|
|
397
|
+
from graph_agents_cli.setup.cmd_auth import load_env, write_env
|
|
398
|
+
|
|
399
|
+
ttl_s = parse_ttl(ttl)
|
|
400
|
+
sub = sub.strip()
|
|
401
|
+
if not sub:
|
|
402
|
+
raise click.BadParameter("--sub must not be empty")
|
|
403
|
+
role_list = _csv(roles)
|
|
404
|
+
actor_list = [a.strip() for a in actors]
|
|
405
|
+
for actor in actor_list:
|
|
406
|
+
if not actor or len(actor) > 256 or any(c.isspace() or ord(c) < 32 for c in actor):
|
|
407
|
+
raise click.BadParameter(
|
|
408
|
+
f"{actor[:40]!r}: an agent id of 1-256 characters without whitespace",
|
|
409
|
+
param_hint="--act",
|
|
410
|
+
)
|
|
411
|
+
if azp is not None and (not azp.strip() or any(c.isspace() for c in azp.strip())):
|
|
412
|
+
raise click.BadParameter("a client id without whitespace", param_hint="--azp")
|
|
413
|
+
azp = azp.strip() if azp is not None else None
|
|
414
|
+
|
|
415
|
+
root = find_project_root(Path.cwd())
|
|
416
|
+
if root is None:
|
|
417
|
+
raise DevTokenError(
|
|
418
|
+
"Not inside a graph-agents-cli project (no graph-agents-cli-manifest.yaml here or "
|
|
419
|
+
"above)."
|
|
420
|
+
)
|
|
421
|
+
config = read_project_config(str(root))
|
|
422
|
+
env_file = root / ".env"
|
|
423
|
+
if not env_file.is_file():
|
|
424
|
+
raise DevTokenError(
|
|
425
|
+
f"No .env in {root}: the local server reads its settings from it. Create it with "
|
|
426
|
+
"`cp .env.example .env`, then rerun."
|
|
427
|
+
)
|
|
428
|
+
env = load_env(env_file)
|
|
429
|
+
_check_setup(env, manifest_policy=config.auth_policy)
|
|
430
|
+
|
|
431
|
+
key_dir = root / DEV_KEY_DIR
|
|
432
|
+
_private_dir(key_dir)
|
|
433
|
+
key_path = key_dir / PRIVATE_KEY_FILE
|
|
434
|
+
issuer = env.get("AUTH_JWT_ISSUER").strip() or DEV_ISSUER
|
|
435
|
+
audiences = _csv(env.get("AUTH_JWT_AUDIENCE"))
|
|
436
|
+
audience = audiences[0] if audiences else (config.project_name or root.name)
|
|
437
|
+
claims = build_claims(
|
|
438
|
+
sub=sub,
|
|
439
|
+
roles=role_list,
|
|
440
|
+
issuer=issuer,
|
|
441
|
+
audience=audience,
|
|
442
|
+
ttl_s=ttl_s,
|
|
443
|
+
principal_claim=env.get("AUTH_JWT_PRINCIPAL_CLAIM").strip() or "sub",
|
|
444
|
+
roles_claim=env.get("AUTH_JWT_ROLES_CLAIM").strip() or "roles",
|
|
445
|
+
actors=actor_list,
|
|
446
|
+
azp=azp,
|
|
447
|
+
# Where the server reads the actor (AUTH_JWT_ACTOR_CLAIM, `act` when unset or empty).
|
|
448
|
+
actor_claim_name=env.get("AUTH_JWT_ACTOR_CLAIM").strip() or "act",
|
|
449
|
+
)
|
|
450
|
+
signed = _sign(root, key_path, claims)
|
|
451
|
+
public_pem = signed["public_key"]
|
|
452
|
+
(key_dir / PUBLIC_KEY_FILE).write_text(public_pem, encoding="utf-8")
|
|
453
|
+
|
|
454
|
+
configured = env.get("AUTH_JWT_PUBLIC_KEY").strip()
|
|
455
|
+
if configured and _normalised_pem(configured) != _normalised_pem(public_pem):
|
|
456
|
+
where = env.where("AUTH_JWT_PUBLIC_KEY")
|
|
457
|
+
how = "unset it in your shell" if where == "environment" else f"clear it in {where}"
|
|
458
|
+
raise DevTokenError(
|
|
459
|
+
f"AUTH_JWT_PUBLIC_KEY ({where}) is not the dev key in {DEV_KEY_DIR}/, so the local "
|
|
460
|
+
f"server would refuse a dev token. To use the dev key, {how} and rerun."
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
wanted = {
|
|
464
|
+
"AUTH_JWT_PUBLIC_KEY": env_line_value(public_pem),
|
|
465
|
+
"AUTH_JWT_ISSUER": issuer,
|
|
466
|
+
"AUTH_JWT_AUDIENCE": audience,
|
|
467
|
+
}
|
|
468
|
+
written = [name for name in wanted if not env.has(name)]
|
|
469
|
+
write_env(env_file, {name: wanted[name] for name in written})
|
|
470
|
+
if not written and os.name != "nt" and env_file.exists():
|
|
471
|
+
env_file.chmod(0o600) # write_env keeps it 0600 when it writes
|
|
472
|
+
|
|
473
|
+
err = click.get_text_stream("stderr")
|
|
474
|
+
roles_text = ", ".join(role_list) if role_list else "none"
|
|
475
|
+
via = f", presented by {' <- '.join(actor_list)}" if actor_list else ""
|
|
476
|
+
click.echo(
|
|
477
|
+
f"Dev token for {sub} (roles: {roles_text}{via}), valid {ttl.strip()}, accepted only by "
|
|
478
|
+
"a server with this project's dev key.",
|
|
479
|
+
file=err,
|
|
480
|
+
)
|
|
481
|
+
actor_claim_off = "AUTH_JWT_ACTOR_CLAIM" in env.values and not env.has("AUTH_JWT_ACTOR_CLAIM")
|
|
482
|
+
if actor_list and actor_claim_off:
|
|
483
|
+
click.echo(
|
|
484
|
+
"AUTH_JWT_ACTOR_CLAIM is empty in .env: the server reads this token as the user's "
|
|
485
|
+
"own, not as an agent's.",
|
|
486
|
+
file=err,
|
|
487
|
+
)
|
|
488
|
+
if written:
|
|
489
|
+
click.echo(
|
|
490
|
+
f"Wrote {', '.join(written)} to {env_file} (the private key stays in "
|
|
491
|
+
f"{DEV_KEY_DIR / PRIVATE_KEY_FILE}).",
|
|
492
|
+
file=err,
|
|
493
|
+
)
|
|
494
|
+
from graph_agents_cli.run._local_server import get_server_port
|
|
495
|
+
|
|
496
|
+
if get_server_port(root) is not None:
|
|
497
|
+
click.echo(
|
|
498
|
+
"A local server is running with the old settings: restart it with "
|
|
499
|
+
"`graph-agents-cli run --stop-server`.",
|
|
500
|
+
file=err,
|
|
501
|
+
)
|
|
502
|
+
role_flag = f" --roles {shlex.quote(','.join(role_list))}" if role_list else ""
|
|
503
|
+
role_flag += "".join(f" --act {shlex.quote(a)}" for a in actor_list)
|
|
504
|
+
role_flag += f" --azp {shlex.quote(azp)}" if azp else ""
|
|
505
|
+
command = f"graph-agents-cli auth dev-token --sub {shlex.quote(sub)}{role_flag}"
|
|
506
|
+
click.echo(
|
|
507
|
+
"Keep the token out of argv and shell history: put it in the variable run and eval send,\n"
|
|
508
|
+
f' export {API_KEY_ENV}="$({command})"\n'
|
|
509
|
+
"Never deploy the dev key: deployed environments verify tokens from your identity "
|
|
510
|
+
"provider (AUTH_JWT_JWKS_URL in the chart values).",
|
|
511
|
+
file=err,
|
|
512
|
+
)
|
|
513
|
+
click.echo(signed["token"])
|