multi-forge 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {multi_forge-0.3.0 → multi_forge-0.4.0}/PKG-INFO +4 -3
- {multi_forge-0.3.0 → multi_forge-0.4.0}/README.md +3 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/pyproject.toml +4 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/__init__.py +1 -1
- multi_forge-0.4.0/src/forge/cli/activity.py +151 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/auth.py +1 -3
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/backend.py +1 -3
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/claude.py +1 -3
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/config_cmd.py +114 -10
- multi_forge-0.4.0/src/forge/cli/editor.py +44 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/extensions.py +2 -5
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/gc.py +2 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/hooks/commands.py +16 -44
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/hooks/direct_commands.py +71 -3
- multi_forge-0.4.0/src/forge/cli/hooks/policy.py +212 -0
- multi_forge-0.4.0/src/forge/cli/hooks/protocols.py +53 -0
- multi_forge-0.4.0/src/forge/cli/launch_confirmation.py +134 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/main.py +70 -7
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/memory.py +20 -42
- multi_forge-0.3.0/src/forge/cli/session_handoff.py → multi_forge-0.4.0/src/forge/cli/memory_report.py +24 -22
- multi_forge-0.3.0/src/forge/cli/handoff.py → multi_forge-0.4.0/src/forge/cli/memory_writer.py +12 -12
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/policy.py +2 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/proxy.py +28 -16
- multi_forge-0.4.0/src/forge/cli/proxy_audit.py +253 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/proxy_costs.py +203 -55
- multi_forge-0.4.0/src/forge/cli/runtime.py +105 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/search.py +2 -26
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/session.py +8 -23
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/session_fork.py +226 -14
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/session_lifecycle.py +194 -130
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/status_line.py +490 -161
- multi_forge-0.4.0/src/forge/cli/statusline/__init__.py +9 -0
- multi_forge-0.4.0/src/forge/cli/statusline/context.py +144 -0
- multi_forge-0.4.0/src/forge/cli/statusline/names.py +61 -0
- multi_forge-0.4.0/src/forge/cli/statusline/palette.py +149 -0
- multi_forge-0.4.0/src/forge/cli/statusline/registry.py +379 -0
- multi_forge-0.4.0/src/forge/cli/statusline/throttle.py +172 -0
- multi_forge-0.4.0/src/forge/cli/transfer.py +186 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/workflow.py +57 -4
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/backends/litellm.yaml +12 -0
- multi_forge-0.4.0/src/forge/config/defaults/templates/anthropic-passthrough.yaml +30 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-anthropic-local.yaml +1 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-anthropic.yaml +3 -3
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-gemini.yaml +1 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-anthropic.yaml +3 -3
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-gemini-flash.yaml +4 -4
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-gemini.yaml +1 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-minimax.yaml +6 -4
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/loader.py +6 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/schema.py +243 -9
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/auth/capabilities.py +2 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/auth/template_secrets.py +23 -8
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/data/model_catalog.yaml +95 -3
- multi_forge-0.4.0/src/forge/core/invoker/__init__.py +23 -0
- multi_forge-0.4.0/src/forge/core/invoker/claude.py +457 -0
- multi_forge-0.4.0/src/forge/core/invoker/types.py +111 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/clients/litellm.py +58 -7
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/clients/openai_compat.py +26 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/clients/openrouter.py +8 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/credentials.py +20 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/types.py +6 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/models/__init__.py +0 -11
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/ops/gc.py +23 -12
- multi_forge-0.4.0/src/forge/core/ops/transfer.py +265 -0
- multi_forge-0.4.0/src/forge/core/ops/usage_summary.py +364 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/reactive/__init__.py +17 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/reactive/cost_tracking.py +72 -54
- multi_forge-0.4.0/src/forge/core/reactive/env.py +363 -0
- multi_forge-0.4.0/src/forge/core/reactive/headless_json.py +133 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/reactive/proxy.py +1 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/reactive/routing.py +2 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/reactive/session_runner.py +122 -25
- multi_forge-0.4.0/src/forge/core/reactive/structured_output.py +153 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/reactive/tagger.py +34 -3
- multi_forge-0.4.0/src/forge/core/runtime/__init__.py +27 -0
- multi_forge-0.4.0/src/forge/core/runtime/registry.py +211 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/state/__init__.py +8 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/state/io.py +18 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/transcript.py +1 -1
- multi_forge-0.4.0/src/forge/core/usage/__init__.py +56 -0
- multi_forge-0.4.0/src/forge/core/usage/billing.py +28 -0
- multi_forge-0.4.0/src/forge/core/usage/correlation.py +91 -0
- multi_forge-0.4.0/src/forge/core/usage/emit.py +440 -0
- multi_forge-0.4.0/src/forge/core/usage/ledger.py +306 -0
- multi_forge-0.4.0/src/forge/core/usage/vocabulary.py +70 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/workqueue/queue.py +12 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/__init__.py +3 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/exceptions.py +3 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/installer.py +6 -6
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/preset.py +2 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/tracking.py +2 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/deterministic/coding_standards.py +24 -12
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/semantic/supervisor.py +14 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/team/handlers.py +20 -4
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/types.py +9 -3
- multi_forge-0.4.0/src/forge/proxy/audit_logger.py +464 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/client_adapter.py +17 -7
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/converters.py +7 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/cost_logger.py +35 -10
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/cost_tracker.py +31 -28
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/data_models.py +6 -1
- multi_forge-0.4.0/src/forge/proxy/intercept.py +314 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/metrics.py +38 -8
- multi_forge-0.4.0/src/forge/proxy/passthrough.py +281 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/proxies.py +1 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/proxy_orchestrator.py +58 -7
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/server.py +586 -67
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/utils.py +27 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/adversarial.py +4 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/consensus.py +4 -0
- multi_forge-0.4.0/src/forge/review/engine.py +357 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/models.py +14 -9
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/runtime_config.py +141 -15
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/claude/__init__.py +14 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/claude/invoke.py +21 -4
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/claude/paths.py +14 -4
- multi_forge-0.4.0/src/forge/session/claude/relocate.py +171 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/manager.py +344 -50
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/memory_inheritance.py +3 -3
- multi_forge-0.3.0/src/forge/session/handoff_agent.py → multi_forge-0.4.0/src/forge/session/memory_writer.py +48 -39
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/models.py +46 -12
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/passport.py +1 -28
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/plan_resolution.py +1 -1
- multi_forge-0.4.0/src/forge/session/prev_sessions.py +236 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/project_memory.py +1 -34
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/shadow_curation.py +13 -2
- multi_forge-0.3.0/src/forge/session/handoff.py → multi_forge-0.4.0/src/forge/session/transfer.py +309 -87
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/sidecar/container.py +84 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/panel/SKILL.md +4 -4
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/SKILL.md +44 -44
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/10-resume.md +66 -5
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/11-config.md +4 -4
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/13-policy.md +18 -12
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/15-skills.md +6 -6
- multi_forge-0.3.0/src/skills/qa/resources/checklist/16-handoff.md → multi_forge-0.4.0/src/skills/qa/resources/checklist/16-memory.md +19 -14
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/17-info.md +16 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/18-disable.md +10 -10
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/20-cleanup.md +5 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/3-authentication.md +9 -22
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/4-proxy.md +89 -21
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/5-session.md +172 -54
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/6-hook.md +1 -1
- multi_forge-0.4.0/src/skills/qa/resources/checklist/7-costs.md +464 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/8-status-line.md +110 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/9-direct-commands.md +19 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist.md +13 -6
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/report-template.md +1 -1
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/scripts/start-container.sh +37 -18
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/SKILL.md +5 -5
- multi_forge-0.3.0/src/skills/review/references/claude-4.7.md → multi_forge-0.4.0/src/skills/review/references/claude-4.8.md +85 -69
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/references/gemini-3.1.md +2 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review-docs/SKILL.md +2 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/SKILL.md +2 -2
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/walkthrough/resources/checklist.md +1 -1
- multi_forge-0.3.0/src/forge/cli/hooks/policy.py +0 -151
- multi_forge-0.3.0/src/forge/cli/session_memory.py +0 -36
- multi_forge-0.3.0/src/forge/core/data/pricing.yaml +0 -140
- multi_forge-0.3.0/src/forge/core/models/pricing.py +0 -165
- multi_forge-0.3.0/src/forge/core/reactive/env.py +0 -180
- multi_forge-0.3.0/src/forge/core/reactive/structured_output.py +0 -62
- multi_forge-0.3.0/src/forge/review/engine.py +0 -358
- multi_forge-0.3.0/src/forge/session/prev_sessions.py +0 -128
- multi_forge-0.3.0/src/skills/qa/resources/checklist/7-costs.md +0 -309
- {multi_forge-0.3.0 → multi_forge-0.4.0}/.gitignore +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/LICENSE +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/NOTICE +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/agents/.gitkeep +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/commands/.gitkeep +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/backend/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/backend/adapters/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/backend/adapters/litellm.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/backend/creation.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/backend/registry.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/guards.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/hooks/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/hooks/_group.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/hooks/_helpers.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/hooks/install.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/hooks/read_hygiene.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/hooks/verification.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/logs.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/output.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/session_addendum.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/cli/session_manage.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/dataclass_utils.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/backends/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-gemini-flash-local.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-gemini-local.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-gemini-test.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-openai-codex-local.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-openai-local.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/litellm-openai.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-deepseek.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-glm.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-kimi.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-openai-codex.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-openai.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/config/defaults/templates/openrouter-qwen.yaml +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/auth/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/auth/credentials_file.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/auth/protocols.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/auth/secrets.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/data/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/data/system_prompt_addendums/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/data/system_prompt_addendums/gemini.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/data/system_prompt_addendums/openai.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/clients/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/clients/base.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/detection.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/errors.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/llm/protocols.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/logging.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/models/catalog.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/models/types.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/naming.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/ops/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/ops/context.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/ops/proxy.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/ops/resolution.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/ops/session.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/ops/session_context.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/paths.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/process.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/reactive/throttle.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/state/exceptions.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/state/lock.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/state/timestamps.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/typing_helpers.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/workqueue/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/core/workqueue/types.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/cli.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/hooks.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/models.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/settings_merge.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/install/version.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/deterministic/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/deterministic/base.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/deterministic/registry.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/deterministic/tdd.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/engine.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/protocols.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/queries.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/semantic/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/semantic/promotion.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/semantic/verdict.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/store.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/team/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/team/config.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/team/prompts.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/workflow/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/workflow/branches.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/workflow/config.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/workflow/divergence.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/workflow/policy.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/policy/workflow/stages.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/base_client.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/client_factory.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/error_hints.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/model_spec.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/proxy_identity.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/proxy/proxy_startup.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/resources/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/resources/codereview-performance.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/resources/codereview-quick.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/resources/codereview-security.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/resources/codereview.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/resources/docreview-quick.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/resources/docreview.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/resources/thinkdeep.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/routing.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/review/synthesis.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/bm25_store.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/content_store.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/engine.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/exceptions.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/extractor.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/index_state.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/store.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/search/tokenizer.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/active.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/artifacts.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/claude/cleanup.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/cleanup.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/config.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/direct_model.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/effective.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/exceptions.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/hooks/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/hooks/models.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/hooks/session_start.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/identity.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/index.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/overrides.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/store.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/validation.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/worktree/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/worktree/cleanup.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/worktree/config_copy.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/session/worktree/create.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/sidecar/__init__.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/sidecar/docker.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/forge/sidecar/secrets.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/analyze/SKILL.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/challenge/SKILL.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/consensus/SKILL.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/consensus/resources/code_consensus_evaluation.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/consensus/resources/consensus_evaluation.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/consensus/resources/synthesis.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/debate/SKILL.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/debate/resources/code_debate_evaluation.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/debate/resources/debate_evaluation.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/panel/resources/synthesis.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/0-enable.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/1-preflight.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/12-search.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/14-workflow.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/19-uninstall.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/resources/checklist/2-extension.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/qa/scripts/walkthrough-state.py +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/references/claude-4.6.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/references/gpt-5.5.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/references/skills-writing-guide.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/resources/code-anthropic.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/resources/code-gemini.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/resources/code-openai.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review/resources/code.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review-docs/resources/docs-anthropic.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review-docs/resources/docs-gemini.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review-docs/resources/docs-openai.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/review-docs/resources/docs.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/smoke-test/SKILL.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/smoke-test/scripts/smoke-test.sh +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/resources/code-anthropic.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/resources/code-gemini.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/resources/code-openai.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/resources/code.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/resources/docs-anthropic.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/resources/docs-gemini.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/resources/docs-openai.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/understand/resources/docs.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/walkthrough/SKILL.md +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/walkthrough/scripts/run-in-repo.sh +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/walkthrough/scripts/setup-test-repo.sh +0 -0
- {multi_forge-0.3.0 → multi_forge-0.4.0}/src/skills/walkthrough/scripts/walkthrough-state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-forge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Multi-runtime agent toolkit: proxy routing, cost control, session management, policy enforcement, and workflow orchestration
|
|
5
5
|
Project-URL: Homepage, https://github.com/hapa1i/multi-forge
|
|
6
6
|
Project-URL: Repository, https://github.com/hapa1i/multi-forge
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
# Multi-Forge
|
|
43
43
|
|
|
44
44
|
<p align="left">
|
|
45
|
-
<img src="assets/logo.jpg" alt="Dusk" width=
|
|
45
|
+
<img src="assets/logo.jpg" alt="Dusk" width=240">
|
|
46
46
|
</p>
|
|
47
47
|
|
|
48
48
|
[](https://pypi.org/project/multi-forge/)
|
|
@@ -96,7 +96,7 @@ Running `claude` directly bypasses session tracking. When you launch through For
|
|
|
96
96
|
| Hook-driven artifacts | No | Yes -- plan snapshots, transcript capture |
|
|
97
97
|
| Policy enforcement | No | Yes -- TDD, coding standards, supervisor |
|
|
98
98
|
| Search across sessions | No | Yes -- `forge search` indexes transcripts |
|
|
99
|
-
|
|
|
99
|
+
| Project memory | No | Yes -- passported docs auto-updated on exit |
|
|
100
100
|
|
|
101
101
|
Even without a proxy, `forge session start` gives you session tracking, hooks, and the status line (direct mode is the
|
|
102
102
|
default). The proxy adds multi-model routing on top. (`forge claude start` is also available as a bare launcher with
|
|
@@ -209,6 +209,7 @@ inside Claude Code for an interactive walkthrough.
|
|
|
209
209
|
| ---------------------- | -------------------------------------------- |
|
|
210
210
|
| `forge claude` | Bare launch, settings preset management |
|
|
211
211
|
| `forge session` | Named sessions, worktrees, resume, fork |
|
|
212
|
+
| `forge memory` | Project memory passports, shadow proposals |
|
|
212
213
|
| `forge proxy` | Model routing, templates, tier mappings |
|
|
213
214
|
| `forge authentication` | Credential management (`credentials.yaml`) |
|
|
214
215
|
| `forge policy` | Policy enforcement, plan supervision |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Multi-Forge
|
|
2
2
|
|
|
3
3
|
<p align="left">
|
|
4
|
-
<img src="assets/logo.jpg" alt="Dusk" width=
|
|
4
|
+
<img src="assets/logo.jpg" alt="Dusk" width=240">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
[](https://pypi.org/project/multi-forge/)
|
|
@@ -55,7 +55,7 @@ Running `claude` directly bypasses session tracking. When you launch through For
|
|
|
55
55
|
| Hook-driven artifacts | No | Yes -- plan snapshots, transcript capture |
|
|
56
56
|
| Policy enforcement | No | Yes -- TDD, coding standards, supervisor |
|
|
57
57
|
| Search across sessions | No | Yes -- `forge search` indexes transcripts |
|
|
58
|
-
|
|
|
58
|
+
| Project memory | No | Yes -- passported docs auto-updated on exit |
|
|
59
59
|
|
|
60
60
|
Even without a proxy, `forge session start` gives you session tracking, hooks, and the status line (direct mode is the
|
|
61
61
|
default). The proxy adds multi-model routing on top. (`forge claude start` is also available as a bare launcher with
|
|
@@ -168,6 +168,7 @@ inside Claude Code for an interactive walkthrough.
|
|
|
168
168
|
| ---------------------- | -------------------------------------------- |
|
|
169
169
|
| `forge claude` | Bare launch, settings preset management |
|
|
170
170
|
| `forge session` | Named sessions, worktrees, resume, fork |
|
|
171
|
+
| `forge memory` | Project memory passports, shadow proposals |
|
|
171
172
|
| `forge proxy` | Model routing, templates, tier mappings |
|
|
172
173
|
| `forge authentication` | Credential management (`credentials.yaml`) |
|
|
173
174
|
| `forge policy` | Policy enforcement, plan supervision |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "multi-forge"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Multi-runtime agent toolkit: proxy routing, cost control, session management, policy enforcement, and workflow orchestration"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -122,10 +122,11 @@ disallow_untyped_calls = false
|
|
|
122
122
|
constraint-dependencies = [
|
|
123
123
|
"filelock>=3.20.3",
|
|
124
124
|
"virtualenv>=20.36.1",
|
|
125
|
-
"aiohttp>=3.
|
|
125
|
+
"aiohttp>=3.14.0",
|
|
126
126
|
"cryptography>=46.0.7",
|
|
127
127
|
"python-multipart>=0.0.26",
|
|
128
128
|
"Pygments>=2.20.0",
|
|
129
|
+
"starlette>=1.0.1",
|
|
129
130
|
]
|
|
130
131
|
# litellm pins fastapi-sso<0.17.0 but 0.16.x has a CSRF vulnerability.
|
|
131
132
|
# Forge doesn't use fastapi-sso directly; override to force the patched version.
|
|
@@ -148,6 +149,7 @@ dev = [
|
|
|
148
149
|
"types-PyYAML>=6.0.0",
|
|
149
150
|
"types-requests>=2.31.0",
|
|
150
151
|
"python-dotenv>=1.2.1",
|
|
152
|
+
"httpx2>=2.3.0",
|
|
151
153
|
]
|
|
152
154
|
provider-check = [
|
|
153
155
|
"anthropic>=0.100.0",
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""``forge activity`` — per-session Forge *automation* activity (supervisor, memory
|
|
2
|
+
writer, workflow verbs) + policy decisions. NOT your full interactive Claude usage.
|
|
3
|
+
|
|
4
|
+
Reads the two already-captured planes (usage ledger + ``confirmed.policy.decisions``)
|
|
5
|
+
via :func:`forge.core.ops.usage_summary.build_session_activity_summary` and renders a
|
|
6
|
+
table. Cost is reported-or-estimated (best-effort attribution) — ``forge proxy costs show``
|
|
7
|
+
stays the authoritative spend view.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
from dataclasses import asdict
|
|
15
|
+
from datetime import datetime, timedelta, timezone
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
|
|
21
|
+
from forge.cli.output import print_error_with_tip
|
|
22
|
+
from forge.core.ops.session_context import (
|
|
23
|
+
SessionContextError,
|
|
24
|
+
resolve_session_identifier,
|
|
25
|
+
)
|
|
26
|
+
from forge.core.ops.usage_summary import (
|
|
27
|
+
SessionActivitySummary,
|
|
28
|
+
build_session_activity_summary,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@click.command("activity")
|
|
35
|
+
@click.argument("session", required=False)
|
|
36
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
37
|
+
@click.option("--days", "-d", type=int, default=30, help="Look back this many days (default: 30)")
|
|
38
|
+
@click.option("--all", "all_time", is_flag=True, help="Report all time (ignore --days)")
|
|
39
|
+
def activity_cmd(session: str | None, as_json: bool, days: int, all_time: bool) -> None:
|
|
40
|
+
"""Show Forge's automation activity for a session: supervisor checks, cost, tokens.
|
|
41
|
+
|
|
42
|
+
This is what Forge did *on top of* your session — the supervisor, memory writer,
|
|
43
|
+
and workflow verbs (panel/debate/...) — plus policy decisions. It is **not** your
|
|
44
|
+
full interactive Claude usage. Reads the usage ledger and the session's
|
|
45
|
+
policy-decision log. Cost is reported-or-estimated (best-effort attribution);
|
|
46
|
+
'forge proxy costs show' is the authoritative spend view.
|
|
47
|
+
|
|
48
|
+
\b
|
|
49
|
+
Examples:
|
|
50
|
+
forge activity # current session ($FORGE_SESSION)
|
|
51
|
+
forge activity planner # a named session (or Claude UUID)
|
|
52
|
+
forge activity --all --json # full history, JSON
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
session_name, forge_root = resolve_session_identifier(session)
|
|
56
|
+
except SessionContextError as e:
|
|
57
|
+
if as_json:
|
|
58
|
+
click.echo(json.dumps({"error": str(e)}))
|
|
59
|
+
else:
|
|
60
|
+
print_error_with_tip(
|
|
61
|
+
str(e),
|
|
62
|
+
"Run 'forge session list' to see sessions.",
|
|
63
|
+
console=console,
|
|
64
|
+
)
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
since = None if all_time else datetime.now(timezone.utc) - timedelta(days=days)
|
|
68
|
+
summary = build_session_activity_summary(session_name, forge_root, since=since)
|
|
69
|
+
|
|
70
|
+
if as_json:
|
|
71
|
+
console.print_json(data=asdict(summary))
|
|
72
|
+
return
|
|
73
|
+
_render(summary, days=None if all_time else days)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _render(summary: SessionActivitySummary, *, days: int | None) -> None:
|
|
77
|
+
scope = "all time" if days is None else f"last {days}d"
|
|
78
|
+
if summary.is_empty:
|
|
79
|
+
console.print(f"[dim]No Forge activity for session '{summary.session}' ({scope}).[/dim]")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
console.print(f"\n[bold]Forge activity — {summary.session}[/bold] [dim]({scope})[/dim]")
|
|
83
|
+
console.print(
|
|
84
|
+
"[dim]Forge automation (supervisor, memory writer, workflow verbs) — "
|
|
85
|
+
"not your full interactive session.[/dim]"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if summary.commands:
|
|
89
|
+
# The Workers column only earns its width when a fan-out (panel/debate/...) ran;
|
|
90
|
+
# supervisor/memory-writer have no workers, so most sessions skip it.
|
|
91
|
+
show_workers = any(c.workers for c in summary.commands)
|
|
92
|
+
table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
|
|
93
|
+
table.add_column("Command", style="cyan")
|
|
94
|
+
table.add_column("Calls", justify="right")
|
|
95
|
+
if show_workers:
|
|
96
|
+
table.add_column("Workers", justify="right", style="dim")
|
|
97
|
+
table.add_column("Errors", justify="right")
|
|
98
|
+
table.add_column("Tokens in/out", justify="right", style="dim")
|
|
99
|
+
table.add_column("Cost", justify="right", style="dim")
|
|
100
|
+
for c in summary.commands:
|
|
101
|
+
errors = f"[red]{c.errors}[/red]" if c.errors else "0"
|
|
102
|
+
tokens = f"{c.input_tokens}/{c.output_tokens}" if (c.input_tokens or c.output_tokens) else "-"
|
|
103
|
+
cost = f"~{_fmt_usd(c.cost_micro_usd)}" if c.cost_micro_usd is not None else "-"
|
|
104
|
+
row = [c.command, str(c.calls)]
|
|
105
|
+
if show_workers:
|
|
106
|
+
row.append(str(c.workers) if c.workers else "-")
|
|
107
|
+
row += [errors, tokens, cost]
|
|
108
|
+
table.add_row(*row)
|
|
109
|
+
console.print(table)
|
|
110
|
+
|
|
111
|
+
pol = summary.policy
|
|
112
|
+
if pol and pol.has_content:
|
|
113
|
+
console.print(
|
|
114
|
+
f"\n[bold]Supervisor[/bold]: {pol.supervisor_allow} allow · "
|
|
115
|
+
f"{pol.supervisor_warn} warn · {pol.supervisor_deny} block"
|
|
116
|
+
)
|
|
117
|
+
for warning in pol.recent_warnings:
|
|
118
|
+
console.print(f" [yellow]•[/yellow] {warning}")
|
|
119
|
+
|
|
120
|
+
if summary.subagents:
|
|
121
|
+
console.print(f"\n[bold]Subagents[/bold]: {summary.subagents}")
|
|
122
|
+
|
|
123
|
+
total_cost = f"~{_fmt_usd(summary.total_cost_micro_usd)}" if summary.total_cost_micro_usd is not None else "n/a"
|
|
124
|
+
console.print(
|
|
125
|
+
f"\n[dim]Total:[/dim] {summary.total_events} events · "
|
|
126
|
+
f"{summary.total_input_tokens}/{summary.total_output_tokens} tok · {total_cost}"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
for note in _footnotes(summary):
|
|
130
|
+
console.print(f"[dim]{note}[/dim]")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _footnotes(summary: SessionActivitySummary) -> list[str]:
|
|
134
|
+
notes: list[str] = []
|
|
135
|
+
if summary.cost_partial:
|
|
136
|
+
notes.append("cost is best-effort and partial (some calls report no cost)")
|
|
137
|
+
if summary.policy is not None and summary.policy.log_capped:
|
|
138
|
+
notes.append("policy decision log is at capacity — older decisions may not be shown")
|
|
139
|
+
if summary.session_tagging_partial:
|
|
140
|
+
notes.append("some calls (e.g. the action tagger) are not session-attributed")
|
|
141
|
+
notes.append("cost is reported-or-estimated, best-effort; 'forge proxy costs show' is the authoritative spend view")
|
|
142
|
+
return notes
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _fmt_usd(micros: int | None) -> str:
|
|
146
|
+
if micros is None:
|
|
147
|
+
return "-"
|
|
148
|
+
dollars = micros / 1_000_000
|
|
149
|
+
if dollars and abs(dollars) < 0.01:
|
|
150
|
+
return f"${dollars:.4f}"
|
|
151
|
+
return f"${dollars:.2f}"
|
|
@@ -408,8 +408,7 @@ def status(profile: str | None) -> None:
|
|
|
408
408
|
help="Profile to remove credentials from (default: 'default' or FORGE_PROFILE)",
|
|
409
409
|
)
|
|
410
410
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
411
|
-
|
|
412
|
-
def logout(profile: str | None, yes: bool, force: bool) -> None:
|
|
411
|
+
def logout(profile: str | None, yes: bool) -> None:
|
|
413
412
|
"""Remove stored credentials for a profile.
|
|
414
413
|
|
|
415
414
|
Deletes the profile from ~/.forge/credentials.yaml.
|
|
@@ -421,7 +420,6 @@ def logout(profile: str | None, yes: bool, force: bool) -> None:
|
|
|
421
420
|
forge authentication logout --profile work
|
|
422
421
|
forge authentication logout -y # Skip confirmation
|
|
423
422
|
"""
|
|
424
|
-
yes = yes or force
|
|
425
423
|
profile_name = resolve_profile(profile)
|
|
426
424
|
|
|
427
425
|
if not yes:
|
|
@@ -251,8 +251,7 @@ def stop_cmd(adapter: str, port: int) -> None:
|
|
|
251
251
|
help="Delete specific instance (if not specified, deletes config)",
|
|
252
252
|
)
|
|
253
253
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
254
|
-
|
|
255
|
-
def delete_cmd(adapter: str, port: int | None, yes: bool, force: bool) -> None:
|
|
254
|
+
def delete_cmd(adapter: str, port: int | None, yes: bool) -> None:
|
|
256
255
|
"""Delete a backend instance or config.
|
|
257
256
|
|
|
258
257
|
Without --port: Deletes the backend config (stops all instances first).
|
|
@@ -260,7 +259,6 @@ def delete_cmd(adapter: str, port: int | None, yes: bool, force: bool) -> None:
|
|
|
260
259
|
"""
|
|
261
260
|
import shutil
|
|
262
261
|
|
|
263
|
-
yes = yes or force
|
|
264
262
|
console = Console(width=200)
|
|
265
263
|
|
|
266
264
|
if port is not None:
|
|
@@ -392,13 +392,11 @@ def preset_edit() -> None:
|
|
|
392
392
|
|
|
393
393
|
@preset.command("reset")
|
|
394
394
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
395
|
-
|
|
396
|
-
def preset_reset(yes: bool, force: bool) -> None:
|
|
395
|
+
def preset_reset(yes: bool) -> None:
|
|
397
396
|
"""Reset settings preset to built-in defaults."""
|
|
398
397
|
from forge.core.state import atomic_write_text
|
|
399
398
|
from forge.install.preset import get_builtin_preset_json, get_preset_path
|
|
400
399
|
|
|
401
|
-
yes = yes or force
|
|
402
400
|
preset_path = get_preset_path()
|
|
403
401
|
|
|
404
402
|
if not yes:
|
|
@@ -16,7 +16,8 @@ import shutil
|
|
|
16
16
|
import subprocess
|
|
17
17
|
import sys
|
|
18
18
|
import tempfile
|
|
19
|
-
from
|
|
19
|
+
from collections.abc import MutableMapping
|
|
20
|
+
from dataclasses import asdict, fields, is_dataclass
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import Any
|
|
22
23
|
|
|
@@ -71,7 +72,12 @@ def show_cmd(raw: bool = False) -> None:
|
|
|
71
72
|
|
|
72
73
|
effective: dict[str, Any] = {}
|
|
73
74
|
for f in fields(RuntimeConfig):
|
|
74
|
-
|
|
75
|
+
val = getattr(rc, f.name)
|
|
76
|
+
# Nested config (e.g. statusline) must render as a plain mapping — yaml
|
|
77
|
+
# can't dump a dataclass instance.
|
|
78
|
+
if is_dataclass(val) and not isinstance(val, type):
|
|
79
|
+
val = asdict(val)
|
|
80
|
+
effective[f.name] = val
|
|
75
81
|
|
|
76
82
|
content = yaml.dump(effective, default_flow_style=False, sort_keys=False)
|
|
77
83
|
|
|
@@ -107,6 +113,11 @@ def set_cmd(key_value: str) -> None:
|
|
|
107
113
|
|
|
108
114
|
key, value = key_value.split("=", 1)
|
|
109
115
|
|
|
116
|
+
# Nested section keys (e.g. statusline.cost_mode) take the dotted path.
|
|
117
|
+
if "." in key:
|
|
118
|
+
_set_nested_key(key, value, console)
|
|
119
|
+
return
|
|
120
|
+
|
|
110
121
|
known_fields = {f.name: f for f in fields(RuntimeConfig)}
|
|
111
122
|
if key not in known_fields:
|
|
112
123
|
console.print(f"[red]Error:[/red] Unknown config key: '{key}'")
|
|
@@ -198,6 +209,20 @@ def edit_cmd() -> None:
|
|
|
198
209
|
console.print(f"Your changes are saved at: {display_path(tmp_path)}")
|
|
199
210
|
sys.exit(1)
|
|
200
211
|
|
|
212
|
+
# Segment names aren't validated by StatusLineConfig (the renderer and
|
|
213
|
+
# the set/edit CLI own that), so the edit path must enforce the allowlist
|
|
214
|
+
# too — otherwise statusline.segments: [path, bogus] would be accepted.
|
|
215
|
+
sl_section = edited_data.get("statusline")
|
|
216
|
+
if isinstance(sl_section, dict) and isinstance(sl_section.get("segments"), list):
|
|
217
|
+
unknown_segs = _unknown_segments(sl_section["segments"])
|
|
218
|
+
if unknown_segs:
|
|
219
|
+
from forge.cli.statusline.names import SEGMENT_NAMES
|
|
220
|
+
|
|
221
|
+
console.print(f"[red]Error:[/red] Unknown statusline segment(s): {', '.join(map(str, unknown_segs))}")
|
|
222
|
+
console.print(f"[dim]Valid segments: {', '.join(SEGMENT_NAMES)}[/dim]")
|
|
223
|
+
console.print(f"Your changes are saved at: {display_path(tmp_path)}")
|
|
224
|
+
sys.exit(1)
|
|
225
|
+
|
|
201
226
|
write_runtime_config(dict(edited_data))
|
|
202
227
|
success = True
|
|
203
228
|
console.print("[green]Updated[/green] runtime configuration")
|
|
@@ -213,14 +238,12 @@ def edit_cmd() -> None:
|
|
|
213
238
|
@config.command("reset")
|
|
214
239
|
@click.argument("key", required=False)
|
|
215
240
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
216
|
-
|
|
217
|
-
def reset_cmd(key: str | None = None, yes: bool = False, force: bool = False) -> None:
|
|
241
|
+
def reset_cmd(key: str | None = None, yes: bool = False) -> None:
|
|
218
242
|
"""Reset configuration to defaults.
|
|
219
243
|
|
|
220
244
|
With KEY: removes that key (reverts to built-in default).
|
|
221
245
|
Without KEY: deletes the entire config file.
|
|
222
246
|
"""
|
|
223
|
-
yes = yes or force
|
|
224
247
|
console = Console(width=200)
|
|
225
248
|
config_path = get_config_path()
|
|
226
249
|
|
|
@@ -257,18 +280,23 @@ def reset_cmd(key: str | None = None, yes: bool = False, force: bool = False) ->
|
|
|
257
280
|
return
|
|
258
281
|
|
|
259
282
|
del data[key]
|
|
283
|
+
_persist_or_clear(data, config_path)
|
|
284
|
+
|
|
285
|
+
default_val = getattr(RuntimeConfig(), key)
|
|
286
|
+
console.print(f"[green]Reset[/green] {key} (default: {default_val})")
|
|
260
287
|
|
|
288
|
+
|
|
289
|
+
# --- Helpers ---
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _persist_or_clear(data: MutableMapping[str, Any], config_path: Path) -> None:
|
|
293
|
+
"""Write ``data`` back, or remove the config file when nothing remains."""
|
|
261
294
|
if data:
|
|
262
295
|
write_runtime_config(dict(data))
|
|
263
296
|
else:
|
|
264
297
|
config_path.unlink()
|
|
265
298
|
reset_runtime_config()
|
|
266
299
|
|
|
267
|
-
default_val = getattr(RuntimeConfig(), key)
|
|
268
|
-
console.print(f"[green]Reset[/green] {key} (default: {default_val})")
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
# --- Helpers ---
|
|
272
300
|
|
|
273
301
|
_COERCE_ERROR = object()
|
|
274
302
|
|
|
@@ -301,3 +329,79 @@ def _coerce_value(key: str, value: str, field_info: Any) -> Any:
|
|
|
301
329
|
|
|
302
330
|
# String fields: pass through
|
|
303
331
|
return value
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _unknown_segments(segments: list[Any]) -> list[Any]:
|
|
335
|
+
"""Return segment names not in the allowlist (the set/edit strict gate).
|
|
336
|
+
|
|
337
|
+
Segment names are intentionally NOT validated by ``StatusLineConfig`` (the
|
|
338
|
+
renderer drops unknown names on load); the write paths reject them instead.
|
|
339
|
+
"""
|
|
340
|
+
from forge.cli.statusline.names import SEGMENT_NAMES
|
|
341
|
+
|
|
342
|
+
return [s for s in segments if s not in SEGMENT_NAMES]
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _set_nested_key(key: str, value: str, console: Console) -> None:
|
|
346
|
+
"""Set a dotted nested config key. Only ``statusline.<subkey>`` is supported.
|
|
347
|
+
|
|
348
|
+
Strict (fail-closed): unknown section/subkey, invalid enum values, and
|
|
349
|
+
unknown segment names all error and exit non-zero, naming valid options.
|
|
350
|
+
"""
|
|
351
|
+
from forge.cli.statusline.names import SEGMENT_NAMES
|
|
352
|
+
from forge.runtime_config import StatusLineConfig
|
|
353
|
+
|
|
354
|
+
section, _, subkey = key.partition(".")
|
|
355
|
+
if section != "statusline":
|
|
356
|
+
console.print(f"[red]Error:[/red] Unknown config section: '{section}'")
|
|
357
|
+
console.print("\n[dim]Only 'statusline.*' nested keys are supported.[/dim]")
|
|
358
|
+
sys.exit(1)
|
|
359
|
+
|
|
360
|
+
sl_fields = {f.name: f for f in fields(StatusLineConfig)}
|
|
361
|
+
if subkey not in sl_fields:
|
|
362
|
+
console.print(f"[red]Error:[/red] Unknown statusline key: '{subkey}'")
|
|
363
|
+
console.print(f"\n[dim]Available: {', '.join(sorted(sl_fields))}[/dim]")
|
|
364
|
+
sys.exit(1)
|
|
365
|
+
|
|
366
|
+
coerced_sub: Any
|
|
367
|
+
if subkey == "segments":
|
|
368
|
+
coerced_sub = [s.strip() for s in value.split(",") if s.strip()]
|
|
369
|
+
unknown = _unknown_segments(coerced_sub)
|
|
370
|
+
if unknown:
|
|
371
|
+
console.print(f"[red]Error:[/red] Unknown segment(s): {', '.join(unknown)}")
|
|
372
|
+
console.print(f"\n[dim]Valid segments: {', '.join(SEGMENT_NAMES)}[/dim]")
|
|
373
|
+
sys.exit(1)
|
|
374
|
+
else:
|
|
375
|
+
coerced_sub = _coerce_value(subkey, value, sl_fields[subkey])
|
|
376
|
+
if coerced_sub is _COERCE_ERROR:
|
|
377
|
+
console.print(f"[red]Error:[/red] Invalid value for 'statusline.{subkey}': {value}")
|
|
378
|
+
sys.exit(1)
|
|
379
|
+
|
|
380
|
+
config_path = get_config_path()
|
|
381
|
+
if config_path.is_file():
|
|
382
|
+
from ruamel.yaml import YAML
|
|
383
|
+
|
|
384
|
+
ruamel = YAML()
|
|
385
|
+
ruamel.preserve_quotes = True
|
|
386
|
+
with open(config_path) as f:
|
|
387
|
+
data = ruamel.load(f) or {}
|
|
388
|
+
else:
|
|
389
|
+
data = {}
|
|
390
|
+
|
|
391
|
+
section_data = data.get("statusline")
|
|
392
|
+
if not isinstance(section_data, dict):
|
|
393
|
+
section_data = {}
|
|
394
|
+
section_data[subkey] = coerced_sub
|
|
395
|
+
data["statusline"] = section_data
|
|
396
|
+
|
|
397
|
+
# Validate via construction — StatusLineConfig.__post_init__ rejects bad enums
|
|
398
|
+
# (fail-closed); segment names were already checked above.
|
|
399
|
+
known_fields = {f.name for f in fields(RuntimeConfig)}
|
|
400
|
+
try:
|
|
401
|
+
RuntimeConfig(**{k: v for k, v in dict(data).items() if k in known_fields})
|
|
402
|
+
except (ValueError, TypeError) as e:
|
|
403
|
+
console.print(f"[red]Error:[/red] Invalid configuration: {e}")
|
|
404
|
+
sys.exit(1)
|
|
405
|
+
|
|
406
|
+
write_runtime_config(data)
|
|
407
|
+
console.print(f"[green]Set[/green] {key}={coerced_sub}")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Shared $EDITOR launcher for editable transfer/context files.
|
|
2
|
+
|
|
3
|
+
Extracted from ``session_lifecycle`` so both ``forge session resume --review``
|
|
4
|
+
and ``forge transfer edit`` use one editor-launch path with the same
|
|
5
|
+
git-commit-style abort behavior: a non-zero editor exit aborts and leaves the
|
|
6
|
+
file untouched.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import shlex
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
|
|
20
|
+
from forge.cli.output import print_tip
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def open_in_editor(file_path: Path, *, console: Console, abort_tip: str | None = None) -> None:
|
|
24
|
+
"""Open ``file_path`` in $EDITOR, aborting on a non-zero editor exit.
|
|
25
|
+
|
|
26
|
+
Git-commit-style: a non-zero editor exit prints the optional ``abort_tip``
|
|
27
|
+
and exits with the editor's return code, leaving the file as the user left
|
|
28
|
+
it. Exits 1 when $EDITOR is empty or its program is not on PATH.
|
|
29
|
+
"""
|
|
30
|
+
editor = os.environ.get("EDITOR", "vim")
|
|
31
|
+
editor_argv = shlex.split(editor)
|
|
32
|
+
if not editor_argv:
|
|
33
|
+
console.print("[red]Error:[/red] $EDITOR is empty. Set $EDITOR to an available editor.")
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
if not shutil.which(editor_argv[0]):
|
|
36
|
+
console.print(f"[red]Error:[/red] Editor '{editor}' not found. Set $EDITOR to an available editor.")
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
result = subprocess.run([*editor_argv, str(file_path)])
|
|
40
|
+
if result.returncode != 0:
|
|
41
|
+
console.print(f"[red]Aborted:[/red] editor exited with code {result.returncode}.")
|
|
42
|
+
if abort_tip:
|
|
43
|
+
print_tip(abort_tip, blank_before=False, console=console)
|
|
44
|
+
sys.exit(result.returncode)
|
|
@@ -712,8 +712,7 @@ def sync_cmd(scope: str | None, force: bool) -> None:
|
|
|
712
712
|
help="Disable ALL tracked installations",
|
|
713
713
|
)
|
|
714
714
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
715
|
-
|
|
716
|
-
def disable_cmd(scope: str | None, uninstall_all: bool, yes: bool, force: bool) -> None:
|
|
715
|
+
def disable_cmd(scope: str | None, uninstall_all: bool, yes: bool) -> None:
|
|
717
716
|
"""Disable Forge extensions.
|
|
718
717
|
|
|
719
718
|
Removes only files and settings entries that were added by Forge.
|
|
@@ -737,8 +736,6 @@ def disable_cmd(scope: str | None, uninstall_all: bool, yes: bool, force: bool)
|
|
|
737
736
|
forge extension disable --scope local # Disable local scope
|
|
738
737
|
forge extension disable --all --yes # Disable everything
|
|
739
738
|
"""
|
|
740
|
-
yes = yes or force
|
|
741
|
-
|
|
742
739
|
if uninstall_all and scope is not None:
|
|
743
740
|
raise click.UsageError("--all and --scope are mutually exclusive.")
|
|
744
741
|
try:
|
|
@@ -791,7 +788,7 @@ def disable_cmd(scope: str | None, uninstall_all: bool, yes: bool, force: bool)
|
|
|
791
788
|
console.print("[bold]Settings:[/bold]")
|
|
792
789
|
console.print(table)
|
|
793
790
|
|
|
794
|
-
if not
|
|
791
|
+
if not yes:
|
|
795
792
|
if not click.confirm("\nProceed with disable?"):
|
|
796
793
|
console.print("[dim]Cancelled.[/dim]")
|
|
797
794
|
return
|
|
@@ -24,7 +24,7 @@ from forge.core.ops.gc import CleanError, CleanReport, collect_clean_report, run
|
|
|
24
24
|
@click.option("--verbose", "-v", is_flag=True, help="Show individual items")
|
|
25
25
|
@click.option("--json", "as_json", is_flag=True, help="JSON output")
|
|
26
26
|
def clean_cmd(scope: str, yes: bool, verbose: bool, as_json: bool) -> None:
|
|
27
|
-
"""Remove orphaned Forge state (sessions,
|
|
27
|
+
"""Remove orphaned Forge state (sessions, transfer files, stale entries).
|
|
28
28
|
|
|
29
29
|
By default, shows what would be cleaned (dry-run). Pass --yes to actually delete.
|
|
30
30
|
|
|
@@ -156,7 +156,7 @@ def _category_label(category: str) -> str:
|
|
|
156
156
|
"""Human-readable label for a category."""
|
|
157
157
|
labels = {
|
|
158
158
|
"session_dirs": "Orphan session dirs:",
|
|
159
|
-
"
|
|
159
|
+
"transfer_files": "Orphan transfer files:",
|
|
160
160
|
"active_entries": "Stale active entries:",
|
|
161
161
|
"work_queue": "Stale work queue:",
|
|
162
162
|
"proxies": "Stale proxy entries:",
|