keystone-cli 0.2.1__tar.gz → 0.3.0__tar.gz
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.
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/PKG-INFO +4 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/pyproject.toml +10 -4
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/__main__.py +45 -0
- keystone_cli-0.3.0/src/keystone_cli/_jsonout.py +161 -0
- keystone_cli-0.3.0/src/keystone_cli/_tls.py +40 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/agent_logs.py +27 -11
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/auth/token_store.py +6 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/agent.py +824 -194
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/kb.py +9 -1
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/kb_metadata.py +6 -3
- keystone_cli-0.3.0/src/keystone_cli/commands/prompt.py +695 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/tool_credentials.py +9 -1
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/whoami.py +10 -3
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/workspace.py +20 -4
- keystone_cli-0.3.0/src/keystone_cli/fetch.py +337 -0
- keystone_cli-0.3.0/src/keystone_cli/manifest_ref.py +259 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/runner.py +18 -1
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/skills/keystone-debug/SKILL.md +21 -1
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/blank/README.md.tmpl +15 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/blank/agent.yaml.tmpl +5 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/blank/pyproject.toml.tmpl +23 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/blank/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/blank/tests/test_graph.py.tmpl +20 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/guardrails/README.md.tmpl +15 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/guardrails/agent.yaml.tmpl +0 -2
- keystone_cli-0.3.0/src/keystone_cli/templates/guardrails/pyproject.toml.tmpl +23 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/guardrails/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/guardrails/tests/test_graph.py.tmpl +100 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/hitl/README.md.tmpl +17 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/hitl/agent.yaml.tmpl +0 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/hitl/pkg/graph.py.tmpl +6 -2
- keystone_cli-0.3.0/src/keystone_cli/templates/hitl/pyproject.toml.tmpl +23 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/hitl/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/hitl/tests/test_graph.py.tmpl +84 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/kb-builder/README.md.tmpl +15 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/kb-builder/agent.yaml.tmpl +0 -2
- {keystone_cli-0.2.1/src/keystone_cli/templates/rag-qa → keystone_cli-0.3.0/src/keystone_cli/templates/kb-builder}/pyproject.toml.tmpl +11 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/kb-builder/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/kb-builder/tests/test_graph.py.tmpl +137 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/llm/README.md.tmpl +72 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm/agent.yaml.tmpl +20 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm/pkg/graph.py.tmpl +19 -1
- keystone_cli-0.3.0/src/keystone_cli/templates/llm/pyproject.toml.tmpl +23 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/llm/system-prompt.txt.tmpl +1 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/llm/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/llm/tests/test_graph.py.tmpl +36 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm-streaming/README.md.tmpl +43 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm-streaming/agent.yaml.tmpl +9 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm-streaming/pkg/graph.py.tmpl +18 -1
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm-streaming/pyproject.toml.tmpl +11 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/llm-streaming/system-prompt.txt.tmpl +1 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/llm-streaming/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/llm-streaming/tests/test_graph.py.tmpl +38 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory/README.md.tmpl +15 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/memory/pyproject.toml.tmpl +23 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/memory/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/memory/tests/test_graph.py.tmpl +41 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory-service/README.md.tmpl +15 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory-service/agent.yaml.tmpl +0 -2
- keystone_cli-0.3.0/src/keystone_cli/templates/memory-service/pyproject.toml.tmpl +23 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/memory-service/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/memory-service/tests/test_graph.py.tmpl +84 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/ocr/README.md.tmpl +32 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/ocr/agent.yaml.tmpl +5 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/ocr/pyproject.toml.tmpl +11 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/ocr/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/ocr/tests/test_graph.py.tmpl +117 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/rag-qa/README.md.tmpl +66 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/rag-qa/agent.yaml.tmpl +20 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/rag-qa/pkg/graph.py.tmpl +18 -1
- {keystone_cli-0.2.1/src/keystone_cli/templates/kb-builder → keystone_cli-0.3.0/src/keystone_cli/templates/rag-qa}/pyproject.toml.tmpl +11 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/rag-qa/system-prompt.txt.tmpl +1 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/rag-qa/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/rag-qa/tests/test_graph.py.tmpl +82 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/skill/README.md.tmpl +15 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/skill/agent.yaml.tmpl +0 -2
- keystone_cli-0.3.0/src/keystone_cli/templates/skill/pyproject.toml.tmpl +23 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/skill/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/skill/tests/test_graph.py.tmpl +61 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/tool-agent/README.md.tmpl +74 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/tool-agent/agent.yaml.tmpl +9 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/tool-agent/pkg/graph.py.tmpl +21 -1
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/tool-agent/pyproject.toml.tmpl +11 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/tool-agent/system-prompt.txt.tmpl +1 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/tool-agent/tests/conftest.py.tmpl +25 -0
- keystone_cli-0.3.0/src/keystone_cli/templates/tool-agent/tests/test_graph.py.tmpl +35 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli.egg-info/PKG-INFO +4 -2
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli.egg-info/SOURCES.txt +42 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli.egg-info/requires.txt +3 -1
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_agent.py +26 -0
- keystone_cli-0.3.0/tests/test_agent_manifest_cmd.py +204 -0
- keystone_cli-0.3.0/tests/test_agent_test_cmd.py +121 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_deploy.py +192 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_doctor.py +81 -0
- keystone_cli-0.3.0/tests/test_fetch.py +696 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_help_quality.py +68 -1
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_init.py +17 -0
- keystone_cli-0.3.0/tests/test_json_output.py +450 -0
- keystone_cli-0.3.0/tests/test_jsonout.py +168 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_logs.py +28 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_offline.py +13 -0
- keystone_cli-0.3.0/tests/test_prompt_commands.py +402 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_runs.py +24 -0
- keystone_cli-0.3.0/tests/test_template_prompt_guard.py +62 -0
- keystone_cli-0.3.0/tests/test_template_unit_tests.py +66 -0
- keystone_cli-0.3.0/tests/test_tls.py +125 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_traces.py +60 -0
- keystone_cli-0.2.1/src/keystone_cli/templates/blank/pyproject.toml.tmpl +0 -12
- keystone_cli-0.2.1/src/keystone_cli/templates/guardrails/pyproject.toml.tmpl +0 -12
- keystone_cli-0.2.1/src/keystone_cli/templates/hitl/pyproject.toml.tmpl +0 -12
- keystone_cli-0.2.1/src/keystone_cli/templates/llm/README.md.tmpl +0 -29
- keystone_cli-0.2.1/src/keystone_cli/templates/llm/pyproject.toml.tmpl +0 -12
- keystone_cli-0.2.1/src/keystone_cli/templates/memory/pyproject.toml.tmpl +0 -12
- keystone_cli-0.2.1/src/keystone_cli/templates/memory-service/pyproject.toml.tmpl +0 -12
- keystone_cli-0.2.1/src/keystone_cli/templates/rag-qa/README.md.tmpl +0 -23
- keystone_cli-0.2.1/src/keystone_cli/templates/skill/pyproject.toml.tmpl +0 -12
- keystone_cli-0.2.1/src/keystone_cli/templates/tool-agent/README.md.tmpl +0 -31
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/setup.cfg +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/__init__.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/_build_env.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/_stdio.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/_version.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/agent_skills.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/auth/__init__.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/auth/device_flow.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/__init__.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/dev.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/login.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/commands/skills.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/devloop.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/platform_client.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/run_debug.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/scaffold.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/blank/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/blank/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/blank/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/blank/pkg/graph.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/guardrails/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/guardrails/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/guardrails/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/guardrails/pkg/graph.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/hitl/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/hitl/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/hitl/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/kb-builder/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/kb-builder/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/kb-builder/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/kb-builder/pkg/graph.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm-streaming/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm-streaming/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/llm-streaming/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory/agent.yaml.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory/pkg/graph.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory-service/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory-service/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory-service/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/memory-service/pkg/graph.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/ocr/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/ocr/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/ocr/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/ocr/pkg/graph.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/rag-qa/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/rag-qa/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/rag-qa/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/skill/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/skill/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/skill/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/skill/pkg/graph.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/tool-agent/env.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/tool-agent/gitignore.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli/templates/tool-agent/pkg/__init__.py.tmpl +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli.egg-info/dependency_links.txt +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli.egg-info/entry_points.txt +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/src/keystone_cli.egg-info/top_level.txt +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_assistant_provenance_and_flags.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_auth.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_build_progress.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_ci_token.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_config_env_mismatch.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_dev.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_encoding.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_hitl_approver_cli.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_hitl_decisions_cli.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_invoke.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_invoke_stream.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_kb_commands.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_kb_metadata_commands.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_login_command.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_platform_client.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_publish.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_retire_delete.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_run_debug.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_skills.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_status.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_stdio.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_threads.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_tool_credentials.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_version.py +0 -0
- {keystone_cli-0.2.1 → keystone_cli-0.3.0}/tests/test_workspace.py +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: keystone-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Keystone pro-code agent CLI — build, test, deploy and operate agents on the Keystone platform.
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
|
-
Requires-Dist: keystone-agent-sdk<0.14,>=0.13.
|
|
6
|
+
Requires-Dist: keystone-agent-sdk<0.14,>=0.13.10
|
|
7
7
|
Requires-Dist: keystone-request-context<0.4,>=0.3
|
|
8
8
|
Requires-Dist: typer<1,>=0.12
|
|
9
9
|
Requires-Dist: httpx<1,>=0.27
|
|
10
|
+
Requires-Dist: truststore<1,>=0.10
|
|
11
|
+
Requires-Dist: pytest<9,>=8
|
|
10
12
|
Provides-Extra: dev
|
|
11
13
|
Requires-Dist: pytest<9,>=8; extra == "dev"
|
|
12
14
|
Requires-Dist: pytest-cov<7,>=6; extra == "dev"
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "keystone-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Keystone pro-code agent CLI — build, test, deploy and operate agents on the Keystone platform."
|
|
9
9
|
requires-python = ">=3.12"
|
|
10
10
|
dependencies = [
|
|
11
11
|
# Pinned to a compatible range: on an index (Artifact Registry) an unpinned name resolves to
|
|
12
12
|
# ANY published version — the wheel's metadata is the only pin that travels with it.
|
|
13
|
-
"keystone-agent-sdk>=0.13.
|
|
13
|
+
"keystone-agent-sdk>=0.13.10,<0.14",
|
|
14
14
|
# Transitive (the SDK's building-block clients) + used directly by `run` to set a local
|
|
15
15
|
# caller-identity ContextVar and to patch the clients for --offline. uv sources are NOT
|
|
16
16
|
# transitive, so declare it directly (cf. services/llm-gateway).
|
|
@@ -18,6 +18,12 @@ dependencies = [
|
|
|
18
18
|
"typer>=0.12,<1",
|
|
19
19
|
# `login` (device-flow → Keycloak) + `deploy` (bundle upload → agent-hub via Kong). FDP-3170/3169.
|
|
20
20
|
"httpx>=0.27,<1",
|
|
21
|
+
# FDP-6328 — verify TLS through the OS trust store (keystone_cli/_tls.py), so a corporate CA in
|
|
22
|
+
# the macOS Keychain / Windows store works without SSL_CERT_FILE. Pure Python, no dependencies.
|
|
23
|
+
"truststore>=0.10,<1",
|
|
24
|
+
# FDP-6569 — `keystone agent test` runs the project's unit tests in this environment (the one
|
|
25
|
+
# `agent run` uses), so pytest ships with the CLI. anyio, their async plugin, comes with httpx.
|
|
26
|
+
"pytest>=8,<9",
|
|
21
27
|
]
|
|
22
28
|
|
|
23
29
|
[project.optional-dependencies]
|
|
@@ -42,10 +48,10 @@ where = ["src"]
|
|
|
42
48
|
|
|
43
49
|
# `agent init` templates ride INSIDE the wheel (offline scaffold — no network fetch).
|
|
44
50
|
[tool.setuptools.package-data]
|
|
45
|
-
keystone_cli = ["templates/*/*.tmpl", "templates/*/pkg/*.tmpl", "skills/*/SKILL.md"]
|
|
51
|
+
keystone_cli = ["templates/*/*.tmpl", "templates/*/pkg/*.tmpl", "templates/*/tests/*.tmpl", "skills/*/SKILL.md"]
|
|
46
52
|
|
|
47
53
|
[tool.ruff]
|
|
48
|
-
target-version = "0.
|
|
54
|
+
target-version = "0.3.0"
|
|
49
55
|
line-length = 120
|
|
50
56
|
|
|
51
57
|
[tool.ruff.lint]
|
|
@@ -5,11 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from keystone_cli._stdio import make_stdio_unicode_safe
|
|
8
|
+
from keystone_cli._tls import use_system_trust_store
|
|
8
9
|
from keystone_cli._version import version_line
|
|
9
10
|
from keystone_cli.commands.agent import agent_app
|
|
10
11
|
from keystone_cli.commands.dev import dev
|
|
11
12
|
from keystone_cli.commands.kb import kb_app
|
|
12
13
|
from keystone_cli.commands.login import login
|
|
14
|
+
from keystone_cli.commands.prompt import prompt_app
|
|
13
15
|
from keystone_cli.commands.skills import skills_app
|
|
14
16
|
from keystone_cli.commands.tool_credentials import tool_credentials_app
|
|
15
17
|
from keystone_cli.commands.whoami import whoami
|
|
@@ -20,8 +22,47 @@ from keystone_cli.commands.workspace import workspace_app
|
|
|
20
22
|
# late for the very invocation QC reported first. No-op on a console that already copes.
|
|
21
23
|
make_stdio_unicode_safe()
|
|
22
24
|
|
|
25
|
+
#: Shown under the command list (FDP-6604): where to start, what changes the CLI, what the exit codes
|
|
26
|
+
#: mean. Every KEYSTONE_* variable the code reads is listed — test_help_quality enforces it.
|
|
27
|
+
EPILOG = """\b
|
|
28
|
+
Get started:
|
|
29
|
+
keystone login
|
|
30
|
+
keystone workspace use <name>
|
|
31
|
+
keystone agent init my-agent --template llm
|
|
32
|
+
keystone agent validate my-agent
|
|
33
|
+
keystone agent test my-agent
|
|
34
|
+
keystone agent deploy my-agent --publish --wait
|
|
35
|
+
keystone agent invoke my-agent --input '{"prompt": "hi"}'
|
|
36
|
+
Fields of agent.yaml: keystone agent manifest. Each command: --help.
|
|
37
|
+
|
|
38
|
+
\b
|
|
39
|
+
Environment:
|
|
40
|
+
KEYSTONE_CONFIG config file (default ~/.keystone/config.json);
|
|
41
|
+
use one file per environment
|
|
42
|
+
KEYSTONE_API_BASE platform API URL (overrides the saved one)
|
|
43
|
+
KEYSTONE_AGENT_API_BASE agent API URL: invoke, runs, hitl, threads
|
|
44
|
+
KEYSTONE_TOKEN access token, used as given and never renewed
|
|
45
|
+
(for CI)
|
|
46
|
+
KEYSTONE_ISSUER login: Keycloak realm URL
|
|
47
|
+
KEYSTONE_CLIENT_ID login: client id
|
|
48
|
+
KEYSTONE_CLIENT_SECRET login --client-credentials: client secret
|
|
49
|
+
KEYSTONE_NO_BROWSER login: do not open a browser
|
|
50
|
+
KEYSTONE_SYSTEM_TRUST 0: do not trust the OS certificate store
|
|
51
|
+
|
|
52
|
+
\b
|
|
53
|
+
Exit codes:
|
|
54
|
+
0 done
|
|
55
|
+
1 failed (the message says why)
|
|
56
|
+
2 usage error: an unknown command or option, a missing
|
|
57
|
+
argument, or a field name that agent manifest does not know
|
|
58
|
+
agent test returns the exit code of pytest. agent fetch returns 1 when
|
|
59
|
+
it stops early, and still writes what it got.
|
|
60
|
+
With --json, a failed command prints {"error": ..., "exit_code": ...}
|
|
61
|
+
on stdout and the message on stderr."""
|
|
62
|
+
|
|
23
63
|
app = typer.Typer(
|
|
24
64
|
help="Keystone CLI — build, test, deploy and operate pro-code agents.",
|
|
65
|
+
epilog=EPILOG,
|
|
25
66
|
no_args_is_help=True,
|
|
26
67
|
add_completion=False,
|
|
27
68
|
)
|
|
@@ -45,6 +86,9 @@ def _main(
|
|
|
45
86
|
),
|
|
46
87
|
) -> None:
|
|
47
88
|
"""Keystone pro-code agent CLI."""
|
|
89
|
+
# FDP-6328 — here, not at import: every command passes through this callback before it opens
|
|
90
|
+
# a connection, while merely importing the app (tests do) leaves the process's `ssl` alone.
|
|
91
|
+
use_system_trust_store()
|
|
48
92
|
|
|
49
93
|
|
|
50
94
|
@app.command(name="version")
|
|
@@ -62,6 +106,7 @@ app.add_typer(workspace_app, name="workspace")
|
|
|
62
106
|
app.add_typer(kb_app, name="kb")
|
|
63
107
|
app.add_typer(tool_credentials_app, name="tool-credentials")
|
|
64
108
|
app.add_typer(skills_app, name="skills")
|
|
109
|
+
app.add_typer(prompt_app, name="prompt")
|
|
65
110
|
app.command(name="login")(login)
|
|
66
111
|
app.command(name="dev")(dev)
|
|
67
112
|
app.command(name="whoami")(whoami)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""``--json`` output shared by the read commands (FDP-6604).
|
|
2
|
+
|
|
3
|
+
A script or a coding agent should not parse a table. Every list / get / status / current command
|
|
4
|
+
prints the API's own answer with ``--json`` — the same body, not a reshaped copy, so it carries
|
|
5
|
+
every field the table leaves out. ``test_help_quality`` enforces that these commands have the flag.
|
|
6
|
+
|
|
7
|
+
:func:`json_errors` keeps stdout parseable while such a command runs: see its docstring.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import contextlib
|
|
13
|
+
import functools
|
|
14
|
+
import io
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from typing import Any, TextIO, cast
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
#: What every ``--json`` help says about a failure (a command with its own --json text appends it).
|
|
24
|
+
JSON_FAIL_NOTE = (
|
|
25
|
+
'If the command fails before its answer, it prints {"error": ..., "exit_code": ...}; '
|
|
26
|
+
"a stream ends with that object."
|
|
27
|
+
)
|
|
28
|
+
JSON_HELP = f"Print the raw JSON answer instead of a table, for scripts and agents. {JSON_FAIL_NOTE}"
|
|
29
|
+
|
|
30
|
+
#: The names a command's ``--json`` flag has in this CLI (``prompt`` and ``kb files`` use as_json).
|
|
31
|
+
JSON_PARAMS = ("json_out", "as_json")
|
|
32
|
+
|
|
33
|
+
_ANSI = re.compile(r"\x1b\[[0-9;]*m")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _JsonRun:
|
|
37
|
+
"""One ``--json`` command run: the real stdout, the text it printed elsewhere, what it answered."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, stdout: TextIO) -> None:
|
|
40
|
+
self.stdout = stdout
|
|
41
|
+
self.text: list[str] = []
|
|
42
|
+
self.printed = False
|
|
43
|
+
self.streamed = False
|
|
44
|
+
|
|
45
|
+
def emit(self, text: str) -> None:
|
|
46
|
+
self.printed = True
|
|
47
|
+
self.stdout.write(text + "\n")
|
|
48
|
+
self.stdout.flush()
|
|
49
|
+
|
|
50
|
+
def error_text(self) -> str:
|
|
51
|
+
"""From the last ✗ line to the end: the reason it stopped, with the lines that explain it."""
|
|
52
|
+
lines = [_ANSI.sub("", ln).strip() for ln in "".join(self.text).splitlines()]
|
|
53
|
+
lines = [ln for ln in lines if ln]
|
|
54
|
+
last = max((i for i, ln in enumerate(lines) if ln.startswith("✗")), default=len(lines) - 1)
|
|
55
|
+
reason = " ".join(lines[max(last, 0) :])
|
|
56
|
+
return reason.lstrip("✗").strip() or "the command failed"
|
|
57
|
+
|
|
58
|
+
def fail(self, exit_code: int, error: str | None = None) -> None:
|
|
59
|
+
# A one-document answer stays as it is (runs get of a failed run). A stream gets the error
|
|
60
|
+
# as its last line, so a consumer can tell a broken stream from one that ended.
|
|
61
|
+
if not self.printed or self.streamed:
|
|
62
|
+
self.emit(json.dumps({"error": error or self.error_text(), "exit_code": exit_code}, ensure_ascii=False))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _Recorded(io.TextIOBase):
|
|
66
|
+
"""stdout and stderr while a ``--json`` command runs: written to stderr, and kept for the error."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, target: TextIO, run: _JsonRun) -> None:
|
|
69
|
+
self._target = target
|
|
70
|
+
self._run = run
|
|
71
|
+
|
|
72
|
+
def write(self, s: str | bytes) -> int: # ty: click may hand bytes to a stream it cannot classify
|
|
73
|
+
text = s.decode("utf-8", "replace") if isinstance(s, bytes) else s
|
|
74
|
+
self._run.text.append(text)
|
|
75
|
+
self._target.write(text)
|
|
76
|
+
return len(s)
|
|
77
|
+
|
|
78
|
+
def flush(self) -> None:
|
|
79
|
+
self._target.flush()
|
|
80
|
+
|
|
81
|
+
def isatty(self) -> bool:
|
|
82
|
+
return self._target.isatty()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
#: The ``--json`` run in progress. A module global, not a ContextVar: one command runs per process,
|
|
86
|
+
#: and a worker thread (fetch-style thread pools) must see it too, or its answer would go to stderr.
|
|
87
|
+
_CURRENT: _JsonRun | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def echo_json(value: Any) -> None:
|
|
91
|
+
"""The answer of a ``--json`` command: the only output that reaches stdout while it runs."""
|
|
92
|
+
_write(json.dumps(value, indent=2, ensure_ascii=False, default=str))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def echo_json_line(value: Any) -> None:
|
|
96
|
+
"""One JSON object on one line, for a command that streams (``logs --follow --json``)."""
|
|
97
|
+
if _CURRENT is not None:
|
|
98
|
+
_CURRENT.streamed = True
|
|
99
|
+
_write(json.dumps(value, ensure_ascii=False, default=str))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _write(text: str) -> None:
|
|
103
|
+
if _CURRENT is None:
|
|
104
|
+
typer.echo(text)
|
|
105
|
+
else:
|
|
106
|
+
_CURRENT.emit(text)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _exception_exit(exc: Exception) -> tuple[int, str]:
|
|
110
|
+
"""A click-style exception (BadParameter, UsageError...) carries its exit code and message."""
|
|
111
|
+
code = getattr(exc, "exit_code", None)
|
|
112
|
+
format_message = getattr(exc, "format_message", None)
|
|
113
|
+
message = format_message() if callable(format_message) else f"{type(exc).__name__}: {exc}"
|
|
114
|
+
return (code if isinstance(code, int) and code else 1), message
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def json_errors[F: Callable[..., Any]](fn: F) -> F:
|
|
118
|
+
"""Keep stdout JSON only while a command runs with ``--json``.
|
|
119
|
+
|
|
120
|
+
* stdout carries only :func:`echo_json` / :func:`echo_json_line`. Everything else the command
|
|
121
|
+
prints — ✗ / ! lines, hints, a confirm prompt — goes to stderr, so it cannot break the JSON.
|
|
122
|
+
A streaming command still streams: each answer line is written at once.
|
|
123
|
+
* If the command stops before its answer, stdout gets
|
|
124
|
+
``{"error": <from its last ✗ line>, "exit_code": <code>}``: on a non-zero exit, an abort, or
|
|
125
|
+
an exception (raised again, so a traceback still shows on stderr). A command that printed
|
|
126
|
+
its one answer and then failed (``runs get`` of a failed run) keeps it; a stream gets the
|
|
127
|
+
error object as its last line.
|
|
128
|
+
|
|
129
|
+
A usage error (an unknown option, exit 2) happens before the command runs; Typer prints it to
|
|
130
|
+
stderr. ``test_help_quality`` checks that every command with ``--json`` has this decorator.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
@functools.wraps(fn)
|
|
134
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
135
|
+
global _CURRENT # noqa: PLW0603 — one command per process; reset in `finally`
|
|
136
|
+
if not any(kwargs.get(name) for name in JSON_PARAMS):
|
|
137
|
+
return fn(*args, **kwargs)
|
|
138
|
+
run = _JsonRun(sys.stdout)
|
|
139
|
+
stderr = sys.stderr
|
|
140
|
+
_CURRENT = run
|
|
141
|
+
try:
|
|
142
|
+
with (
|
|
143
|
+
contextlib.redirect_stdout(cast("TextIO", _Recorded(stderr, run))),
|
|
144
|
+
contextlib.redirect_stderr(cast("TextIO", _Recorded(stderr, run))),
|
|
145
|
+
):
|
|
146
|
+
return fn(*args, **kwargs)
|
|
147
|
+
except typer.Exit as exc:
|
|
148
|
+
if exc.exit_code:
|
|
149
|
+
run.fail(exc.exit_code)
|
|
150
|
+
raise
|
|
151
|
+
except typer.Abort:
|
|
152
|
+
run.fail(1, "aborted")
|
|
153
|
+
raise
|
|
154
|
+
except Exception as exc:
|
|
155
|
+
run.fail(*_exception_exit(exc))
|
|
156
|
+
raise
|
|
157
|
+
finally:
|
|
158
|
+
_CURRENT = None
|
|
159
|
+
|
|
160
|
+
setattr(wrapper, "_json_errors", True) # noqa: B010 — read by test_help_quality
|
|
161
|
+
return cast("F", wrapper)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Verify TLS against the operating system's trust store (FDP-6328).
|
|
2
|
+
|
|
3
|
+
httpx verifies servers against ``certifi``'s bundled CA list, never the OS store. On a network
|
|
4
|
+
that inspects TLS — TCB's, where a corporate proxy re-signs every HTTPS connection with the
|
|
5
|
+
bank's own CA — that CA is installed in the OS store (macOS Keychain, Windows certificate
|
|
6
|
+
store), so browsers work, but certifi does not know it. Every CLI command then failed with
|
|
7
|
+
``CERTIFICATE_VERIFY_FAILED`` until the user exported ``SSL_CERT_FILE=<corp bundle>`` on each
|
|
8
|
+
call — which REPLACES certifi rather than adding to it, so hosts outside the proxy broke instead.
|
|
9
|
+
|
|
10
|
+
``truststore`` makes Python's ``ssl`` verify through the OS (Security.framework on macOS,
|
|
11
|
+
CryptoAPI on Windows, OpenSSL with the system paths on Linux) — the approach pip takes for the
|
|
12
|
+
same reason. ``SSL_CERT_FILE`` keeps working on top: httpx hands it to ``load_verify_locations``,
|
|
13
|
+
which truststore adds as extra trust anchors (checked with a local CA and httpx 0.28.1: OS-trusted
|
|
14
|
+
and SSL_CERT_FILE-trusted servers both verify).
|
|
15
|
+
|
|
16
|
+
``KEYSTONE_SYSTEM_TRUST=0`` turns it off (certifi only, the old behaviour), for the day the OS
|
|
17
|
+
store itself is the problem.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
from collections.abc import Mapping
|
|
24
|
+
|
|
25
|
+
import truststore
|
|
26
|
+
|
|
27
|
+
ENV_SYSTEM_TRUST = "KEYSTONE_SYSTEM_TRUST"
|
|
28
|
+
_OFF = {"0", "false", "no", "off"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def use_system_trust_store(env: Mapping[str, str] | None = None) -> bool:
|
|
32
|
+
"""Make every TLS connection this process opens verify through the OS trust store.
|
|
33
|
+
|
|
34
|
+
Call once, before any HTTP client exists (an ``httpx.Client`` builds its SSL context when it
|
|
35
|
+
is created). Returns whether it is on. Idempotent: truststore ignores a second injection."""
|
|
36
|
+
env = os.environ if env is None else env
|
|
37
|
+
if (env.get(ENV_SYSTEM_TRUST) or "").strip().lower() in _OFF:
|
|
38
|
+
return False
|
|
39
|
+
truststore.inject_into_ssl()
|
|
40
|
+
return True
|
|
@@ -147,10 +147,25 @@ def explain(resp: httpx.Response) -> str:
|
|
|
147
147
|
return f"{head}: {message}" + (f" — {hint}" if hint else "")
|
|
148
148
|
|
|
149
149
|
|
|
150
|
+
#: A function that returns valid auth headers per request (a long paging run outlives a 300 s token).
|
|
151
|
+
HeaderSource = Callable[[], dict[str, str]]
|
|
152
|
+
#: Headers, or a source of them.
|
|
153
|
+
Headers = dict[str, str] | HeaderSource
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def merge_lines(*batches: list[dict[str, Any]], limit: int) -> tuple[list[dict[str, Any]], bool]:
|
|
157
|
+
"""Batches of lines → unique by line id, oldest first, the newest ``limit`` kept.
|
|
158
|
+
Returns ``(lines, cut)``; ``cut`` when the union held more than ``limit``."""
|
|
159
|
+
merged = {str(line.get("id")): line for batch in batches for line in batch}
|
|
160
|
+
epoch = datetime(1970, 1, 1, tzinfo=UTC) # by parsed time: mixed-precision ISO stamps mis-sort as text
|
|
161
|
+
lines = sorted(merged.values(), key=lambda ln: (parse_stamp(ln.get("timestamp")) or epoch, str(ln.get("id"))))
|
|
162
|
+
return (lines[-limit:] if limit else []), len(lines) > limit
|
|
163
|
+
|
|
164
|
+
|
|
150
165
|
def fetch_history(
|
|
151
166
|
client: httpx.Client,
|
|
152
167
|
base: str,
|
|
153
|
-
headers:
|
|
168
|
+
headers: Headers,
|
|
154
169
|
agent_id: str,
|
|
155
170
|
*,
|
|
156
171
|
start: datetime,
|
|
@@ -173,7 +188,8 @@ def fetch_history(
|
|
|
173
188
|
params = [("from", iso(start)), ("to", iso(end)), ("limit", str(min(MAX_PAGE, limit - len(lines))))]
|
|
174
189
|
if before:
|
|
175
190
|
params.append(("before", before))
|
|
176
|
-
|
|
191
|
+
page_headers = headers if isinstance(headers, dict) else headers()
|
|
192
|
+
resp = client.get(url, headers=page_headers, params=[*params, *filters.params()])
|
|
177
193
|
if resp.status_code != 200:
|
|
178
194
|
raise LogsError(explain(resp))
|
|
179
195
|
body = resp.json() or {}
|
|
@@ -194,7 +210,7 @@ def fetch_history(
|
|
|
194
210
|
def fetch_run_history(
|
|
195
211
|
client: httpx.Client,
|
|
196
212
|
base: str,
|
|
197
|
-
headers:
|
|
213
|
+
headers: Headers,
|
|
198
214
|
agent_id: str,
|
|
199
215
|
*,
|
|
200
216
|
start: datetime,
|
|
@@ -235,11 +251,8 @@ def fetch_run_history(
|
|
|
235
251
|
filters=dataclasses.replace(filters, run_id=None, correlation_id=trace_id),
|
|
236
252
|
limit=limit,
|
|
237
253
|
)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
lines = sorted(merged.values(), key=lambda ln: (parse_stamp(ln.get("timestamp")) or epoch, str(ln.get("id"))))
|
|
241
|
-
more = more_run or more_trace or len(lines) > limit
|
|
242
|
-
return lines[-limit:], more
|
|
254
|
+
lines, cut = merge_lines(by_run, by_trace, limit=limit)
|
|
255
|
+
return lines, more_run or more_trace or cut
|
|
243
256
|
|
|
244
257
|
|
|
245
258
|
def iter_sse(chunks: Iterator[str]) -> Iterator[tuple[str, str]]:
|
|
@@ -275,7 +288,7 @@ class FollowState:
|
|
|
275
288
|
def follow(
|
|
276
289
|
client: httpx.Client,
|
|
277
290
|
base: str,
|
|
278
|
-
headers: dict[str, str],
|
|
291
|
+
headers: dict[str, str] | Callable[[], dict[str, str]],
|
|
279
292
|
agent_id: str,
|
|
280
293
|
*,
|
|
281
294
|
state: FollowState,
|
|
@@ -297,7 +310,10 @@ def follow(
|
|
|
297
310
|
up — a timer on the connection — and the read that was waiting returns at once. The read
|
|
298
311
|
timeout still bounds the wait for the response headers (the server backfills before sending
|
|
299
312
|
them). An idle stream is never torn down early, so no extra reconnects (each costs a Splunk
|
|
300
|
-
backfill and a rate-limit slot).
|
|
313
|
+
backfill and a rate-limit slot).
|
|
314
|
+
|
|
315
|
+
``headers`` may be a callable, asked again for every connection (FDP-6283): a reconnect comes
|
|
316
|
+
~15 min after the first connection, long after a 300 s access token has expired."""
|
|
301
317
|
url = base + STREAM_PATH.format(agent_id=agent_id)
|
|
302
318
|
while True:
|
|
303
319
|
params = [("since", iso(state.since)), *filters.params()]
|
|
@@ -309,7 +325,7 @@ def follow(
|
|
|
309
325
|
reason = _one_stream(
|
|
310
326
|
client,
|
|
311
327
|
url,
|
|
312
|
-
headers,
|
|
328
|
+
headers if isinstance(headers, dict) else headers(),
|
|
313
329
|
params,
|
|
314
330
|
timeout=timeout,
|
|
315
331
|
close_after=left,
|
|
@@ -149,6 +149,12 @@ def config_path() -> Path:
|
|
|
149
149
|
return Path(override) if override else Path.home() / ".keystone" / "config.json"
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
def platform_base(cfg: AuthConfig, override: str | None = None) -> str:
|
|
153
|
+
"""Platform API URL: ``override`` (a command's ``--api-base``), then ``KEYSTONE_API_BASE``, then
|
|
154
|
+
the saved one, then the build default. The one place this order is written."""
|
|
155
|
+
return (override or os.environ.get("KEYSTONE_API_BASE") or cfg.api_base or DEFAULT_API_BASE).rstrip("/")
|
|
156
|
+
|
|
157
|
+
|
|
152
158
|
def load() -> AuthConfig:
|
|
153
159
|
path = config_path()
|
|
154
160
|
if not path.exists():
|