keystone-cli 0.1.26__tar.gz → 0.1.32__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.
Files changed (141) hide show
  1. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/PKG-INFO +3 -3
  2. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/pyproject.toml +4 -4
  3. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/__main__.py +8 -2
  4. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/auth/token_store.py +44 -1
  5. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/commands/agent.py +587 -117
  6. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/commands/dev.py +6 -1
  7. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/commands/kb.py +77 -18
  8. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/commands/login.py +26 -4
  9. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/commands/tool_credentials.py +67 -13
  10. keystone_cli-0.1.32/src/keystone_cli/commands/whoami.py +41 -0
  11. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/commands/workspace.py +21 -4
  12. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/runner.py +96 -0
  13. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/scaffold.py +2 -0
  14. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/blank/README.md.tmpl +3 -1
  15. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/hitl/README.md.tmpl +35 -0
  16. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/hitl/agent.yaml.tmpl +2 -0
  17. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/hitl/pkg/graph.py.tmpl +3 -0
  18. keystone_cli-0.1.32/src/keystone_cli/templates/ocr/README.md.tmpl +40 -0
  19. keystone_cli-0.1.32/src/keystone_cli/templates/ocr/agent.yaml.tmpl +46 -0
  20. keystone_cli-0.1.32/src/keystone_cli/templates/ocr/pkg/graph.py.tmpl +96 -0
  21. keystone_cli-0.1.32/src/keystone_cli/templates/ocr/pyproject.toml.tmpl +12 -0
  22. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/tool-agent/README.md.tmpl +2 -1
  23. keystone_cli-0.1.32/src/keystone_cli/templates/tool-agent/env.tmpl +10 -0
  24. keystone_cli-0.1.32/src/keystone_cli/templates/tool-agent/gitignore.tmpl +7 -0
  25. keystone_cli-0.1.32/src/keystone_cli/templates/tool-agent/pkg/__init__.py.tmpl +0 -0
  26. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli.egg-info/PKG-INFO +3 -3
  27. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli.egg-info/SOURCES.txt +12 -0
  28. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli.egg-info/requires.txt +1 -1
  29. keystone_cli-0.1.32/tests/test_config_env_mismatch.py +102 -0
  30. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_doctor.py +17 -0
  31. keystone_cli-0.1.32/tests/test_help_quality.py +103 -0
  32. keystone_cli-0.1.32/tests/test_hitl_approver_cli.py +100 -0
  33. keystone_cli-0.1.32/tests/test_hitl_decisions_cli.py +139 -0
  34. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_init.py +3 -0
  35. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_offline.py +9 -0
  36. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/setup.cfg +0 -0
  37. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/__init__.py +0 -0
  38. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/_build_env.py +0 -0
  39. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/_stdio.py +0 -0
  40. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/_version.py +0 -0
  41. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/auth/__init__.py +0 -0
  42. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/auth/device_flow.py +0 -0
  43. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/commands/__init__.py +0 -0
  44. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/devloop.py +0 -0
  45. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/platform_client.py +0 -0
  46. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/blank/agent.yaml.tmpl +0 -0
  47. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/blank/env.tmpl +0 -0
  48. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/blank/gitignore.tmpl +0 -0
  49. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/blank/pkg/__init__.py.tmpl +0 -0
  50. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/blank/pkg/graph.py.tmpl +0 -0
  51. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/blank/pyproject.toml.tmpl +0 -0
  52. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/guardrails/README.md.tmpl +0 -0
  53. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/guardrails/agent.yaml.tmpl +0 -0
  54. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/guardrails/env.tmpl +0 -0
  55. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/guardrails/gitignore.tmpl +0 -0
  56. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/guardrails/pkg/__init__.py.tmpl +0 -0
  57. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/guardrails/pkg/graph.py.tmpl +0 -0
  58. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/guardrails/pyproject.toml.tmpl +0 -0
  59. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/hitl/env.tmpl +0 -0
  60. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/hitl/gitignore.tmpl +0 -0
  61. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/hitl/pkg/__init__.py.tmpl +0 -0
  62. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/hitl/pyproject.toml.tmpl +0 -0
  63. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/kb-builder/README.md.tmpl +0 -0
  64. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/kb-builder/agent.yaml.tmpl +0 -0
  65. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/kb-builder/env.tmpl +0 -0
  66. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/kb-builder/gitignore.tmpl +0 -0
  67. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/kb-builder/pkg/__init__.py.tmpl +0 -0
  68. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/kb-builder/pkg/graph.py.tmpl +0 -0
  69. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/kb-builder/pyproject.toml.tmpl +0 -0
  70. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm/README.md.tmpl +0 -0
  71. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm/agent.yaml.tmpl +0 -0
  72. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm/env.tmpl +0 -0
  73. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm/gitignore.tmpl +0 -0
  74. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm/pkg/__init__.py.tmpl +0 -0
  75. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm/pkg/graph.py.tmpl +0 -0
  76. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm/pyproject.toml.tmpl +0 -0
  77. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm-streaming/README.md.tmpl +0 -0
  78. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm-streaming/agent.yaml.tmpl +0 -0
  79. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm-streaming/env.tmpl +0 -0
  80. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm-streaming/gitignore.tmpl +0 -0
  81. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm-streaming/pkg/__init__.py.tmpl +0 -0
  82. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm-streaming/pkg/graph.py.tmpl +0 -0
  83. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/llm-streaming/pyproject.toml.tmpl +0 -0
  84. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory/README.md.tmpl +0 -0
  85. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory/agent.yaml.tmpl +0 -0
  86. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory/env.tmpl +0 -0
  87. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory/gitignore.tmpl +0 -0
  88. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory/pkg/__init__.py.tmpl +0 -0
  89. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory/pkg/graph.py.tmpl +0 -0
  90. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory/pyproject.toml.tmpl +0 -0
  91. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory-service/README.md.tmpl +0 -0
  92. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory-service/agent.yaml.tmpl +0 -0
  93. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory-service/env.tmpl +0 -0
  94. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory-service/gitignore.tmpl +0 -0
  95. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory-service/pkg/__init__.py.tmpl +0 -0
  96. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory-service/pkg/graph.py.tmpl +0 -0
  97. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/memory-service/pyproject.toml.tmpl +0 -0
  98. {keystone_cli-0.1.26/src/keystone_cli/templates/rag-qa → keystone_cli-0.1.32/src/keystone_cli/templates/ocr}/env.tmpl +0 -0
  99. {keystone_cli-0.1.26/src/keystone_cli/templates/rag-qa → keystone_cli-0.1.32/src/keystone_cli/templates/ocr}/gitignore.tmpl +0 -0
  100. {keystone_cli-0.1.26/src/keystone_cli/templates/rag-qa → keystone_cli-0.1.32/src/keystone_cli/templates/ocr}/pkg/__init__.py.tmpl +0 -0
  101. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/rag-qa/README.md.tmpl +0 -0
  102. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/rag-qa/agent.yaml.tmpl +0 -0
  103. {keystone_cli-0.1.26/src/keystone_cli/templates/skill → keystone_cli-0.1.32/src/keystone_cli/templates/rag-qa}/env.tmpl +0 -0
  104. {keystone_cli-0.1.26/src/keystone_cli/templates/skill → keystone_cli-0.1.32/src/keystone_cli/templates/rag-qa}/gitignore.tmpl +0 -0
  105. {keystone_cli-0.1.26/src/keystone_cli/templates/skill → keystone_cli-0.1.32/src/keystone_cli/templates/rag-qa}/pkg/__init__.py.tmpl +0 -0
  106. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/rag-qa/pkg/graph.py.tmpl +0 -0
  107. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/rag-qa/pyproject.toml.tmpl +0 -0
  108. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/skill/README.md.tmpl +0 -0
  109. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/skill/agent.yaml.tmpl +0 -0
  110. {keystone_cli-0.1.26/src/keystone_cli/templates/tool-agent → keystone_cli-0.1.32/src/keystone_cli/templates/skill}/env.tmpl +0 -0
  111. {keystone_cli-0.1.26/src/keystone_cli/templates/tool-agent → keystone_cli-0.1.32/src/keystone_cli/templates/skill}/gitignore.tmpl +0 -0
  112. {keystone_cli-0.1.26/src/keystone_cli/templates/tool-agent → keystone_cli-0.1.32/src/keystone_cli/templates/skill}/pkg/__init__.py.tmpl +0 -0
  113. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/skill/pkg/graph.py.tmpl +0 -0
  114. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/skill/pyproject.toml.tmpl +0 -0
  115. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/tool-agent/agent.yaml.tmpl +0 -0
  116. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/tool-agent/pkg/graph.py.tmpl +0 -0
  117. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli/templates/tool-agent/pyproject.toml.tmpl +0 -0
  118. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli.egg-info/dependency_links.txt +0 -0
  119. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli.egg-info/entry_points.txt +0 -0
  120. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/src/keystone_cli.egg-info/top_level.txt +0 -0
  121. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_agent.py +0 -0
  122. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_assistant_provenance_and_flags.py +0 -0
  123. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_auth.py +0 -0
  124. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_build_progress.py +0 -0
  125. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_ci_token.py +0 -0
  126. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_deploy.py +0 -0
  127. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_dev.py +0 -0
  128. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_encoding.py +0 -0
  129. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_invoke.py +0 -0
  130. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_login_command.py +0 -0
  131. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_platform_client.py +0 -0
  132. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_publish.py +0 -0
  133. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_retire_delete.py +0 -0
  134. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_runs.py +0 -0
  135. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_status.py +0 -0
  136. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_stdio.py +0 -0
  137. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_threads.py +0 -0
  138. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_tool_credentials.py +0 -0
  139. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_traces.py +0 -0
  140. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_version.py +0 -0
  141. {keystone_cli-0.1.26 → keystone_cli-0.1.32}/tests/test_workspace.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keystone-cli
3
- Version: 0.1.26
4
- Summary: Keystone pro-code agent CLI — author, validate, and run agents locally, then deploy and invoke them on the Keystone platform.
3
+ Version: 0.1.32
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.11,>=0.10
6
+ Requires-Dist: keystone-agent-sdk<0.12,>=0.11.0
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
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "keystone-cli"
7
- version = "0.1.26"
8
- description = "Keystone pro-code agent CLI — author, validate, and run agents locally, then deploy and invoke them on the Keystone platform."
7
+ version = "0.1.32"
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.10,<0.11",
13
+ "keystone-agent-sdk>=0.11.0,<0.12",
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).
@@ -45,7 +45,7 @@ where = ["src"]
45
45
  keystone_cli = ["templates/*/*.tmpl", "templates/*/pkg/*.tmpl"]
46
46
 
47
47
  [tool.ruff]
48
- target-version = "0.1.26"
48
+ target-version = "0.1.32"
49
49
  line-length = 120
50
50
 
51
51
  [tool.ruff.lint]
@@ -11,6 +11,7 @@ from keystone_cli.commands.dev import dev
11
11
  from keystone_cli.commands.kb import kb_app
12
12
  from keystone_cli.commands.login import login
13
13
  from keystone_cli.commands.tool_credentials import tool_credentials_app
14
+ from keystone_cli.commands.whoami import whoami
14
15
  from keystone_cli.commands.workspace import workspace_app
15
16
 
16
17
  # FDP-4360 — before Typer builds anything. `--help` is rendered inside `app()`, and the help
@@ -19,7 +20,7 @@ from keystone_cli.commands.workspace import workspace_app
19
20
  make_stdio_unicode_safe()
20
21
 
21
22
  app = typer.Typer(
22
- help="Keystone pro-code agent CLI — author, validate, and run agents locally.",
23
+ help="Keystone CLI — build, test, deploy and operate pro-code agents.",
23
24
  no_args_is_help=True,
24
25
  add_completion=False,
25
26
  )
@@ -47,7 +48,11 @@ def _main(
47
48
 
48
49
  @app.command(name="version")
49
50
  def version() -> None:
50
- """Show the CLI version, its target env, and the bundled agent-sdk version."""
51
+ """Show the CLI version, its target env and the bundled agent-sdk version.
52
+
53
+ Example:
54
+ keystone version
55
+ """
51
56
  typer.echo(version_line())
52
57
 
53
58
 
@@ -57,6 +62,7 @@ app.add_typer(kb_app, name="kb")
57
62
  app.add_typer(tool_credentials_app, name="tool-credentials")
58
63
  app.command(name="login")(login)
59
64
  app.command(name="dev")(dev)
65
+ app.command(name="whoami")(whoami)
60
66
 
61
67
 
62
68
  if __name__ == "__main__":
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import json
11
11
  import os
12
+ import sys
12
13
  from dataclasses import asdict, dataclass, fields
13
14
  from pathlib import Path
14
15
 
@@ -85,6 +86,46 @@ class AuthConfig:
85
86
  env: str = ""
86
87
 
87
88
 
89
+ def endpoint_env(cfg: AuthConfig) -> str | None:
90
+ """Which env's BAKED endpoints this config carries (issuer or api_base match), or None.
91
+
92
+ None for an empty config and for custom endpoints (a deliberate ``--api-base`` / ``--issuer``
93
+ pointing somewhere no env bakes), so only a config that is recognisably ANOTHER env's is flagged.
94
+ """
95
+ for env, (issuer, api_base, _agent) in _ENV_DEFAULTS.items():
96
+ if (cfg.issuer and cfg.issuer == issuer) or (cfg.api_base and cfg.api_base.rstrip("/") == api_base):
97
+ return env
98
+ return None
99
+
100
+
101
+ def endpoints_belong_to_other_env(cfg: AuthConfig) -> str | None:
102
+ """The OTHER env whose endpoints this config carries (vs. this package's ``BUILD_ENV``), else None.
103
+
104
+ FDP-3072: the ``env`` stamp alone is not proof. A config stamped ``env=prod`` can still hold the
105
+ dev issuer/api_base (seen 2026-09-30 after installing the prod package over a dev login): the
106
+ stamp matched, so ``login`` kept the cache and every command silently went to dev.
107
+ """
108
+ env = endpoint_env(cfg)
109
+ return env if env is not None and env != BUILD_ENV else None
110
+
111
+
112
+ _warned_mismatch = False
113
+
114
+
115
+ def _warn_if_other_env(cfg: AuthConfig) -> None:
116
+ """One stderr line per process — a warning, never a block (custom endpoints are never flagged)."""
117
+ global _warned_mismatch
118
+ other = endpoints_belong_to_other_env(cfg)
119
+ if other is None or _warned_mismatch:
120
+ return
121
+ _warned_mismatch = True
122
+ print(
123
+ f"⚠ {config_path()} points at the {other} endpoints, but this is the {BUILD_ENV} CLI — "
124
+ f"commands would go to {other}. Run `keystone login` to reset it to {BUILD_ENV}.",
125
+ file=sys.stderr,
126
+ )
127
+
128
+
88
129
  def config_path() -> Path:
89
130
  """Config file path. ``KEYSTONE_CONFIG`` overrides (tests); default ``~/.keystone/config.json``."""
90
131
  override = os.environ.get("KEYSTONE_CONFIG")
@@ -100,7 +141,9 @@ def load() -> AuthConfig:
100
141
  # turned a valid session into "not logged in".
101
142
  data = json.loads(path.read_text(encoding="utf-8"))
102
143
  known = {f.name for f in fields(AuthConfig)}
103
- return AuthConfig(**{k: v for k, v in data.items() if k in known})
144
+ cfg = AuthConfig(**{k: v for k, v in data.items() if k in known})
145
+ _warn_if_other_env(cfg)
146
+ return cfg
104
147
 
105
148
 
106
149
  def _ensure_private_dir(directory: Path) -> None: