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.
- {gdmcode-0.1.3 → gdmcode-0.1.5}/PKG-INFO +2 -2
- {gdmcode-0.1.3 → gdmcode-0.1.5}/README.md +1 -1
- {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/cli-reference.md +3 -3
- {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/quick-start.md +7 -1
- {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/security-hardening.md +2 -0
- gdmcode-0.1.5/gdmcode/__init__.py +1 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/tool_orchestrator.py +3 -11
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/auth.py +7 -4
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/cli.py +41 -7
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/commands.py +93 -1
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/mcp_server.py +1 -1
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/main.py +14 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/repl.py +145 -7
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/__init__.py +15 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/pyproject.toml +1 -1
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_cli_smoke.py +37 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_commands.py +43 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_repl_smoke.py +91 -1
- gdmcode-0.1.3/gdmcode/__init__.py +0 -1
- {gdmcode-0.1.3 → gdmcode-0.1.5}/.gitignore +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/CONTRIBUTING.md +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/config.toml +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/agentic-runtime-audit.md +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/architecture.md +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/configuration.md +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/deployment.md +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/docs/plugin-guide.md +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/_internal/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/_internal/constants.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/_internal/domain_skills.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/commit_classifier.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/context_budget.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/daemon.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/dag_validator.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/debug_loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/impact_analyzer.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/impact_graph.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/orchestrator.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/regression_guard.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/review_gate.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/risk_scorer.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/self_healing.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/smart_test_selector.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/system_prompt.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/task_tracker.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/test_validator.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/transcript.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/verification_loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/work_director.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/agent/worktree_manager.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/artifacts/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/artifacts/artifact_store.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/artifacts/verification_graph.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/config.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/cost_tracker.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/db/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/db/migrations.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/audit_log.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/identity.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/rbac.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/team_config.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/enterprise/usage_analytics.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/exceptions.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/git_workflow.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/github_actions.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/sentry_integration.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/sentry_server.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/integrations/webhook_security.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/code_index.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/compressor.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/context_memory.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/continuous_memory.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/conventions.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/db.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/document_index.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/file_cache.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/project_scanner.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/memory/session_store.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/client.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/definitions.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/router.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/models/schemas.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/permissions.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/command_filter.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/models.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/permission_handler.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/phone_ui.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/protocol.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/qr.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/server.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/token_manager.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/remote/tunnel.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/runtime/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/runtime/branch_farm.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/runtime/replay.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sandbox/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sandbox/hermetic.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sandbox/policy.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sdk/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sdk/plugin_base.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sdk/plugin_host.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/sdk/plugin_loader.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/security.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/bridge.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/bridge_cli.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/bridge_client.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/server/protocol_version.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/session/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/session/event_fanout.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/session/input_broker.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/session/permission_bridge.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/_atomic.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/agent_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/ask_user_tool.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/bash_tool.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/browser_tool.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/browser_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/dep_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/document_reader.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/document_tool.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/document_writer.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/impact_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/playwright_tool.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/quality_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/read_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/result_cache.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/search_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/shell_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/tools/write_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/audio_capture.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/audio_playback.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/errors.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/models.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/providers.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/vad.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/gdmcode/voice/voice_loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/proxy/Dockerfile +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/proxy/main.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/proxy/requirements.txt +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/remote/__init__.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/remote/test_remote_server.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_agent_loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_agent_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_api_fallback.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_artifact_store.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_audit_log.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_auth.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_auto_quality.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_autonomy_levels.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_bash_tool.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_batch_api.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_branch_farm.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_bridge.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_bridge_smoke.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_browser_tool_smoke.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_browser_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_btw_queue.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_budget_tracker.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_chrome_extension.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_ci_runner.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_code_index.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_compression.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_confidence.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_config.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_continuous_memory.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_convention_drift.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_cost_tracker.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_daemon.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_daemon_stability.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_daemon_watchdog.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_db.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_debate.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_debug_loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_debug_loop_smoke.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_dep_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_doctor.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_document_index.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_document_reader.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_document_tool.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_document_writer.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_domain_skills.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_eval_harness.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_event_log.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_failure_taxonomy.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_file_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_git_workflow.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_github_actions.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_health.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_hermetic_sandbox.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_identity_rbac.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_impact_analysis.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_impact_analyzer.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_impact_graph.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_impact_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_injection_gate.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_leaderboard.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_local_models.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_loop_p3.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_mcp_server.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_memory.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_migrations.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_mock_provider.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_model_config.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_orchestrator.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_package.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_permissions.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_phase2_modules.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_phone_ui.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_playwright_tool.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_plugin_sdk.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_protocol_version.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_provenance.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_proxy_server.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_quality_integration.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_reasoning_toggle.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_redaction.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_regression_collector.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_regression_guard_integration.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_regression_runner.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_replay.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_resilience.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_result_cache.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_review_gate_expanded.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_risk_scorer.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_rollback.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_router.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_router_compressor_conventions.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_router_escalation.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_sandbox.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_scoring.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_search_tools.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_self_healing.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_semantic_edit.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_sentry_integration.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_session_checkpoint.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_session_controller.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_session_restore.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_signal_handling.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_swebench_adapter.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_swebench_runner.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_system_prompt.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_team_config.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tool_cache.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tool_orchestrator.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tool_timeout.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tools_registry.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_tunnel_qr.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_usage_analytics.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_verification_graph.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_verification_loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_voice_loop.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_voice_providers.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_whole_codebase.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/test_work_director.py +0 -0
- {gdmcode-0.1.3 → gdmcode-0.1.5}/tests/voice/__init__.py +0 -0
- {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
|
+
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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.
|
|
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()
|