claude-mpm 5.4.85__py3-none-any.whl â 5.6.76__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md â CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +109 -706
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +128 -16
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +84 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +345 -40
- claude_mpm/cli/startup_display.py +76 -7
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +149 -0
- claude_mpm/commander/chat/commands.py +124 -0
- claude_mpm/commander/chat/repl.py +1957 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +603 -0
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +392 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +233 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +57 -0
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +868 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +127 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +403 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +362 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +35 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +53 -4
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +39 -13
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +52 -12
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js â 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js â 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js â 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js â 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js â 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js â B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js â B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js â BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js â BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js â BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js â BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js â BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js â Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js â C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js â C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js â C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js â CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js â CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js â CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js â CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js â CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js â CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js â CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js â CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js â CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js â D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js â D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js â D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js â DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js â DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js â DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js â DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js â DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js â D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js â DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js â DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js â DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js â DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js â IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js â JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js â R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js â Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js â qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js â ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js â wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js â app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js â 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js â 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
- claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +291 -59
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +41 -26
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +22 -15
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +46 -19
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +325 -0
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +111 -16
- claude_mpm/services/pm_skills_deployer.py +261 -87
- claude_mpm/services/skills/git_skill_source_manager.py +130 -10
- claude_mpm/services/skills/selective_skill_deployer.py +142 -16
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/{pm-teaching-mode â mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.76.dist-info/METADATA +416 -0
- {claude_mpm-5.4.85.dist-info â claude_mpm-5.6.76.dist-info}/RECORD +312 -175
- {claude_mpm-5.4.85.dist-info â claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.85.dist-info â claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md â mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns â mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking â mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow â mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration â mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols â mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info â claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info â claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info â claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OAuth management commands for claude-mpm CLI.
|
|
3
|
+
|
|
4
|
+
WHY: Users need a way to manage OAuth authentication for MCP services
|
|
5
|
+
that require OAuth2 flows (e.g., Google Workspace) directly from the terminal.
|
|
6
|
+
|
|
7
|
+
DESIGN DECISIONS:
|
|
8
|
+
- Use BaseCommand for consistent CLI patterns
|
|
9
|
+
- Reuse OAuth logic from commander/chat/repl.py
|
|
10
|
+
- Support multiple credential sources: .env.local, .env, environment variables
|
|
11
|
+
- Provide clear feedback during OAuth flow
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
from rich.panel import Panel
|
|
22
|
+
from rich.table import Table
|
|
23
|
+
|
|
24
|
+
from ..shared import BaseCommand, CommandResult
|
|
25
|
+
|
|
26
|
+
console = Console()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load_oauth_credentials_from_env_files() -> tuple[str | None, str | None]:
|
|
30
|
+
"""Load OAuth credentials from .env files.
|
|
31
|
+
|
|
32
|
+
Checks .env.local first (user overrides), then .env.
|
|
33
|
+
Returns tuple of (client_id, client_secret), either may be None.
|
|
34
|
+
"""
|
|
35
|
+
client_id = None
|
|
36
|
+
client_secret = None
|
|
37
|
+
|
|
38
|
+
# Priority order: .env.local first (user overrides), then .env
|
|
39
|
+
env_files = [".env.local", ".env"]
|
|
40
|
+
|
|
41
|
+
for env_file in env_files:
|
|
42
|
+
env_path = Path.cwd() / env_file
|
|
43
|
+
if env_path.exists():
|
|
44
|
+
try:
|
|
45
|
+
with open(env_path) as f:
|
|
46
|
+
for line in f:
|
|
47
|
+
line = line.strip()
|
|
48
|
+
# Skip empty lines and comments
|
|
49
|
+
if not line or line.startswith("#"):
|
|
50
|
+
continue
|
|
51
|
+
if "=" in line:
|
|
52
|
+
key, _, value = line.partition("=")
|
|
53
|
+
key = key.strip()
|
|
54
|
+
value = value.strip().strip('"').strip("'")
|
|
55
|
+
|
|
56
|
+
if key == "GOOGLE_OAUTH_CLIENT_ID" and not client_id:
|
|
57
|
+
client_id = value
|
|
58
|
+
elif (
|
|
59
|
+
key == "GOOGLE_OAUTH_CLIENT_SECRET"
|
|
60
|
+
and not client_secret
|
|
61
|
+
):
|
|
62
|
+
client_secret = value
|
|
63
|
+
|
|
64
|
+
# If we found both, no need to check more files
|
|
65
|
+
if client_id and client_secret:
|
|
66
|
+
break
|
|
67
|
+
except Exception: # nosec B110 - intentionally ignore .env file read errors
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
return client_id, client_secret
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class OAuthCommand(BaseCommand):
|
|
74
|
+
"""OAuth management command for MCP services."""
|
|
75
|
+
|
|
76
|
+
def __init__(self):
|
|
77
|
+
super().__init__("oauth")
|
|
78
|
+
|
|
79
|
+
def validate_args(self, args) -> str | None:
|
|
80
|
+
"""Validate command arguments."""
|
|
81
|
+
# If no oauth_command specified, default to 'list'
|
|
82
|
+
if not hasattr(args, "oauth_command") or not args.oauth_command:
|
|
83
|
+
args.oauth_command = None # Will show help
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
valid_commands = ["list", "setup", "status", "revoke", "refresh"]
|
|
87
|
+
if args.oauth_command not in valid_commands:
|
|
88
|
+
return f"Unknown oauth command: {args.oauth_command}. Valid commands: {', '.join(valid_commands)}"
|
|
89
|
+
|
|
90
|
+
# Validate service_name for commands that require it
|
|
91
|
+
if args.oauth_command in ["setup", "status", "revoke", "refresh"]:
|
|
92
|
+
if not hasattr(args, "service_name") or not args.service_name:
|
|
93
|
+
return f"oauth {args.oauth_command} requires a service name"
|
|
94
|
+
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def run(self, args) -> CommandResult:
|
|
98
|
+
"""Execute the OAuth command."""
|
|
99
|
+
# If no subcommand, show help
|
|
100
|
+
if not hasattr(args, "oauth_command") or not args.oauth_command:
|
|
101
|
+
self._show_help()
|
|
102
|
+
return CommandResult.success_result("Help displayed")
|
|
103
|
+
|
|
104
|
+
if args.oauth_command == "list":
|
|
105
|
+
return self._list_services(args)
|
|
106
|
+
if args.oauth_command == "setup":
|
|
107
|
+
return self._setup_oauth(args)
|
|
108
|
+
if args.oauth_command == "status":
|
|
109
|
+
return self._show_status(args)
|
|
110
|
+
if args.oauth_command == "revoke":
|
|
111
|
+
return self._revoke_tokens(args)
|
|
112
|
+
if args.oauth_command == "refresh":
|
|
113
|
+
return self._refresh_tokens(args)
|
|
114
|
+
|
|
115
|
+
return CommandResult.error_result(
|
|
116
|
+
f"Unknown oauth command: {args.oauth_command}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def _show_help(self) -> None:
|
|
120
|
+
"""Display OAuth command help."""
|
|
121
|
+
help_text = """
|
|
122
|
+
[bold]OAuth Commands:[/bold]
|
|
123
|
+
oauth list List OAuth-capable MCP services
|
|
124
|
+
oauth setup <service> Set up OAuth authentication for a service
|
|
125
|
+
oauth status <service> Show OAuth token status for a service
|
|
126
|
+
oauth revoke <service> Revoke OAuth tokens for a service
|
|
127
|
+
oauth refresh <service> Refresh OAuth tokens for a service
|
|
128
|
+
|
|
129
|
+
[bold]Examples:[/bold]
|
|
130
|
+
claude-mpm oauth list
|
|
131
|
+
claude-mpm oauth setup workspace-mcp
|
|
132
|
+
claude-mpm oauth status workspace-mcp
|
|
133
|
+
"""
|
|
134
|
+
console.print(help_text)
|
|
135
|
+
|
|
136
|
+
def _list_services(self, args) -> CommandResult:
|
|
137
|
+
"""List OAuth-capable MCP services."""
|
|
138
|
+
try:
|
|
139
|
+
from claude_mpm.services.mcp_service_registry import MCPServiceRegistry
|
|
140
|
+
|
|
141
|
+
services = MCPServiceRegistry.list_all()
|
|
142
|
+
oauth_services = [s for s in services if s.oauth_provider]
|
|
143
|
+
|
|
144
|
+
if not oauth_services:
|
|
145
|
+
console.print("[yellow]No OAuth-capable services found.[/yellow]")
|
|
146
|
+
return CommandResult.success_result("No OAuth services found")
|
|
147
|
+
|
|
148
|
+
# Check output format
|
|
149
|
+
output_format = getattr(args, "format", "table")
|
|
150
|
+
|
|
151
|
+
if output_format == "json":
|
|
152
|
+
data = [
|
|
153
|
+
{
|
|
154
|
+
"name": s.name,
|
|
155
|
+
"description": s.description,
|
|
156
|
+
"oauth_provider": s.oauth_provider,
|
|
157
|
+
"oauth_scopes": s.oauth_scopes,
|
|
158
|
+
"required_env": s.required_env,
|
|
159
|
+
}
|
|
160
|
+
for s in oauth_services
|
|
161
|
+
]
|
|
162
|
+
console.print(json.dumps(data, indent=2))
|
|
163
|
+
return CommandResult.success_result(
|
|
164
|
+
"Services listed", data={"services": data}
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Table format
|
|
168
|
+
table = Table(title="OAuth-Capable MCP Services")
|
|
169
|
+
table.add_column("Service", style="cyan")
|
|
170
|
+
table.add_column("Provider", style="green")
|
|
171
|
+
table.add_column("Description", style="white")
|
|
172
|
+
|
|
173
|
+
for service in oauth_services:
|
|
174
|
+
table.add_row(
|
|
175
|
+
service.name,
|
|
176
|
+
service.oauth_provider or "",
|
|
177
|
+
service.description,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
console.print(table)
|
|
181
|
+
return CommandResult.success_result(
|
|
182
|
+
f"Found {len(oauth_services)} OAuth-capable service(s)"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
except ImportError:
|
|
186
|
+
return CommandResult.error_result("MCP Service Registry not available")
|
|
187
|
+
except Exception as e:
|
|
188
|
+
return CommandResult.error_result(f"Error listing services: {e}")
|
|
189
|
+
|
|
190
|
+
def _setup_oauth(self, args) -> CommandResult:
|
|
191
|
+
"""Set up OAuth for a service."""
|
|
192
|
+
service_name = args.service_name
|
|
193
|
+
|
|
194
|
+
# Get service info from registry to get provider and scopes
|
|
195
|
+
try:
|
|
196
|
+
from claude_mpm.services.mcp_service_registry import MCPServiceRegistry
|
|
197
|
+
|
|
198
|
+
service = MCPServiceRegistry.get(service_name)
|
|
199
|
+
if not service:
|
|
200
|
+
return CommandResult.error_result(f"Service '{service_name}' not found")
|
|
201
|
+
|
|
202
|
+
provider_name = service.oauth_provider
|
|
203
|
+
if not provider_name:
|
|
204
|
+
return CommandResult.error_result(
|
|
205
|
+
f"Service '{service_name}' does not use OAuth"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
scopes = service.oauth_scopes or None
|
|
209
|
+
except ImportError:
|
|
210
|
+
return CommandResult.error_result("MCP Service Registry not available")
|
|
211
|
+
|
|
212
|
+
# Priority: 1) .env files, 2) environment variables, 3) interactive prompt
|
|
213
|
+
client_id, client_secret = _load_oauth_credentials_from_env_files()
|
|
214
|
+
|
|
215
|
+
# Fall back to environment variables if not found in .env files
|
|
216
|
+
if not client_id:
|
|
217
|
+
client_id = os.environ.get("GOOGLE_OAUTH_CLIENT_ID")
|
|
218
|
+
if not client_secret:
|
|
219
|
+
client_secret = os.environ.get("GOOGLE_OAUTH_CLIENT_SECRET")
|
|
220
|
+
|
|
221
|
+
# Set credentials in environment so OAuth provider can access them
|
|
222
|
+
if client_id and client_secret:
|
|
223
|
+
os.environ["GOOGLE_OAUTH_CLIENT_ID"] = client_id
|
|
224
|
+
os.environ["GOOGLE_OAUTH_CLIENT_SECRET"] = client_secret
|
|
225
|
+
|
|
226
|
+
# If credentials missing, prompt for them interactively
|
|
227
|
+
if not client_id or not client_secret:
|
|
228
|
+
console.print("\n[yellow]Google OAuth credentials not found.[/yellow]")
|
|
229
|
+
console.print("Checked: .env.local, .env, and environment variables.\n")
|
|
230
|
+
console.print(
|
|
231
|
+
"Get credentials from: https://console.cloud.google.com/apis/credentials\n"
|
|
232
|
+
)
|
|
233
|
+
console.print("[dim]Tip: Add to .env.local for automatic loading:[/dim]")
|
|
234
|
+
console.print('[dim] GOOGLE_OAUTH_CLIENT_ID="your-client-id"[/dim]')
|
|
235
|
+
console.print(
|
|
236
|
+
'[dim] GOOGLE_OAUTH_CLIENT_SECRET="your-client-secret"[/dim]\n' # pragma: allowlist secret
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
from prompt_toolkit import prompt as pt_prompt
|
|
241
|
+
|
|
242
|
+
client_id = pt_prompt("Enter GOOGLE_OAUTH_CLIENT_ID: ")
|
|
243
|
+
if not client_id.strip():
|
|
244
|
+
return CommandResult.error_result("Client ID is required")
|
|
245
|
+
|
|
246
|
+
client_secret = pt_prompt(
|
|
247
|
+
"Enter GOOGLE_OAUTH_CLIENT_SECRET: ", is_password=True
|
|
248
|
+
)
|
|
249
|
+
if not client_secret.strip():
|
|
250
|
+
return CommandResult.error_result("Client Secret is required")
|
|
251
|
+
|
|
252
|
+
# Set in environment for this session
|
|
253
|
+
os.environ["GOOGLE_OAUTH_CLIENT_ID"] = client_id.strip()
|
|
254
|
+
os.environ["GOOGLE_OAUTH_CLIENT_SECRET"] = client_secret.strip()
|
|
255
|
+
console.print("\n[green]Credentials set for this session.[/green]")
|
|
256
|
+
|
|
257
|
+
except (EOFError, KeyboardInterrupt):
|
|
258
|
+
return CommandResult.error_result("Credential entry cancelled")
|
|
259
|
+
except ImportError:
|
|
260
|
+
return CommandResult.error_result(
|
|
261
|
+
"prompt_toolkit not available for interactive input. "
|
|
262
|
+
"Please set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET environment variables."
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Run OAuth flow
|
|
266
|
+
try:
|
|
267
|
+
from claude_mpm.auth import OAuthManager
|
|
268
|
+
from claude_mpm.auth.callback_server import DEFAULT_PORT
|
|
269
|
+
from claude_mpm.auth.providers.google import OAuthError
|
|
270
|
+
|
|
271
|
+
manager = OAuthManager()
|
|
272
|
+
|
|
273
|
+
# Get the actual callback port from the server
|
|
274
|
+
callback_port = DEFAULT_PORT
|
|
275
|
+
no_browser = getattr(args, "no_browser", False)
|
|
276
|
+
|
|
277
|
+
console.print(f"\n[cyan]Setting up OAuth for '{service_name}'...[/cyan]")
|
|
278
|
+
console.print(
|
|
279
|
+
f"Callback server listening on http://localhost:{callback_port}/callback"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if not no_browser:
|
|
283
|
+
console.print("Opening browser for authentication...")
|
|
284
|
+
else:
|
|
285
|
+
console.print(
|
|
286
|
+
"[yellow]Browser auto-open disabled. Please open the URL manually.[/yellow]"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Run async OAuth flow - authenticate returns OAuthToken directly
|
|
290
|
+
# and raises OAuthError on failure
|
|
291
|
+
token = asyncio.run(
|
|
292
|
+
manager.authenticate(
|
|
293
|
+
service_name=service_name,
|
|
294
|
+
provider_name=provider_name,
|
|
295
|
+
scopes=scopes,
|
|
296
|
+
open_browser=not no_browser,
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Success - token was returned
|
|
301
|
+
console.print(f"\n[green]OAuth setup complete for '{service_name}'[/green]")
|
|
302
|
+
if token.expires_at:
|
|
303
|
+
console.print(f" Token expires: {token.expires_at}")
|
|
304
|
+
return CommandResult.success_result(
|
|
305
|
+
f"OAuth setup complete for '{service_name}'"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
except OAuthError as e:
|
|
309
|
+
return CommandResult.error_result(f"OAuth setup failed: {e}")
|
|
310
|
+
except ImportError as e:
|
|
311
|
+
return CommandResult.error_result(f"OAuth module not available: {e}")
|
|
312
|
+
except Exception as e:
|
|
313
|
+
return CommandResult.error_result(f"Error during OAuth setup: {e}")
|
|
314
|
+
|
|
315
|
+
def _show_status(self, args) -> CommandResult:
|
|
316
|
+
"""Show OAuth token status for a service."""
|
|
317
|
+
service_name = args.service_name
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
from claude_mpm.auth import OAuthManager
|
|
321
|
+
from claude_mpm.auth.models import TokenStatus
|
|
322
|
+
|
|
323
|
+
manager = OAuthManager()
|
|
324
|
+
# get_status is synchronous and returns (TokenStatus, StoredToken | None)
|
|
325
|
+
token_status, stored_token = manager.get_status(service_name)
|
|
326
|
+
|
|
327
|
+
if token_status == TokenStatus.MISSING or stored_token is None:
|
|
328
|
+
console.print(
|
|
329
|
+
f"[yellow]No OAuth tokens found for '{service_name}'[/yellow]"
|
|
330
|
+
)
|
|
331
|
+
return CommandResult.success_result(
|
|
332
|
+
f"No tokens found for '{service_name}'"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Build status dict for display
|
|
336
|
+
is_valid = token_status == TokenStatus.VALID
|
|
337
|
+
status_data = {
|
|
338
|
+
"valid": is_valid,
|
|
339
|
+
"status": token_status.name,
|
|
340
|
+
"expires_at": stored_token.token.expires_at,
|
|
341
|
+
"scopes": stored_token.token.scopes,
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# Check output format
|
|
345
|
+
output_format = getattr(args, "format", "table")
|
|
346
|
+
|
|
347
|
+
if output_format == "json":
|
|
348
|
+
console.print(json.dumps(status_data, indent=2, default=str))
|
|
349
|
+
return CommandResult.success_result(
|
|
350
|
+
"Status displayed", data=status_data
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Table format
|
|
354
|
+
self._print_token_status(service_name, status_data)
|
|
355
|
+
return CommandResult.success_result("Status displayed")
|
|
356
|
+
|
|
357
|
+
except ImportError:
|
|
358
|
+
return CommandResult.error_result("OAuth module not available")
|
|
359
|
+
except Exception as e:
|
|
360
|
+
return CommandResult.error_result(f"Error checking status: {e}")
|
|
361
|
+
|
|
362
|
+
def _print_token_status(self, name: str, status: dict[str, Any]) -> None:
|
|
363
|
+
"""Print token status information."""
|
|
364
|
+
panel_content = []
|
|
365
|
+
panel_content.append(f"[bold]Service:[/bold] {name}")
|
|
366
|
+
panel_content.append("[bold]Stored:[/bold] Yes")
|
|
367
|
+
|
|
368
|
+
if status.get("valid"):
|
|
369
|
+
panel_content.append("[bold]Status:[/bold] [green]Valid[/green]")
|
|
370
|
+
else:
|
|
371
|
+
panel_content.append("[bold]Status:[/bold] [red]Invalid/Expired[/red]")
|
|
372
|
+
|
|
373
|
+
if status.get("expires_at"):
|
|
374
|
+
panel_content.append(f"[bold]Expires:[/bold] {status['expires_at']}")
|
|
375
|
+
|
|
376
|
+
if status.get("scopes"):
|
|
377
|
+
scopes = ", ".join(status["scopes"])
|
|
378
|
+
panel_content.append(f"[bold]Scopes:[/bold] {scopes}")
|
|
379
|
+
|
|
380
|
+
panel = Panel(
|
|
381
|
+
"\n".join(panel_content),
|
|
382
|
+
title="OAuth Token Status",
|
|
383
|
+
border_style="green" if status.get("valid") else "red",
|
|
384
|
+
)
|
|
385
|
+
console.print(panel)
|
|
386
|
+
|
|
387
|
+
def _revoke_tokens(self, args) -> CommandResult:
|
|
388
|
+
"""Revoke OAuth tokens for a service."""
|
|
389
|
+
service_name = args.service_name
|
|
390
|
+
|
|
391
|
+
# Confirm unless -y flag
|
|
392
|
+
if not getattr(args, "yes", False):
|
|
393
|
+
console.print(
|
|
394
|
+
f"[yellow]This will revoke OAuth tokens for '{service_name}'.[/yellow]"
|
|
395
|
+
)
|
|
396
|
+
try:
|
|
397
|
+
from prompt_toolkit import prompt as pt_prompt
|
|
398
|
+
|
|
399
|
+
confirm = pt_prompt("Are you sure? (y/N): ")
|
|
400
|
+
if confirm.lower() not in ("y", "yes"):
|
|
401
|
+
return CommandResult.success_result("Revocation cancelled")
|
|
402
|
+
except (EOFError, KeyboardInterrupt):
|
|
403
|
+
return CommandResult.success_result("Revocation cancelled")
|
|
404
|
+
except ImportError:
|
|
405
|
+
# No prompt_toolkit, proceed without confirmation
|
|
406
|
+
pass
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
from claude_mpm.auth import OAuthManager
|
|
410
|
+
|
|
411
|
+
manager = OAuthManager()
|
|
412
|
+
|
|
413
|
+
console.print(f"[cyan]Revoking OAuth tokens for '{service_name}'...[/cyan]")
|
|
414
|
+
# revoke() returns bool directly
|
|
415
|
+
revoked = asyncio.run(manager.revoke(service_name))
|
|
416
|
+
|
|
417
|
+
if revoked:
|
|
418
|
+
console.print(
|
|
419
|
+
f"[green]OAuth tokens revoked for '{service_name}'[/green]"
|
|
420
|
+
)
|
|
421
|
+
return CommandResult.success_result(
|
|
422
|
+
f"Tokens revoked for '{service_name}'"
|
|
423
|
+
)
|
|
424
|
+
return CommandResult.error_result(
|
|
425
|
+
f"Failed to revoke tokens for '{service_name}'"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
except ImportError:
|
|
429
|
+
return CommandResult.error_result("OAuth module not available")
|
|
430
|
+
except Exception as e:
|
|
431
|
+
return CommandResult.error_result(f"Error revoking tokens: {e}")
|
|
432
|
+
|
|
433
|
+
def _refresh_tokens(self, args) -> CommandResult:
|
|
434
|
+
"""Refresh OAuth tokens for a service."""
|
|
435
|
+
service_name = args.service_name
|
|
436
|
+
|
|
437
|
+
try:
|
|
438
|
+
from claude_mpm.auth import OAuthManager
|
|
439
|
+
from claude_mpm.auth.providers.google import OAuthError
|
|
440
|
+
|
|
441
|
+
manager = OAuthManager()
|
|
442
|
+
|
|
443
|
+
console.print(
|
|
444
|
+
f"[cyan]Refreshing OAuth tokens for '{service_name}'...[/cyan]"
|
|
445
|
+
)
|
|
446
|
+
# refresh_if_needed() returns Optional[OAuthToken]
|
|
447
|
+
token = asyncio.run(manager.refresh_if_needed(service_name))
|
|
448
|
+
|
|
449
|
+
if token is not None:
|
|
450
|
+
console.print(
|
|
451
|
+
f"[green]OAuth tokens refreshed for '{service_name}'[/green]"
|
|
452
|
+
)
|
|
453
|
+
if token.expires_at:
|
|
454
|
+
console.print(f" New expiry: {token.expires_at}")
|
|
455
|
+
return CommandResult.success_result(
|
|
456
|
+
f"Tokens refreshed for '{service_name}'"
|
|
457
|
+
)
|
|
458
|
+
return CommandResult.error_result(
|
|
459
|
+
f"Failed to refresh tokens for '{service_name}' - no token found or no refresh token available"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
except OAuthError as e:
|
|
463
|
+
return CommandResult.error_result(f"Failed to refresh: {e}")
|
|
464
|
+
except ImportError:
|
|
465
|
+
return CommandResult.error_result("OAuth module not available")
|
|
466
|
+
except Exception as e:
|
|
467
|
+
return CommandResult.error_result(f"Error refreshing tokens: {e}")
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def manage_oauth(args) -> int:
|
|
471
|
+
"""Main entry point for OAuth management commands.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
args: Parsed command line arguments
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
Exit code (0 for success, non-zero for errors)
|
|
478
|
+
"""
|
|
479
|
+
command = OAuthCommand()
|
|
480
|
+
result = command.execute(args)
|
|
481
|
+
return result.exit_code
|
claude_mpm/cli/commands/run.py
CHANGED
|
@@ -13,7 +13,7 @@ DESIGN DECISIONS:
|
|
|
13
13
|
- Support multiple output formats (json, yaml, table, text)
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import subprocess
|
|
16
|
+
import subprocess # nosec B404 - required for process management
|
|
17
17
|
import sys
|
|
18
18
|
from datetime import datetime, timezone
|
|
19
19
|
from typing import Optional
|
|
@@ -489,6 +489,18 @@ class RunCommand(BaseCommand):
|
|
|
489
489
|
if hasattr(args, "claude_args") and args.claude_args:
|
|
490
490
|
claude_args.extend(args.claude_args)
|
|
491
491
|
|
|
492
|
+
# Add --resume if flag is set
|
|
493
|
+
if getattr(args, "resume", False) and "--resume" not in claude_args:
|
|
494
|
+
claude_args.insert(0, "--resume")
|
|
495
|
+
|
|
496
|
+
# Add --chrome if flag is set
|
|
497
|
+
if getattr(args, "chrome", False) and "--chrome" not in claude_args:
|
|
498
|
+
claude_args.insert(0, "--chrome")
|
|
499
|
+
|
|
500
|
+
# Add --no-chrome if flag is set
|
|
501
|
+
if getattr(args, "no_chrome", False) and "--no-chrome" not in claude_args:
|
|
502
|
+
claude_args.insert(0, "--no-chrome")
|
|
503
|
+
|
|
492
504
|
# Create runner
|
|
493
505
|
runner = ClaudeRunner(
|
|
494
506
|
enable_tickets=enable_tickets,
|
|
@@ -553,7 +565,7 @@ class RunCommand(BaseCommand):
|
|
|
553
565
|
wrapper_path = get_scripts_dir() / "interactive_wrapper.py"
|
|
554
566
|
if wrapper_path.exists():
|
|
555
567
|
print("Starting interactive session with command interception...")
|
|
556
|
-
subprocess.run([sys.executable, str(wrapper_path)], check=False)
|
|
568
|
+
subprocess.run([sys.executable, str(wrapper_path)], check=False) # nosec B603 - trusted internal paths
|
|
557
569
|
else:
|
|
558
570
|
self.logger.warning(
|
|
559
571
|
"Interactive wrapper not found, falling back to normal mode"
|
|
@@ -907,6 +919,26 @@ def run_session_legacy(args):
|
|
|
907
919
|
else:
|
|
908
920
|
logger.info("[INFO]ī¸ --resume already in claude_args")
|
|
909
921
|
|
|
922
|
+
# Add --chrome to claude_args if the flag is set
|
|
923
|
+
chrome_flag_present = getattr(args, "chrome", False)
|
|
924
|
+
if chrome_flag_present:
|
|
925
|
+
logger.info("đ --chrome flag detected in args")
|
|
926
|
+
if "--chrome" not in raw_claude_args:
|
|
927
|
+
raw_claude_args = ["--chrome", *raw_claude_args]
|
|
928
|
+
logger.info("â
Added --chrome to claude_args")
|
|
929
|
+
else:
|
|
930
|
+
logger.info("âšī¸ --chrome already in claude_args")
|
|
931
|
+
|
|
932
|
+
# Add --no-chrome to claude_args if the flag is set
|
|
933
|
+
no_chrome_flag_present = getattr(args, "no_chrome", False)
|
|
934
|
+
if no_chrome_flag_present:
|
|
935
|
+
logger.info("đ --no-chrome flag detected in args")
|
|
936
|
+
if "--no-chrome" not in raw_claude_args:
|
|
937
|
+
raw_claude_args = ["--no-chrome", *raw_claude_args]
|
|
938
|
+
logger.info("â
Added --no-chrome to claude_args")
|
|
939
|
+
else:
|
|
940
|
+
logger.info("âšī¸ --no-chrome already in claude_args")
|
|
941
|
+
|
|
910
942
|
# Filter out claude-mpm specific flags before passing to Claude CLI
|
|
911
943
|
logger.debug(f"Pre-filter claude_args: {raw_claude_args}")
|
|
912
944
|
claude_args = filter_claude_mpm_args(raw_claude_args)
|
|
@@ -1044,7 +1076,7 @@ def run_session_legacy(args):
|
|
|
1044
1076
|
wrapper_path = get_scripts_dir() / "interactive_wrapper.py"
|
|
1045
1077
|
if wrapper_path.exists():
|
|
1046
1078
|
print("Starting interactive session with command interception...")
|
|
1047
|
-
subprocess.run([sys.executable, str(wrapper_path)], check=False)
|
|
1079
|
+
subprocess.run([sys.executable, str(wrapper_path)], check=False) # nosec B603 - trusted internal paths
|
|
1048
1080
|
else:
|
|
1049
1081
|
logger.warning("Interactive wrapper not found, falling back to normal mode")
|
|
1050
1082
|
runner.run_interactive(context)
|
|
@@ -11,6 +11,7 @@ for better UX. Handles errors gracefully with actionable messages.
|
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
import logging
|
|
14
|
+
import os
|
|
14
15
|
import re
|
|
15
16
|
|
|
16
17
|
from ...config.skill_sources import SkillSource, SkillSourceConfiguration
|
|
@@ -20,6 +21,33 @@ from ...services.skills.skill_discovery_service import SkillDiscoveryService
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
def _get_github_token(source: SkillSource | None = None) -> str | None:
|
|
25
|
+
"""Get GitHub token with source-specific override support.
|
|
26
|
+
|
|
27
|
+
Priority: source.token > GITHUB_TOKEN > GH_TOKEN
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
source: Optional SkillSource to check for per-source token
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
GitHub token if found, None otherwise
|
|
34
|
+
|
|
35
|
+
Security Note:
|
|
36
|
+
Token is never logged or printed to avoid exposure.
|
|
37
|
+
"""
|
|
38
|
+
# Priority 1: Per-source token (env var reference or direct)
|
|
39
|
+
if source and source.token:
|
|
40
|
+
if source.token.startswith("$"):
|
|
41
|
+
# Env var reference: $VAR_NAME -> os.environ.get("VAR_NAME")
|
|
42
|
+
env_var_name = source.token[1:]
|
|
43
|
+
return os.environ.get(env_var_name)
|
|
44
|
+
# Direct token (not recommended but supported)
|
|
45
|
+
return source.token
|
|
46
|
+
|
|
47
|
+
# Priority 2-3: Global environment variables
|
|
48
|
+
return os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
49
|
+
|
|
50
|
+
|
|
23
51
|
def _test_skill_repository_access(source: SkillSource) -> dict:
|
|
24
52
|
"""Test if skill repository is accessible via GitHub API.
|
|
25
53
|
|
|
@@ -58,7 +86,13 @@ def _test_skill_repository_access(source: SkillSource) -> dict:
|
|
|
58
86
|
# Test GitHub API access
|
|
59
87
|
api_url = f"https://api.github.com/repos/{owner_repo}"
|
|
60
88
|
|
|
61
|
-
|
|
89
|
+
# Build headers with authentication if token available
|
|
90
|
+
headers = {"Accept": "application/vnd.github+json"}
|
|
91
|
+
token = _get_github_token(source)
|
|
92
|
+
if token:
|
|
93
|
+
headers["Authorization"] = f"token {token}"
|
|
94
|
+
|
|
95
|
+
response = requests.get(api_url, headers=headers, timeout=10)
|
|
62
96
|
|
|
63
97
|
if response.status_code == 200:
|
|
64
98
|
return {"accessible": True, "error": None}
|
|
@@ -68,9 +102,14 @@ def _test_skill_repository_access(source: SkillSource) -> dict:
|
|
|
68
102
|
"error": f"Repository not found: {owner_repo}",
|
|
69
103
|
}
|
|
70
104
|
if response.status_code == 403:
|
|
105
|
+
error_msg = "Access denied (private repository or rate limit)"
|
|
106
|
+
if not token:
|
|
107
|
+
error_msg += (
|
|
108
|
+
". Try setting GITHUB_TOKEN environment variable for private repos"
|
|
109
|
+
)
|
|
71
110
|
return {
|
|
72
111
|
"accessible": False,
|
|
73
|
-
"error":
|
|
112
|
+
"error": error_msg,
|
|
74
113
|
}
|
|
75
114
|
return {
|
|
76
115
|
"accessible": False,
|
|
@@ -263,6 +302,15 @@ def handle_add_skill_source(args) -> int:
|
|
|
263
302
|
|
|
264
303
|
# Create new source
|
|
265
304
|
enabled = not args.disabled
|
|
305
|
+
token = getattr(args, "token", None)
|
|
306
|
+
|
|
307
|
+
# Security warning for direct tokens
|
|
308
|
+
if token and not token.startswith("$"):
|
|
309
|
+
print("â ī¸ Warning: Direct token values in config are not recommended")
|
|
310
|
+
print(" Consider using environment variable reference instead:")
|
|
311
|
+
print(" --token $MY_PRIVATE_TOKEN")
|
|
312
|
+
print()
|
|
313
|
+
|
|
266
314
|
source = SkillSource(
|
|
267
315
|
id=source_id,
|
|
268
316
|
type="git",
|
|
@@ -270,6 +318,7 @@ def handle_add_skill_source(args) -> int:
|
|
|
270
318
|
branch=args.branch,
|
|
271
319
|
priority=args.priority,
|
|
272
320
|
enabled=enabled,
|
|
321
|
+
token=token,
|
|
273
322
|
)
|
|
274
323
|
|
|
275
324
|
# Determine if we should test
|
|
@@ -538,6 +538,7 @@ class SkillsManagementCommand(BaseCommand):
|
|
|
538
538
|
toolchain = getattr(args, "toolchain", None)
|
|
539
539
|
categories = getattr(args, "categories", None)
|
|
540
540
|
force = getattr(args, "force", False)
|
|
541
|
+
deploy_all = getattr(args, "all", False)
|
|
541
542
|
|
|
542
543
|
if collection:
|
|
543
544
|
console.print(
|
|
@@ -548,14 +549,15 @@ class SkillsManagementCommand(BaseCommand):
|
|
|
548
549
|
"\n[bold cyan]Deploying skills from default collection...[/bold cyan]\n"
|
|
549
550
|
)
|
|
550
551
|
|
|
551
|
-
#
|
|
552
|
-
#
|
|
552
|
+
# Use selective deployment unless --all flag is provided
|
|
553
|
+
# Selective mode deploys only agent-referenced skills
|
|
554
|
+
# --all mode deploys all available skills from the collection
|
|
553
555
|
result = self.skills_deployer.deploy_skills(
|
|
554
556
|
collection=collection,
|
|
555
557
|
toolchain=toolchain,
|
|
556
558
|
categories=categories,
|
|
557
559
|
force=force,
|
|
558
|
-
selective=
|
|
560
|
+
selective=not deploy_all,
|
|
559
561
|
)
|
|
560
562
|
|
|
561
563
|
# Display results
|