gdmcode 0.1.3__tar.gz → 0.1.5__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 (267) hide show
  1. {gdmcode-0.1.3 → gdmcode-0.1.5}/PKG-INFO +2 -2
  2. {gdmcode-0.1.3 → gdmcode-0.1.5}/README.md +1 -1
  3. {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/cli-reference.md +3 -3
  4. {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/quick-start.md +7 -1
  5. {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/security-hardening.md +2 -0
  6. gdmcode-0.1.5/gdmcode/__init__.py +1 -0
  7. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/tool_orchestrator.py +3 -11
  8. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/auth.py +7 -4
  9. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/cli.py +41 -7
  10. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/commands.py +93 -1
  11. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/mcp_server.py +1 -1
  12. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/main.py +14 -0
  13. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/repl.py +145 -7
  14. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/__init__.py +15 -0
  15. {gdmcode-0.1.3 → gdmcode-0.1.5}/pyproject.toml +1 -1
  16. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_cli_smoke.py +37 -0
  17. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_commands.py +43 -0
  18. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_repl_smoke.py +91 -1
  19. gdmcode-0.1.3/gdmcode/__init__.py +0 -1
  20. {gdmcode-0.1.3 → gdmcode-0.1.5}/.gitignore +0 -0
  21. {gdmcode-0.1.3 → gdmcode-0.1.5}/CONTRIBUTING.md +0 -0
  22. {gdmcode-0.1.3 → gdmcode-0.1.5}/config.toml +0 -0
  23. {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/agentic-runtime-audit.md +0 -0
  24. {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/architecture.md +0 -0
  25. {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/configuration.md +0 -0
  26. {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/deployment.md +0 -0
  27. {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/plugin-guide.md +0 -0
  28. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/_internal/__init__.py +0 -0
  29. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/_internal/constants.py +0 -0
  30. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/_internal/domain_skills.py +0 -0
  31. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/__init__.py +0 -0
  32. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/commit_classifier.py +0 -0
  33. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/context_budget.py +0 -0
  34. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/daemon.py +0 -0
  35. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/dag_validator.py +0 -0
  36. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/debug_loop.py +0 -0
  37. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/impact_analyzer.py +0 -0
  38. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/impact_graph.py +0 -0
  39. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/loop.py +0 -0
  40. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/orchestrator.py +0 -0
  41. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/regression_guard.py +0 -0
  42. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/review_gate.py +0 -0
  43. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/risk_scorer.py +0 -0
  44. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/self_healing.py +0 -0
  45. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/smart_test_selector.py +0 -0
  46. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/system_prompt.py +0 -0
  47. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/task_tracker.py +0 -0
  48. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/test_validator.py +0 -0
  49. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/transcript.py +0 -0
  50. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/verification_loop.py +0 -0
  51. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/work_director.py +0 -0
  52. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/worktree_manager.py +0 -0
  53. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/artifacts/__init__.py +0 -0
  54. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/artifacts/artifact_store.py +0 -0
  55. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/artifacts/verification_graph.py +0 -0
  56. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/config.py +0 -0
  57. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/cost_tracker.py +0 -0
  58. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/db/__init__.py +0 -0
  59. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/db/migrations.py +0 -0
  60. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/__init__.py +0 -0
  61. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/audit_log.py +0 -0
  62. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/identity.py +0 -0
  63. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/rbac.py +0 -0
  64. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/team_config.py +0 -0
  65. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/usage_analytics.py +0 -0
  66. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/exceptions.py +0 -0
  67. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/git_workflow.py +0 -0
  68. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/__init__.py +0 -0
  69. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/github_actions.py +0 -0
  70. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/sentry_integration.py +0 -0
  71. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/sentry_server.py +0 -0
  72. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/webhook_security.py +0 -0
  73. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/__init__.py +0 -0
  74. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/code_index.py +0 -0
  75. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/compressor.py +0 -0
  76. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/context_memory.py +0 -0
  77. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/continuous_memory.py +0 -0
  78. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/conventions.py +0 -0
  79. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/db.py +0 -0
  80. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/document_index.py +0 -0
  81. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/file_cache.py +0 -0
  82. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/project_scanner.py +0 -0
  83. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/session_store.py +0 -0
  84. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/__init__.py +0 -0
  85. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/client.py +0 -0
  86. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/definitions.py +0 -0
  87. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/router.py +0 -0
  88. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/schemas.py +0 -0
  89. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/permissions.py +0 -0
  90. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/__init__.py +0 -0
  91. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/command_filter.py +0 -0
  92. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/models.py +0 -0
  93. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/permission_handler.py +0 -0
  94. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/phone_ui.py +0 -0
  95. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/protocol.py +0 -0
  96. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/qr.py +0 -0
  97. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/server.py +0 -0
  98. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/token_manager.py +0 -0
  99. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/tunnel.py +0 -0
  100. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/runtime/__init__.py +0 -0
  101. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/runtime/branch_farm.py +0 -0
  102. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/runtime/replay.py +0 -0
  103. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sandbox/__init__.py +0 -0
  104. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sandbox/hermetic.py +0 -0
  105. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sandbox/policy.py +0 -0
  106. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sdk/__init__.py +0 -0
  107. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sdk/plugin_base.py +0 -0
  108. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sdk/plugin_host.py +0 -0
  109. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sdk/plugin_loader.py +0 -0
  110. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/security.py +0 -0
  111. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/__init__.py +0 -0
  112. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/bridge.py +0 -0
  113. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/bridge_cli.py +0 -0
  114. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/bridge_client.py +0 -0
  115. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/protocol_version.py +0 -0
  116. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/session/__init__.py +0 -0
  117. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/session/event_fanout.py +0 -0
  118. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/session/input_broker.py +0 -0
  119. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/session/permission_bridge.py +0 -0
  120. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/_atomic.py +0 -0
  121. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/agent_tools.py +0 -0
  122. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/ask_user_tool.py +0 -0
  123. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/bash_tool.py +0 -0
  124. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/browser_tool.py +0 -0
  125. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/browser_tools.py +0 -0
  126. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/dep_tools.py +0 -0
  127. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/document_reader.py +0 -0
  128. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/document_tool.py +0 -0
  129. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/document_writer.py +0 -0
  130. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/impact_tools.py +0 -0
  131. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/playwright_tool.py +0 -0
  132. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/quality_tools.py +0 -0
  133. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/read_tools.py +0 -0
  134. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/result_cache.py +0 -0
  135. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/search_tools.py +0 -0
  136. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/shell_tools.py +0 -0
  137. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/write_tools.py +0 -0
  138. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/__init__.py +0 -0
  139. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/audio_capture.py +0 -0
  140. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/audio_playback.py +0 -0
  141. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/errors.py +0 -0
  142. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/models.py +0 -0
  143. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/providers.py +0 -0
  144. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/vad.py +0 -0
  145. {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/voice_loop.py +0 -0
  146. {gdmcode-0.1.3 → gdmcode-0.1.5}/proxy/Dockerfile +0 -0
  147. {gdmcode-0.1.3 → gdmcode-0.1.5}/proxy/main.py +0 -0
  148. {gdmcode-0.1.3 → gdmcode-0.1.5}/proxy/requirements.txt +0 -0
  149. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/__init__.py +0 -0
  150. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/remote/__init__.py +0 -0
  151. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/remote/test_remote_server.py +0 -0
  152. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_agent_loop.py +0 -0
  153. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_agent_tools.py +0 -0
  154. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_api_fallback.py +0 -0
  155. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_artifact_store.py +0 -0
  156. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_audit_log.py +0 -0
  157. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_auth.py +0 -0
  158. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_auto_quality.py +0 -0
  159. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_autonomy_levels.py +0 -0
  160. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_bash_tool.py +0 -0
  161. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_batch_api.py +0 -0
  162. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_branch_farm.py +0 -0
  163. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_bridge.py +0 -0
  164. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_bridge_smoke.py +0 -0
  165. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_browser_tool_smoke.py +0 -0
  166. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_browser_tools.py +0 -0
  167. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_btw_queue.py +0 -0
  168. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_budget_tracker.py +0 -0
  169. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_chrome_extension.py +0 -0
  170. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_ci_runner.py +0 -0
  171. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_code_index.py +0 -0
  172. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_compression.py +0 -0
  173. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_confidence.py +0 -0
  174. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_config.py +0 -0
  175. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_continuous_memory.py +0 -0
  176. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_convention_drift.py +0 -0
  177. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_cost_tracker.py +0 -0
  178. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_daemon.py +0 -0
  179. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_daemon_stability.py +0 -0
  180. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_daemon_watchdog.py +0 -0
  181. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_db.py +0 -0
  182. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_debate.py +0 -0
  183. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_debug_loop.py +0 -0
  184. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_debug_loop_smoke.py +0 -0
  185. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_dep_tools.py +0 -0
  186. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_doctor.py +0 -0
  187. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_document_index.py +0 -0
  188. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_document_reader.py +0 -0
  189. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_document_tool.py +0 -0
  190. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_document_writer.py +0 -0
  191. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_domain_skills.py +0 -0
  192. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_eval_harness.py +0 -0
  193. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_event_log.py +0 -0
  194. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_failure_taxonomy.py +0 -0
  195. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_file_tools.py +0 -0
  196. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_git_workflow.py +0 -0
  197. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_github_actions.py +0 -0
  198. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_health.py +0 -0
  199. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_hermetic_sandbox.py +0 -0
  200. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_identity_rbac.py +0 -0
  201. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_impact_analysis.py +0 -0
  202. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_impact_analyzer.py +0 -0
  203. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_impact_graph.py +0 -0
  204. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_impact_tools.py +0 -0
  205. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_injection_gate.py +0 -0
  206. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_leaderboard.py +0 -0
  207. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_local_models.py +0 -0
  208. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_loop.py +0 -0
  209. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_loop_p3.py +0 -0
  210. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_mcp_server.py +0 -0
  211. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_memory.py +0 -0
  212. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_migrations.py +0 -0
  213. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_mock_provider.py +0 -0
  214. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_model_config.py +0 -0
  215. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_orchestrator.py +0 -0
  216. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_package.py +0 -0
  217. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_permissions.py +0 -0
  218. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_phase2_modules.py +0 -0
  219. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_phone_ui.py +0 -0
  220. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_playwright_tool.py +0 -0
  221. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_plugin_sdk.py +0 -0
  222. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_protocol_version.py +0 -0
  223. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_provenance.py +0 -0
  224. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_proxy_server.py +0 -0
  225. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_quality_integration.py +0 -0
  226. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_reasoning_toggle.py +0 -0
  227. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_redaction.py +0 -0
  228. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_regression_collector.py +0 -0
  229. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_regression_guard_integration.py +0 -0
  230. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_regression_runner.py +0 -0
  231. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_replay.py +0 -0
  232. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_resilience.py +0 -0
  233. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_result_cache.py +0 -0
  234. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_review_gate_expanded.py +0 -0
  235. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_risk_scorer.py +0 -0
  236. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_rollback.py +0 -0
  237. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_router.py +0 -0
  238. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_router_compressor_conventions.py +0 -0
  239. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_router_escalation.py +0 -0
  240. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_sandbox.py +0 -0
  241. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_scoring.py +0 -0
  242. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_search_tools.py +0 -0
  243. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_self_healing.py +0 -0
  244. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_semantic_edit.py +0 -0
  245. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_sentry_integration.py +0 -0
  246. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_session_checkpoint.py +0 -0
  247. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_session_controller.py +0 -0
  248. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_session_restore.py +0 -0
  249. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_signal_handling.py +0 -0
  250. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_swebench_adapter.py +0 -0
  251. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_swebench_runner.py +0 -0
  252. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_system_prompt.py +0 -0
  253. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_team_config.py +0 -0
  254. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tool_cache.py +0 -0
  255. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tool_orchestrator.py +0 -0
  256. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tool_timeout.py +0 -0
  257. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tools_registry.py +0 -0
  258. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tunnel_qr.py +0 -0
  259. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_usage_analytics.py +0 -0
  260. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_verification_graph.py +0 -0
  261. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_verification_loop.py +0 -0
  262. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_voice_loop.py +0 -0
  263. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_voice_providers.py +0 -0
  264. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_whole_codebase.py +0 -0
  265. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_work_director.py +0 -0
  266. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/voice/__init__.py +0 -0
  267. {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/voice/test_audio_foundation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gdmcode
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: gdm: AI coding agent for professional developers
5
5
  Project-URL: Homepage, https://github.com/guidegdm/gdmcode
6
6
  Project-URL: Repository, https://github.com/guidegdm/gdmcode
@@ -204,7 +204,7 @@ gdm login grok # or: gemini | codex | all
204
204
 
205
205
  Secrets are stored in the OS keychain — never in TOML files.
206
206
 
207
- Interactive setup is also available inside `gdm` with `/login` and `/proxy`. First launch without credentials shows onboarding instead of starting model agents.
207
+ Interactive setup is also available inside `gdm` with `/login`, `/logout`, and `/proxy`. First launch without credentials shows onboarding instead of starting model agents; type a normal message like `hey` to test the connected LLM.
208
208
 
209
209
  Full configuration reference: [docs/configuration.md](docs/configuration.md)
210
210
 
@@ -107,7 +107,7 @@ gdm login grok # or: gemini | codex | all
107
107
 
108
108
  Secrets are stored in the OS keychain — never in TOML files.
109
109
 
110
- Interactive setup is also available inside `gdm` with `/login` and `/proxy`. First launch without credentials shows onboarding instead of starting model agents.
110
+ Interactive setup is also available inside `gdm` with `/login`, `/logout`, and `/proxy`. First launch without credentials shows onboarding instead of starting model agents; type a normal message like `hey` to test the connected LLM.
111
111
 
112
112
  Full configuration reference: [docs/configuration.md](docs/configuration.md)
113
113
 
@@ -71,11 +71,11 @@ Authenticate with an AI provider and store credentials in the OS keychain.
71
71
  gdm logout [PROVIDER]
72
72
  ```
73
73
 
74
- Remove stored credentials for a provider.
74
+ Remove stored credentials for a provider, proxy, or all routes.
75
75
 
76
76
  | Argument | Values | Default |
77
77
  |----------|--------|---------|
78
- | `PROVIDER` | `grok`, `gemini`, `codex`, `all` | `all` |
78
+ | `PROVIDER` | `grok`, `gemini`, `codex`, `proxy`, `all` | `all` |
79
79
 
80
80
  ---
81
81
 
@@ -186,4 +186,4 @@ Manage the gdm background daemon (code indexing, session compression, security s
186
186
 
187
187
  | Argument | Values | Default |
188
188
  |----------|--------|---------|
189
- | `ACTION` | `start`, `stop`, `status` | `status` |
189
+ | `ACTION` | `start`, `stop`, `status` | `status` |
@@ -48,6 +48,10 @@ requests and should not store provider keys server-side.
48
48
  You can run `gdm health` after login/proxy setup to check API connectivity, the database, tool
49
49
  availability, and budget state.
50
50
 
51
+ To disconnect later, run `gdm logout grok` or use `/logout grok` inside the interactive shell.
52
+ `/logout all` forgets stored provider and proxy credentials; environment variables or TOML keys
53
+ must be removed manually if they are still present.
54
+
51
55
  ## 4. Navigate to your project
52
56
 
53
57
  ```bash
@@ -65,7 +69,9 @@ gdm "fix the login bug"
65
69
 
66
70
  If you start `gdm` before connecting a model, it shows setup guidance and keeps slash commands
67
71
  available instead of loading the agent loop. Once connected, the agent will read relevant files,
68
- plan a solution, and propose edits. At autonomy level 2
72
+ plan a solution, and propose edits. Type `hey` as a quick connection test; if the provider rejects
73
+ the request, gdm explains whether it looks like no LLM, invalid key, rate limit, billing/credit, or
74
+ network/proxy trouble. At autonomy level 2
69
75
  (default) it will ask before writing files or running shell commands. Lower the level for more
70
76
  prompts; raise it for more independence.
71
77
 
@@ -144,6 +144,8 @@ actor attribution in audit logs.
144
144
  **Rules:**
145
145
  - API keys, tokens, and passwords must be stored in the OS keychain via `gdm login`.
146
146
  - Relay tokens should be entered with `/proxy token` so they are hidden at the terminal.
147
+ - Use `gdm logout <provider>` or `/logout <provider>` to remove keychain/fallback credentials.
148
+ Environment variables and TOML entries override logout and must be removed manually.
147
149
  - They must **never** appear in `config.toml`, `team.toml`, or any environment variable committed
148
150
  to version control.
149
151
  - `team.toml` is committed to git — treat it as fully public.
@@ -0,0 +1 @@
1
+ __version__ = "0.1.5"
@@ -25,7 +25,7 @@ from typing import Any
25
25
  from gdmcode._internal.constants import _ALWAYS_DENY_TOOLS, _WRITE_TOOLS
26
26
  from gdmcode.exceptions import ToolPermissionError
27
27
  from gdmcode.security import AuditLogger
28
- from gdmcode.tools import REGISTRY, ToolResult
28
+ from gdmcode.tools import REGISTRY, ToolResult, ensure_default_tools_registered
29
29
 
30
30
  __all__ = ["ToolOrchestrator"]
31
31
 
@@ -144,15 +144,7 @@ _TOOL_CACHE: _ToolResultCache = _ToolResultCache()
144
144
 
145
145
  def _ensure_tools_registered() -> None:
146
146
  """Import all tool modules so they register themselves with REGISTRY."""
147
- import gdmcode.tools.bash_tool # noqa: F401
148
- import gdmcode.tools.read_tools # noqa: F401
149
- import gdmcode.tools.search_tools # noqa: F401
150
- import gdmcode.tools.shell_tools # noqa: F401
151
- import gdmcode.tools.ask_user_tool # noqa: F401
152
- import gdmcode.tools.write_tools # noqa: F401
153
- import gdmcode.tools.dep_tools # noqa: F401
154
- import gdmcode.tools.impact_tools # noqa: F401
155
- import gdmcode.tools.browser_tool # noqa: F401
147
+ ensure_default_tools_registered()
156
148
 
157
149
 
158
150
  _ensure_tools_registered()
@@ -399,4 +391,4 @@ class ToolOrchestrator:
399
391
  """Dispatch a registered plugin tool by its namespaced tool_name."""
400
392
  if tool_name not in self._plugin_channels:
401
393
  raise KeyError(f"Unknown plugin tool: {tool_name}")
402
- return self._dispatch_plugin(tool_name, args)
394
+ return self._dispatch_plugin(tool_name, args)
@@ -217,7 +217,7 @@ def login_interactive(provider: str, store: CredentialStore | None = None) -> No
217
217
  - "codex" → paste API key
218
218
 
219
219
  Args:
220
- provider: one of "grok", "gemini", "codex", "all"
220
+ provider: one of "grok", "gemini", "codex", "proxy", "all"
221
221
  store: CredentialStore instance (creates a new one if None)
222
222
  """
223
223
  from rich.console import Console
@@ -254,15 +254,18 @@ def logout(provider: str, store: CredentialStore | None = None) -> None:
254
254
  console = Console()
255
255
  s = store or CredentialStore()
256
256
 
257
+ provider = provider.lower().strip()
257
258
  if provider == "all":
258
- for prov in ("grok", "gemini", "gemini_refresh", "codex"):
259
+ for prov in ("grok", "gemini", "gemini_refresh", "codex", "proxy"):
259
260
  s.delete(prov)
260
- console.print("[green]✓[/green] Logged out from all providers.")
261
- else:
261
+ console.print("[green]✓[/green] Logged out from all providers and proxy.")
262
+ elif provider in {"grok", "gemini", "codex", "proxy"}:
262
263
  s.delete(provider)
263
264
  if provider == "gemini":
264
265
  s.delete("gemini_refresh")
265
266
  console.print(f"[green]✓[/green] Logged out from [bold]{provider}[/bold].")
267
+ else:
268
+ console.print("[red]Unknown provider.[/red] Valid providers: grok, gemini, codex, proxy, all")
266
269
 
267
270
 
268
271
  # ---------------------------------------------------------------------------
@@ -92,10 +92,10 @@ def cmd_login(
92
92
  def cmd_logout(
93
93
  provider: Annotated[
94
94
  str,
95
- typer.Argument(help="Provider to log out from: grok | gemini | codex | all"),
95
+ typer.Argument(help="Provider to log out from: grok | gemini | codex | proxy | all"),
96
96
  ] = "all",
97
97
  ) -> None:
98
- """Remove stored credentials for a provider."""
98
+ """Remove stored credentials for a provider, proxy, or all routes."""
99
99
  from gdmcode.auth import logout
100
100
  logout(provider)
101
101
 
@@ -287,7 +287,8 @@ def cmd_health(
287
287
  GdmDatabase = _GdmDatabase
288
288
  from gdmcode.models.definitions import PROVIDER_BASE_URLS, ModelTier, get_model
289
289
  if REGISTRY is None:
290
- from gdmcode.tools import REGISTRY as _REGISTRY
290
+ from gdmcode.tools import REGISTRY as _REGISTRY, ensure_default_tools_registered
291
+ ensure_default_tools_registered()
291
292
  REGISTRY = _REGISTRY
292
293
 
293
294
  @dataclass
@@ -352,8 +353,28 @@ def cmd_health(
352
353
  try:
353
354
  tools = REGISTRY.all_tools()
354
355
  count = len(tools)
355
- status = "ok" if count > 0 else "fail"
356
- return CheckResult("tools", status, latency_ms=int((time.monotonic() - t0) * 1000), detail=f"{count} tools")
356
+ if count == 0:
357
+ return CheckResult("tools", "fail", latency_ms=int((time.monotonic() - t0) * 1000), detail="0 tools registered")
358
+ probe_name = "powershell" if sys.platform == "win32" else "bash"
359
+ probe_args = (
360
+ {"command": "$PWD.Path", "timeout": 5}
361
+ if probe_name == "powershell"
362
+ else {"command": "pwd", "timeout": 5, "read_only": True}
363
+ )
364
+ probe = REGISTRY.call(probe_name, probe_args)
365
+ if not probe.ok:
366
+ return CheckResult(
367
+ "tools",
368
+ "fail",
369
+ latency_ms=int((time.monotonic() - t0) * 1000),
370
+ detail=f"{count} registered; {probe_name} probe failed: {probe.error}",
371
+ )
372
+ return CheckResult(
373
+ "tools",
374
+ "ok",
375
+ latency_ms=int((time.monotonic() - t0) * 1000),
376
+ detail=f"{count} registered; {probe_name} probe ok",
377
+ )
357
378
  except Exception as exc: # noqa: BLE001
358
379
  return CheckResult("tools", "fail", detail=str(exc))
359
380
 
@@ -398,7 +419,8 @@ def cmd_health(
398
419
  ]
399
420
 
400
421
  overall_fail = any(
401
- r.status in ("fail", "missing") and r.priority in ("P0", "P1")
422
+ r.status == "fail" and r.priority in ("P0", "P1")
423
+ or r.status == "missing" and r.priority == "P0"
402
424
  for r in all_checks
403
425
  )
404
426
 
@@ -1179,6 +1201,7 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1179
1201
  """
1180
1202
  from rich.status import Status
1181
1203
  from gdmcode.agent.loop import EventType
1204
+ from gdmcode.repl import _format_llm_error
1182
1205
 
1183
1206
  if not _has_model_connection(cfg):
1184
1207
  console.print("[red]No model connection configured.[/red]")
@@ -1248,10 +1271,21 @@ def _run_one_shot(cfg: object, prompt: str, *, yes: bool, model_override: str |
1248
1271
  console.print(event.content)
1249
1272
  elif event.type == EventType.ERROR:
1250
1273
  status.stop()
1251
- console.print(f"[red]Error:[/red] {event.content}")
1274
+ console.print(_format_llm_error(event.content))
1252
1275
  had_error = True
1253
1276
  elif event.type == EventType.TOOL_CALL:
1277
+ status.stop()
1254
1278
  console.print(f"[yellow] [tool] {event.tool_name}[/yellow]")
1279
+ elif event.type == EventType.TOOL_RESULT:
1280
+ status.stop()
1281
+ result = event.result
1282
+ if result is not None and getattr(result, "error", None):
1283
+ console.print(f"[red] [tool error] {event.tool_name}:[/red] {getattr(result, 'error')}")
1284
+ had_error = True
1285
+ elif result is not None:
1286
+ output = (getattr(result, "output", "") or "").strip()
1287
+ if output:
1288
+ console.print(f"[dim]{output[:2000]}[/dim]")
1255
1289
  elif event.type == EventType.DONE:
1256
1290
  status.stop()
1257
1291
  except Exception as exc: # noqa: BLE001
@@ -7,7 +7,9 @@ Supported commands: /help /model /cost /tasks /btw /compact /clear /status
7
7
  from __future__ import annotations
8
8
 
9
9
  import logging
10
+ import os
10
11
  import subprocess
12
+ import tomllib
11
13
  from dataclasses import dataclass
12
14
  from datetime import datetime, timezone
13
15
  from pathlib import Path
@@ -78,10 +80,25 @@ _COMMANDS: dict[str, str] = {
78
80
  "/artifacts [view|diff|search|export] ...": "List, view, diff, search, or export saved artifacts",
79
81
  "/save [name]": "Save the last assistant response as a named artifact",
80
82
  "/login [provider]": "Authenticate inside the REPL: grok | gemini | codex | all",
83
+ "/logout [provider]": "Forget credentials: grok | gemini | codex | proxy | all",
81
84
  "/proxy [on|off|url <url>|token <tok>|status]": "Route LLM calls via proxy (for geo-blocked regions)",
82
85
  "/exit, /quit": "Exit the REPL",
83
86
  }
84
87
 
88
+ _LOGOUT_PROVIDERS: frozenset[str] = frozenset({"grok", "gemini", "codex", "proxy", "all"})
89
+ _PROVIDER_ENV_KEYS: dict[str, tuple[str, ...]] = {
90
+ "grok": ("XAI_API_KEY",),
91
+ "gemini": ("GEMINI_API_KEY",),
92
+ "codex": ("OPENAI_API_KEY",),
93
+ "proxy": ("GDM_PROXY_TOKEN", "GDM_PROXY_ENABLED"),
94
+ }
95
+ _PROVIDER_TOML_KEYS: dict[str, tuple[tuple[str, str], ...]] = {
96
+ "grok": (("api", "xai_api_key"),),
97
+ "gemini": (("api", "gemini_api_key"),),
98
+ "codex": (("api", "openai_api_key"),),
99
+ "proxy": (("proxy", "enabled"),),
100
+ }
101
+
85
102
 
86
103
  @dataclass
87
104
  class CommandResult:
@@ -102,6 +119,7 @@ class CommandResult:
102
119
  proxy_url: str | None = None
103
120
  proxy_token: str | None = None
104
121
  prompt_secret: str | None = None # if set, REPL prompts for hidden input then calls apply_proxy_token
122
+ reset_agent: bool = False # credentials/model route changed; caller should rebuild loop
105
123
 
106
124
 
107
125
  class CommandDispatcher:
@@ -221,6 +239,8 @@ class CommandDispatcher:
221
239
  return self._cmd_save(parts[1] if len(parts) > 1 else "")
222
240
  case "/login":
223
241
  return self._cmd_login(parts[1] if len(parts) > 1 else "all")
242
+ case "/logout":
243
+ return self._cmd_logout(parts[1] if len(parts) > 1 else "all")
224
244
  case "/proxy":
225
245
  return self._cmd_proxy(parts[1:])
226
246
  case "/exit" | "/quit":
@@ -237,7 +257,12 @@ class CommandDispatcher:
237
257
 
238
258
  def _cmd_help(self) -> CommandResult:
239
259
  """Display a table of all available slash commands."""
240
- tbl = Table(title="gdm slash commands", header_style="bold cyan", show_lines=False)
260
+ tbl = Table(
261
+ title="gdm interactive commands",
262
+ caption="Type a normal message like 'hey' to talk to the connected LLM.",
263
+ header_style="bold cyan",
264
+ show_lines=False,
265
+ )
241
266
  tbl.add_column("Command", style="cyan", no_wrap=True)
242
267
  tbl.add_column("Description")
243
268
  for cmd, desc in _COMMANDS.items():
@@ -1383,9 +1408,76 @@ class CommandDispatcher:
1383
1408
  return CommandResult(
1384
1409
  handled=True,
1385
1410
  refresh_config=True,
1411
+ reset_agent=True,
1386
1412
  output="[green]Login updated.[/green] The next agent turn will use the refreshed credentials.",
1387
1413
  )
1388
1414
 
1415
+ def _credential_sources_after_logout(self, providers: list[str]) -> list[str]:
1416
+ """Return non-keychain sources that can still reconnect after /logout."""
1417
+ targets = ["grok", "gemini", "codex", "proxy"] if "all" in providers else providers
1418
+ sources: list[str] = []
1419
+ for provider in targets:
1420
+ for env_key in _PROVIDER_ENV_KEYS.get(provider, ()):
1421
+ if os.environ.get(env_key):
1422
+ sources.append(f"environment variable {env_key}")
1423
+
1424
+ cfg_file = Path.home() / ".config" / "gdm" / "config.toml"
1425
+ if cfg_file.exists():
1426
+ try:
1427
+ cfg = tomllib.loads(cfg_file.read_text(encoding="utf-8"))
1428
+ except Exception as exc: # noqa: BLE001
1429
+ sources.append(f"{cfg_file} could not be checked ({exc})")
1430
+ else:
1431
+ for provider in targets:
1432
+ for section, key in _PROVIDER_TOML_KEYS.get(provider, ()):
1433
+ if cfg.get(section, {}).get(key):
1434
+ sources.append(f"{cfg_file} [{section}].{key}")
1435
+ return sources
1436
+
1437
+ def _cmd_logout(self, provider: str) -> CommandResult:
1438
+ """Forget stored credentials for one provider, proxy, or all routes."""
1439
+ provider = provider.lower().strip()
1440
+ if provider not in _LOGOUT_PROVIDERS:
1441
+ return CommandResult(
1442
+ handled=True,
1443
+ output="[red]Usage:[/red] /logout [grok|gemini|codex|proxy|all]",
1444
+ )
1445
+
1446
+ providers = ["grok", "gemini", "codex", "proxy"] if provider == "all" else [provider]
1447
+ try:
1448
+ from gdmcode.auth import CredentialStore
1449
+
1450
+ store = CredentialStore()
1451
+ for prov in providers:
1452
+ store.delete(prov)
1453
+ if prov == "gemini":
1454
+ store.delete("gemini_refresh")
1455
+ except Exception as exc: # noqa: BLE001
1456
+ log.exception("logout failed")
1457
+ return CommandResult(handled=True, output=f"[red]Logout failed:[/red] {exc}")
1458
+
1459
+ if "proxy" in providers:
1460
+ self._proxy_enabled = False
1461
+ self._proxy_token = ""
1462
+
1463
+ label = "all providers and proxy" if provider == "all" else provider
1464
+ lines = [f"[green]✓ Logged out from {label}.[/green]"]
1465
+ sources = self._credential_sources_after_logout(providers)
1466
+ if sources:
1467
+ lines.append("[yellow]Still connected through source(s) /logout cannot remove:[/yellow]")
1468
+ lines.extend(f" [dim]- {source}[/dim]" for source in sources)
1469
+ lines.append("[dim]Remove those values manually, then restart or run /logout again.[/dim]")
1470
+ else:
1471
+ lines.append("[dim]Type /login or /proxy token when you want to reconnect.[/dim]")
1472
+
1473
+ return CommandResult(
1474
+ handled=True,
1475
+ output="\n".join(lines),
1476
+ refresh_config=True,
1477
+ reset_agent=True,
1478
+ proxy_action="disable" if "proxy" in providers else None,
1479
+ )
1480
+
1389
1481
  def apply_proxy_token(self, token: str) -> CommandResult:
1390
1482
  """Store proxy token received via hidden prompt (never written to history)."""
1391
1483
  token = token.strip()
@@ -51,7 +51,7 @@ _INTERNAL_ERROR = -32603
51
51
  class MCPServer:
52
52
  """Minimal MCP server with stdio transport."""
53
53
 
54
- def __init__(self, name: str = "gdmcode", version: str = "0.1.3") -> None:
54
+ def __init__(self, name: str = "gdmcode", version: str = "0.1.5") -> None:
55
55
  self._name = name
56
56
  self._version = version
57
57
  self._tools: dict[str, MCPTool] = {}
@@ -6,6 +6,7 @@ pyproject.toml's `gdm = "gdmcode.main:main"` script works.
6
6
  from __future__ import annotations
7
7
 
8
8
  from importlib.metadata import version as _pkg_version, PackageNotFoundError
9
+ import sys
9
10
 
10
11
  from gdmcode.cli import app
11
12
 
@@ -20,8 +21,21 @@ def _get_version() -> str:
20
21
 
21
22
  def main() -> None:
22
23
  """Entry point for the `gdm` CLI command."""
24
+ _configure_stdio()
23
25
  app()
24
26
 
25
27
 
28
+ def _configure_stdio() -> None:
29
+ """Avoid Windows cp1252 crashes when Rich output is redirected."""
30
+ for stream_name in ("stdout", "stderr"):
31
+ stream = getattr(sys, stream_name, None)
32
+ if stream is None or not hasattr(stream, "reconfigure"):
33
+ continue
34
+ try:
35
+ stream.reconfigure(encoding="utf-8", errors="replace")
36
+ except (OSError, ValueError):
37
+ pass
38
+
39
+
26
40
  if __name__ == "__main__":
27
41
  main()