code-muse 0.0.1__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.
- code_muse/__init__.py +26 -0
- code_muse/__main__.py +10 -0
- code_muse/agents/__init__.py +31 -0
- code_muse/agents/_builder.py +214 -0
- code_muse/agents/_compaction.py +506 -0
- code_muse/agents/_diagnostics.py +171 -0
- code_muse/agents/_history.py +382 -0
- code_muse/agents/_key_listeners.py +148 -0
- code_muse/agents/_non_streaming_render.py +148 -0
- code_muse/agents/_runtime.py +596 -0
- code_muse/agents/agent_creator_agent.py +603 -0
- code_muse/agents/agent_helios.py +47 -0
- code_muse/agents/agent_manager.py +740 -0
- code_muse/agents/agent_muse.py +78 -0
- code_muse/agents/agent_planning.py +44 -0
- code_muse/agents/agent_qa_melpomene.py +207 -0
- code_muse/agents/base_agent.py +194 -0
- code_muse/agents/event_stream_handler.py +361 -0
- code_muse/agents/json_agent.py +201 -0
- code_muse/agents/prompt_v3.py +521 -0
- code_muse/agents/subagent_stream_handler.py +273 -0
- code_muse/callbacks.py +941 -0
- code_muse/chatgpt_codex_client.py +333 -0
- code_muse/claude_cache_client.py +853 -0
- code_muse/cli_runner/__init__.py +319 -0
- code_muse/cli_runner/args.py +63 -0
- code_muse/cli_runner/loop.py +510 -0
- code_muse/cli_runner/resume.py +72 -0
- code_muse/cli_runner/runner.py +161 -0
- code_muse/command_line/__init__.py +1 -0
- code_muse/command_line/add_model_menu.py +1331 -0
- code_muse/command_line/agent_menu.py +674 -0
- code_muse/command_line/attachments.py +397 -0
- code_muse/command_line/autosave_menu.py +709 -0
- code_muse/command_line/clipboard.py +528 -0
- code_muse/command_line/colors_menu.py +530 -0
- code_muse/command_line/command_handler.py +262 -0
- code_muse/command_line/command_registry.py +150 -0
- code_muse/command_line/config_commands.py +711 -0
- code_muse/command_line/core_commands.py +740 -0
- code_muse/command_line/diff_menu.py +865 -0
- code_muse/command_line/file_path_completion.py +73 -0
- code_muse/command_line/load_context_completion.py +57 -0
- code_muse/command_line/model_picker_completion.py +512 -0
- code_muse/command_line/model_settings_menu.py +983 -0
- code_muse/command_line/onboarding_slides.py +162 -0
- code_muse/command_line/onboarding_wizard.py +337 -0
- code_muse/command_line/pagination.py +41 -0
- code_muse/command_line/pin_command_completion.py +329 -0
- code_muse/command_line/prompt_toolkit_completion.py +886 -0
- code_muse/command_line/session_commands.py +304 -0
- code_muse/command_line/shell_passthrough.py +145 -0
- code_muse/command_line/skills_completion.py +158 -0
- code_muse/command_line/types.py +18 -0
- code_muse/command_line/uc_menu.py +908 -0
- code_muse/command_line/utils.py +105 -0
- code_muse/command_line/wiggum_state.py +77 -0
- code_muse/config.py +1138 -0
- code_muse/config_agent.py +168 -0
- code_muse/config_appearance.py +241 -0
- code_muse/config_model.py +357 -0
- code_muse/config_security.py +73 -0
- code_muse/error_logging.py +132 -0
- code_muse/evals/__init__.py +35 -0
- code_muse/evals/eval_helpers.py +81 -0
- code_muse/evals/eval_runner.py +299 -0
- code_muse/evals/sample_evals/__init__.py +1 -0
- code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
- code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
- code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
- code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
- code_muse/fs_scan_cache/__init__.py +31 -0
- code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
- code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
- code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
- code_muse/fs_scan_cache/tool_integration.py +309 -0
- code_muse/fs_scan_cache/ttl_policy.py +44 -0
- code_muse/gemini_code_assist.py +383 -0
- code_muse/gemini_model.py +838 -0
- code_muse/hook_engine/README.md +105 -0
- code_muse/hook_engine/__init__.py +21 -0
- code_muse/hook_engine/aliases.py +153 -0
- code_muse/hook_engine/engine.py +221 -0
- code_muse/hook_engine/executor.py +347 -0
- code_muse/hook_engine/matcher.py +154 -0
- code_muse/hook_engine/models.py +245 -0
- code_muse/hook_engine/registry.py +114 -0
- code_muse/hook_engine/trust.py +268 -0
- code_muse/hook_engine/validator.py +144 -0
- code_muse/http_utils.py +360 -0
- code_muse/keymap.py +128 -0
- code_muse/list_filtering.py +26 -0
- code_muse/main.py +10 -0
- code_muse/messaging/__init__.py +259 -0
- code_muse/messaging/bus.py +621 -0
- code_muse/messaging/commands.py +166 -0
- code_muse/messaging/markdown_patches.py +57 -0
- code_muse/messaging/message_queue.py +397 -0
- code_muse/messaging/messages.py +591 -0
- code_muse/messaging/queue_console.py +269 -0
- code_muse/messaging/renderers.py +308 -0
- code_muse/messaging/rich_renderer.py +1158 -0
- code_muse/messaging/shimmer.py +154 -0
- code_muse/messaging/spinner/__init__.py +87 -0
- code_muse/messaging/spinner/console_spinner.py +250 -0
- code_muse/messaging/spinner/spinner_base.py +82 -0
- code_muse/messaging/subagent_console.py +458 -0
- code_muse/model_factory.py +1203 -0
- code_muse/model_switching.py +59 -0
- code_muse/model_utils.py +156 -0
- code_muse/models.json +66 -0
- code_muse/models_cache/__init__.py +26 -0
- code_muse/models_cache/blocking_lru_cache.py +98 -0
- code_muse/models_cache/cache_writer.py +86 -0
- code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
- code_muse/models_cache/sha256_hash.pyx +34 -0
- code_muse/models_cache/startup_integration.py +75 -0
- code_muse/models_dev_api.json +1 -0
- code_muse/models_dev_parser.py +590 -0
- code_muse/motion.py +126 -0
- code_muse/plugins/__init__.py +471 -0
- code_muse/plugins/agent_skills/__init__.py +32 -0
- code_muse/plugins/agent_skills/config.py +176 -0
- code_muse/plugins/agent_skills/discovery.py +309 -0
- code_muse/plugins/agent_skills/downloader.py +389 -0
- code_muse/plugins/agent_skills/installer.py +19 -0
- code_muse/plugins/agent_skills/metadata.py +293 -0
- code_muse/plugins/agent_skills/prompt_builder.py +66 -0
- code_muse/plugins/agent_skills/register_callbacks.py +298 -0
- code_muse/plugins/agent_skills/remote_catalog.py +320 -0
- code_muse/plugins/agent_skills/skill_catalog.py +254 -0
- code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
- code_muse/plugins/agent_skills/skills_menu.py +791 -0
- code_muse/plugins/autonomous_memory/__init__.py +39 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
- code_muse/plugins/autonomous_memory/consolidation.py +82 -0
- code_muse/plugins/autonomous_memory/extraction.py +382 -0
- code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
- code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
- code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
- code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
- code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
- code_muse/plugins/aws_bedrock/__init__.py +14 -0
- code_muse/plugins/aws_bedrock/config.py +99 -0
- code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
- code_muse/plugins/aws_bedrock/utils.py +153 -0
- code_muse/plugins/azure_foundry/README.md +238 -0
- code_muse/plugins/azure_foundry/__init__.py +15 -0
- code_muse/plugins/azure_foundry/config.py +125 -0
- code_muse/plugins/azure_foundry/discovery.py +187 -0
- code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
- code_muse/plugins/azure_foundry/token.py +180 -0
- code_muse/plugins/azure_foundry/utils.py +345 -0
- code_muse/plugins/build_filter/__init__.py +1 -0
- code_muse/plugins/build_filter/register_callbacks.py +201 -0
- code_muse/plugins/build_filter/strategies/__init__.py +1 -0
- code_muse/plugins/build_filter/strategies/build.py +397 -0
- code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
- code_muse/plugins/chatgpt_oauth/config.py +52 -0
- code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
- code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
- code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
- code_muse/plugins/chatgpt_oauth/utils.py +538 -0
- code_muse/plugins/checkpointing/__init__.py +29 -0
- code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
- code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
- code_muse/plugins/checkpointing/register_callbacks.py +51 -0
- code_muse/plugins/checkpointing/restore_command.py +263 -0
- code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
- code_muse/plugins/checkpointing/shadow_git.py +90 -0
- code_muse/plugins/claude_code_hooks/__init__.py +1 -0
- code_muse/plugins/claude_code_hooks/config.py +188 -0
- code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
- code_muse/plugins/claude_code_oauth/README.md +167 -0
- code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
- code_muse/plugins/claude_code_oauth/__init__.py +25 -0
- code_muse/plugins/claude_code_oauth/config.py +52 -0
- code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
- code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
- code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
- code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
- code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
- code_muse/plugins/claude_code_oauth/utils.py +664 -0
- code_muse/plugins/copilot_auth/__init__.py +11 -0
- code_muse/plugins/copilot_auth/config.py +91 -0
- code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
- code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
- code_muse/plugins/copilot_auth/utils.py +584 -0
- code_muse/plugins/custom_commands/__init__.py +14 -0
- code_muse/plugins/custom_commands/args_injection.py +82 -0
- code_muse/plugins/custom_commands/command_discovery.py +89 -0
- code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
- code_muse/plugins/custom_commands/register_callbacks.py +176 -0
- code_muse/plugins/customizable_commands/__init__.py +0 -0
- code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
- code_muse/plugins/destructive_command_guard/__init__.py +14 -0
- code_muse/plugins/destructive_command_guard/detector.py +375 -0
- code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
- code_muse/plugins/example_custom_command/README.md +280 -0
- code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
- code_muse/plugins/file_permission_handler/__init__.py +4 -0
- code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
- code_muse/plugins/filter_engine/__init__.py +30 -0
- code_muse/plugins/filter_engine/classifier.py +153 -0
- code_muse/plugins/filter_engine/content_detector.py +184 -0
- code_muse/plugins/filter_engine/dispatcher.py +244 -0
- code_muse/plugins/filter_engine/register_callbacks.py +188 -0
- code_muse/plugins/filter_engine/registry.py +279 -0
- code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
- code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
- code_muse/plugins/filter_engine/verbosity.py +63 -0
- code_muse/plugins/force_push_guard/__init__.py +5 -0
- code_muse/plugins/force_push_guard/detector.py +96 -0
- code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
- code_muse/plugins/force_push_guard/test_detector.py +143 -0
- code_muse/plugins/frontend_emitter/__init__.py +25 -0
- code_muse/plugins/frontend_emitter/emitter.py +121 -0
- code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
- code_muse/plugins/gac/__init__.py +4 -0
- code_muse/plugins/gac/git_ops.py +136 -0
- code_muse/plugins/gac/prompt.py +191 -0
- code_muse/plugins/gac/register_callbacks.py +82 -0
- code_muse/plugins/hook_creator/__init__.py +1 -0
- code_muse/plugins/hook_creator/register_callbacks.py +34 -0
- code_muse/plugins/hook_manager/__init__.py +1 -0
- code_muse/plugins/hook_manager/config.py +289 -0
- code_muse/plugins/hook_manager/hooks_menu.py +563 -0
- code_muse/plugins/hook_manager/register_callbacks.py +227 -0
- code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
- code_muse/plugins/mindpack/__init__.py +0 -0
- code_muse/plugins/mindpack/factory.py +930 -0
- code_muse/plugins/mindpack/judge.py +573 -0
- code_muse/plugins/mindpack/memory.py +100 -0
- code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
- code_muse/plugins/mindpack/orchestration.py +605 -0
- code_muse/plugins/mindpack/register_callbacks.py +175 -0
- code_muse/plugins/mindpack/schemas.py +358 -0
- code_muse/plugins/mindpack/tools.py +387 -0
- code_muse/plugins/oauth_muse_html.py +226 -0
- code_muse/plugins/ollama_setup/__init__.py +5 -0
- code_muse/plugins/ollama_setup/completer.py +36 -0
- code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
- code_muse/plugins/plan_command/__init__.py +0 -0
- code_muse/plugins/plan_command/register_callbacks.py +206 -0
- code_muse/plugins/plan_mode/__init__.py +37 -0
- code_muse/plugins/plan_mode/mode_cycling.py +40 -0
- code_muse/plugins/plan_mode/plan_generation.py +68 -0
- code_muse/plugins/plan_mode/plan_hooks.py +74 -0
- code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
- code_muse/plugins/plan_mode/register_callbacks.py +121 -0
- code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
- code_muse/plugins/policy_engine/__init__.py +46 -0
- code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
- code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
- code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
- code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
- code_muse/plugins/policy_engine/register_callbacks.py +112 -0
- code_muse/plugins/pop_command/__init__.py +1 -0
- code_muse/plugins/pop_command/register_callbacks.py +189 -0
- code_muse/plugins/prompt_newline/__init__.py +13 -0
- code_muse/plugins/prompt_newline/config.py +19 -0
- code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
- code_muse/plugins/safety_status/__init__.py +0 -0
- code_muse/plugins/safety_status/register_callbacks.py +113 -0
- code_muse/plugins/semantic_compression/__init__.py +6 -0
- code_muse/plugins/semantic_compression/compressor.py +295 -0
- code_muse/plugins/semantic_compression/config.py +123 -0
- code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
- code_muse/plugins/shell_minimizer/__init__.py +50 -0
- code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
- code_muse/plugins/shell_minimizer/pipeline.py +556 -0
- code_muse/plugins/shell_minimizer/primitives.py +482 -0
- code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
- code_muse/plugins/shell_safety/__init__.py +6 -0
- code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_muse/plugins/shell_safety/command_cache.py +149 -0
- code_muse/plugins/shell_safety/register_callbacks.py +202 -0
- code_muse/plugins/synthetic_status/__init__.py +1 -0
- code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
- code_muse/plugins/synthetic_status/status_api.py +145 -0
- code_muse/plugins/token_caching/__init__.py +21 -0
- code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
- code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
- code_muse/plugins/token_caching/register_callbacks.py +54 -0
- code_muse/plugins/token_caching/stats_display.py +35 -0
- code_muse/plugins/token_tracking/__init__.py +26 -0
- code_muse/plugins/token_tracking/database.py +381 -0
- code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
- code_muse/plugins/token_tracking/record.py +55 -0
- code_muse/plugins/token_tracking/register_callbacks.py +277 -0
- code_muse/plugins/token_tracking/reports.py +329 -0
- code_muse/plugins/universal_constructor/__init__.py +13 -0
- code_muse/plugins/universal_constructor/models.py +136 -0
- code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
- code_muse/plugins/universal_constructor/registry.py +390 -0
- code_muse/plugins/universal_constructor/runner.py +474 -0
- code_muse/plugins/universal_constructor/safety.py +440 -0
- code_muse/plugins/universal_constructor/sandbox.py +584 -0
- code_muse/provider_identity.py +105 -0
- code_muse/pydantic_patches.py +410 -0
- code_muse/reopenable_async_client.py +233 -0
- code_muse/round_robin_model.py +151 -0
- code_muse/secret_storage.py +74 -0
- code_muse/security/__init__.py +1 -0
- code_muse/security/redaction.cpython-314-darwin.so +0 -0
- code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/security/redaction.pyx +135 -0
- code_muse/session_storage.py +565 -0
- code_muse/status_display.py +261 -0
- code_muse/stream_parser/__init__.py +76 -0
- code_muse/stream_parser/assistant_text_parser.py +90 -0
- code_muse/stream_parser/citation_parser.py +76 -0
- code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
- code_muse/stream_parser/proposed_plan_parser.py +158 -0
- code_muse/stream_parser/stream_text_chunk.py +23 -0
- code_muse/stream_parser/stream_text_parser.py +27 -0
- code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/tagged_line_parser.pyx +251 -0
- code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
- code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
- code_muse/summarization_agent.py +308 -0
- code_muse/terminal_utils.cpython-314-darwin.so +0 -0
- code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
- code_muse/terminal_utils.pyx +483 -0
- code_muse/tools/__init__.py +459 -0
- code_muse/tools/agent_tools.py +613 -0
- code_muse/tools/ask_user_question/__init__.py +26 -0
- code_muse/tools/ask_user_question/constants.py +73 -0
- code_muse/tools/ask_user_question/demo_tui.py +55 -0
- code_muse/tools/ask_user_question/handler.py +232 -0
- code_muse/tools/ask_user_question/models.py +302 -0
- code_muse/tools/ask_user_question/registration.py +37 -0
- code_muse/tools/ask_user_question/renderers.py +336 -0
- code_muse/tools/ask_user_question/terminal_ui.py +327 -0
- code_muse/tools/ask_user_question/theme.py +156 -0
- code_muse/tools/ask_user_question/tui_loop.py +422 -0
- code_muse/tools/background_jobs.py +99 -0
- code_muse/tools/browser/__init__.py +37 -0
- code_muse/tools/browser/browser_control.py +289 -0
- code_muse/tools/browser/browser_interactions.py +545 -0
- code_muse/tools/browser/browser_locators.py +640 -0
- code_muse/tools/browser/browser_manager.py +376 -0
- code_muse/tools/browser/browser_navigation.py +251 -0
- code_muse/tools/browser/browser_screenshot.py +180 -0
- code_muse/tools/browser/browser_scripts.py +462 -0
- code_muse/tools/browser/browser_workflows.py +222 -0
- code_muse/tools/chrome_cdp/__init__.py +1070 -0
- code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
- code_muse/tools/command_runner.py +1401 -0
- code_muse/tools/common.py +1407 -0
- code_muse/tools/display.py +87 -0
- code_muse/tools/file_modifications.py +1099 -0
- code_muse/tools/file_operations.py +860 -0
- code_muse/tools/image_tools.py +185 -0
- code_muse/tools/meetin_proxy/__init__.py +243 -0
- code_muse/tools/meetin_proxy/capture_addon.py +82 -0
- code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
- code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
- code_muse/tools/path_policy.py +219 -0
- code_muse/tools/skills_tools.py +586 -0
- code_muse/tools/subagent_context.py +158 -0
- code_muse/tools/tools_content.py +50 -0
- code_muse/tools/universal_constructor.py +965 -0
- code_muse/uvx_detection.py +241 -0
- code_muse/version_checker.py +86 -0
- code_muse-0.0.1.data/data/code_muse/models.json +66 -0
- code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
- code_muse-0.0.1.dist-info/METADATA +845 -0
- code_muse-0.0.1.dist-info/RECORD +394 -0
- code_muse-0.0.1.dist-info/WHEEL +4 -0
- code_muse-0.0.1.dist-info/entry_points.txt +2 -0
- code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Image loading tool for visual analysis.
|
|
2
|
+
|
|
3
|
+
Provides ``load_image_for_analysis`` — a tiny generic tool that reads an image
|
|
4
|
+
from disk, validates it, downsizes it if it's billboard-sized, and returns it
|
|
5
|
+
as a ``ToolReturn`` with ``BinaryContent`` so multimodal models can see it.
|
|
6
|
+
|
|
7
|
+
Lives outside the browser package because it has nothing to do with browsers.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import io
|
|
11
|
+
import logging
|
|
12
|
+
import mimetypes
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from PIL import Image, UnidentifiedImageError
|
|
18
|
+
from pydantic_ai import BinaryContent, RunContext, ToolReturn
|
|
19
|
+
|
|
20
|
+
from code_muse.messaging import emit_error, emit_info, emit_success
|
|
21
|
+
from code_muse.tools.common import generate_group_id
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# Bigger than this on either edge and we resize to save tokens.
|
|
26
|
+
MAX_IMAGE_EDGE = 2048
|
|
27
|
+
DEFAULT_MAX_HEIGHT = 768 # kept for backward-compat in tool signature
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _validate_and_prepare_image(
|
|
31
|
+
image_bytes: bytes,
|
|
32
|
+
source_path: str | None = None,
|
|
33
|
+
max_edge: int | None = None,
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
"""Verify image bytes, determine the real MIME type, and optionally resize.
|
|
36
|
+
|
|
37
|
+
The MIME type is determined from the decoded image content, not from the
|
|
38
|
+
file extension. If the image is resized, the output is normalized to PNG so
|
|
39
|
+
the returned bytes and MIME type stay in sync like civilized software.
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
with Image.open(io.BytesIO(image_bytes)) as verified_image:
|
|
43
|
+
verified_image.verify()
|
|
44
|
+
except UnidentifiedImageError as exc:
|
|
45
|
+
raise ValueError("File is not a valid image") from exc
|
|
46
|
+
except Exception as exc:
|
|
47
|
+
raise ValueError(f"Failed to verify image: {exc}") from exc
|
|
48
|
+
|
|
49
|
+
with Image.open(io.BytesIO(image_bytes)) as image:
|
|
50
|
+
image.load()
|
|
51
|
+
original_width, original_height = image.size
|
|
52
|
+
image_format = image.format
|
|
53
|
+
actual_media_type = Image.MIME.get(image_format or "")
|
|
54
|
+
|
|
55
|
+
if not actual_media_type or not actual_media_type.startswith("image/"):
|
|
56
|
+
raise ValueError("Could not determine a valid image MIME type")
|
|
57
|
+
|
|
58
|
+
guessed_media_type = None
|
|
59
|
+
if source_path:
|
|
60
|
+
guessed_media_type, _ = mimetypes.guess_type(source_path)
|
|
61
|
+
|
|
62
|
+
largest_edge = max(original_width, original_height)
|
|
63
|
+
was_resized = False
|
|
64
|
+
output_bytes = image_bytes
|
|
65
|
+
output_media_type = actual_media_type
|
|
66
|
+
output_width = original_width
|
|
67
|
+
output_height = original_height
|
|
68
|
+
|
|
69
|
+
if max_edge and largest_edge > max_edge:
|
|
70
|
+
ratio = max_edge / largest_edge
|
|
71
|
+
output_width = max(1, int(round(original_width * ratio)))
|
|
72
|
+
output_height = max(1, int(round(original_height * ratio)))
|
|
73
|
+
resized = image.resize(
|
|
74
|
+
(output_width, output_height), Image.Resampling.LANCZOS
|
|
75
|
+
)
|
|
76
|
+
output = io.BytesIO()
|
|
77
|
+
resized.save(output, format="PNG", optimize=True)
|
|
78
|
+
output.seek(0)
|
|
79
|
+
output_bytes = output.read()
|
|
80
|
+
output_media_type = "image/png"
|
|
81
|
+
was_resized = True
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"image_bytes": output_bytes,
|
|
85
|
+
"media_type": output_media_type,
|
|
86
|
+
"actual_media_type": actual_media_type,
|
|
87
|
+
"guessed_media_type": guessed_media_type,
|
|
88
|
+
"mime_type_matches_extension": guessed_media_type
|
|
89
|
+
in (None, output_media_type),
|
|
90
|
+
"original_width": original_width,
|
|
91
|
+
"original_height": original_height,
|
|
92
|
+
"output_width": output_width,
|
|
93
|
+
"output_height": output_height,
|
|
94
|
+
"was_resized": was_resized,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def load_image(
|
|
99
|
+
image_path: str,
|
|
100
|
+
max_height: int = DEFAULT_MAX_HEIGHT,
|
|
101
|
+
) -> ToolReturn | dict[str, Any]:
|
|
102
|
+
"""Load an image from the filesystem for visual analysis."""
|
|
103
|
+
group_id = generate_group_id("load_image", image_path)
|
|
104
|
+
emit_info(f"LOAD IMAGE 🖼️ {image_path}", message_group=group_id)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
image_file = Path(image_path)
|
|
108
|
+
|
|
109
|
+
if not image_file.exists():
|
|
110
|
+
error_msg = f"Image file not found: {image_path}"
|
|
111
|
+
emit_error(error_msg, message_group=group_id)
|
|
112
|
+
return {"success": False, "error": error_msg, "image_path": image_path}
|
|
113
|
+
|
|
114
|
+
if not image_file.is_file():
|
|
115
|
+
error_msg = f"Path is not a file: {image_path}"
|
|
116
|
+
emit_error(error_msg, message_group=group_id)
|
|
117
|
+
return {"success": False, "error": error_msg, "image_path": image_path}
|
|
118
|
+
|
|
119
|
+
image_bytes = image_file.read_bytes()
|
|
120
|
+
prepared_image = _validate_and_prepare_image(
|
|
121
|
+
image_bytes,
|
|
122
|
+
source_path=str(image_file),
|
|
123
|
+
max_edge=MAX_IMAGE_EDGE,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
emit_success(f"Loaded image: {image_path}", message_group=group_id)
|
|
127
|
+
|
|
128
|
+
return ToolReturn(
|
|
129
|
+
return_value=f"Image loaded from: {image_path}",
|
|
130
|
+
content=[
|
|
131
|
+
f"Here's the image from {image_file.name}:",
|
|
132
|
+
BinaryContent(
|
|
133
|
+
data=prepared_image["image_bytes"],
|
|
134
|
+
media_type=prepared_image["media_type"],
|
|
135
|
+
),
|
|
136
|
+
"Please analyze what you see in this image.",
|
|
137
|
+
],
|
|
138
|
+
metadata={
|
|
139
|
+
"success": True,
|
|
140
|
+
"image_path": image_path,
|
|
141
|
+
"media_type": prepared_image["media_type"],
|
|
142
|
+
"actual_media_type": prepared_image["actual_media_type"],
|
|
143
|
+
"guessed_media_type": prepared_image["guessed_media_type"],
|
|
144
|
+
"mime_type_matches_extension": prepared_image[
|
|
145
|
+
"mime_type_matches_extension"
|
|
146
|
+
],
|
|
147
|
+
"was_resized": prepared_image["was_resized"],
|
|
148
|
+
"original_size": [
|
|
149
|
+
prepared_image["original_width"],
|
|
150
|
+
prepared_image["original_height"],
|
|
151
|
+
],
|
|
152
|
+
"output_size": [
|
|
153
|
+
prepared_image["output_width"],
|
|
154
|
+
prepared_image["output_height"],
|
|
155
|
+
],
|
|
156
|
+
"max_height": max_height,
|
|
157
|
+
"max_edge": MAX_IMAGE_EDGE,
|
|
158
|
+
"timestamp": time.time(),
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
error_msg = f"Failed to load image: {str(e)}"
|
|
164
|
+
emit_error(error_msg, message_group=group_id)
|
|
165
|
+
logger.exception("Error loading image")
|
|
166
|
+
return {"success": False, "error": error_msg, "image_path": image_path}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def register_load_image(agent):
|
|
170
|
+
"""Register the image loading tool."""
|
|
171
|
+
|
|
172
|
+
@agent.tool
|
|
173
|
+
async def load_image_for_analysis(
|
|
174
|
+
context: RunContext,
|
|
175
|
+
image_path: str,
|
|
176
|
+
) -> ToolReturn | dict[str, Any]:
|
|
177
|
+
"""Load an image file so you can see and analyze it.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
image_path: Path to the image file.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
ToolReturn with the image, or error dict.
|
|
184
|
+
"""
|
|
185
|
+
return await load_image(image_path=image_path)
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""mitmproxy — MITM proxy for provider traffic capture and analysis."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from pydantic_ai import RunContext
|
|
8
|
+
|
|
9
|
+
from code_muse.messaging import emit_error, emit_info, emit_success
|
|
10
|
+
from code_muse.tools.meetin_proxy.proxy_manager import get_manager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MitmproxyConfig(BaseModel):
|
|
14
|
+
"""Configuration for mitmproxy capture session."""
|
|
15
|
+
|
|
16
|
+
target_domain: str = ""
|
|
17
|
+
listen_port: int | None = None
|
|
18
|
+
max_req_body: int = 10000
|
|
19
|
+
max_res_body: int = 100000
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MitmproxyResult(BaseModel):
|
|
23
|
+
"""Result of a mitmproxy capture session."""
|
|
24
|
+
|
|
25
|
+
proxy_port: int
|
|
26
|
+
flow_count: int
|
|
27
|
+
output_path: str
|
|
28
|
+
captured_at: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MitmproxyStatus(BaseModel):
|
|
32
|
+
"""Current status of the mitmproxy instance."""
|
|
33
|
+
|
|
34
|
+
running: bool
|
|
35
|
+
port: int | None = None
|
|
36
|
+
target: str = ""
|
|
37
|
+
flow_count: int = 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def register_mitmproxy(agent):
|
|
41
|
+
"""Register the mitmproxy tool."""
|
|
42
|
+
|
|
43
|
+
@agent.tool
|
|
44
|
+
async def mitmproxy(
|
|
45
|
+
context: RunContext,
|
|
46
|
+
command: str,
|
|
47
|
+
target_domain: str = "",
|
|
48
|
+
listen_port: int | None = None,
|
|
49
|
+
max_req_body: int = 10000,
|
|
50
|
+
max_res_body: int = 100000,
|
|
51
|
+
duration_seconds: int | None = None,
|
|
52
|
+
) -> MitmproxyResult | MitmproxyStatus | dict[str, Any]:
|
|
53
|
+
"""Control the mitmproxy MITM capture proxy.
|
|
54
|
+
|
|
55
|
+
Commands:
|
|
56
|
+
start — Spawn mitmdump, set proxy env vars, begin capture.
|
|
57
|
+
stop — Stop mitmdump, read flows, unset env vars, return results.
|
|
58
|
+
status — Return current proxy status.
|
|
59
|
+
capture — Convenience: start, optionally wait, then stop and return data.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
command: Action to perform — "start", "stop", "status", or "capture".
|
|
63
|
+
target_domain: Only capture traffic matching this domain.
|
|
64
|
+
listen_port: Port to listen on (auto-selected if omitted).
|
|
65
|
+
max_req_body: Maximum request body bytes to record.
|
|
66
|
+
max_res_body: Maximum response body bytes to record.
|
|
67
|
+
duration_seconds: For "capture", seconds to wait before auto-stopping.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
MitmproxyResult on stop/capture, MitmproxyStatus on status/start.
|
|
71
|
+
"""
|
|
72
|
+
manager = get_manager()
|
|
73
|
+
config = MitmproxyConfig(
|
|
74
|
+
target_domain=target_domain,
|
|
75
|
+
listen_port=listen_port,
|
|
76
|
+
max_req_body=max_req_body,
|
|
77
|
+
max_res_body=max_res_body,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if command == "start":
|
|
81
|
+
try:
|
|
82
|
+
port = manager.start(
|
|
83
|
+
target=config.target_domain,
|
|
84
|
+
port=config.listen_port,
|
|
85
|
+
max_req_body=config.max_req_body,
|
|
86
|
+
max_res_body=config.max_res_body,
|
|
87
|
+
)
|
|
88
|
+
return MitmproxyStatus(
|
|
89
|
+
running=True,
|
|
90
|
+
port=port,
|
|
91
|
+
target=config.target_domain,
|
|
92
|
+
flow_count=0,
|
|
93
|
+
)
|
|
94
|
+
except Exception as exc:
|
|
95
|
+
emit_error(f"Failed to start mitmproxy: {exc}")
|
|
96
|
+
return {
|
|
97
|
+
"success": False,
|
|
98
|
+
"error": str(exc),
|
|
99
|
+
"command": command,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
elif command == "stop":
|
|
103
|
+
data = manager.stop()
|
|
104
|
+
status = manager.status()
|
|
105
|
+
# After stop, status will show not running
|
|
106
|
+
flow_count = 0
|
|
107
|
+
output_path = ""
|
|
108
|
+
captured_at = ""
|
|
109
|
+
if data and isinstance(data, dict):
|
|
110
|
+
meta = data.get("meta", {})
|
|
111
|
+
flow_count = meta.get("total_flows", 0)
|
|
112
|
+
captured_at = meta.get("captured_at", "")
|
|
113
|
+
# Write a safe temp copy so caller can inspect flows
|
|
114
|
+
import tempfile
|
|
115
|
+
|
|
116
|
+
fd, output_path = tempfile.mkstemp(
|
|
117
|
+
prefix="mitmproxy_result_", suffix=".json"
|
|
118
|
+
)
|
|
119
|
+
with open(fd, "w", encoding="utf-8") as f:
|
|
120
|
+
json.dump(data, f, indent=2, default=str)
|
|
121
|
+
|
|
122
|
+
if data:
|
|
123
|
+
emit_success(
|
|
124
|
+
f"mitmproxy stopped. Captured {flow_count} flow(s). "
|
|
125
|
+
f"Data written to {output_path}"
|
|
126
|
+
)
|
|
127
|
+
return MitmproxyResult(
|
|
128
|
+
proxy_port=status.get("port") or 0,
|
|
129
|
+
flow_count=flow_count,
|
|
130
|
+
output_path=output_path,
|
|
131
|
+
captured_at=captured_at,
|
|
132
|
+
)
|
|
133
|
+
else:
|
|
134
|
+
emit_info("mitmproxy stopped. No capture data found.")
|
|
135
|
+
return MitmproxyResult(
|
|
136
|
+
proxy_port=status.get("port") or 0,
|
|
137
|
+
flow_count=0,
|
|
138
|
+
output_path="",
|
|
139
|
+
captured_at="",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
elif command == "status":
|
|
143
|
+
st = manager.status()
|
|
144
|
+
return MitmproxyStatus(
|
|
145
|
+
running=st["running"],
|
|
146
|
+
port=st["port"],
|
|
147
|
+
target=st["target"],
|
|
148
|
+
flow_count=st["flow_count"],
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
elif command == "capture":
|
|
152
|
+
try:
|
|
153
|
+
port = manager.start(
|
|
154
|
+
target=config.target_domain,
|
|
155
|
+
port=config.listen_port,
|
|
156
|
+
max_req_body=config.max_req_body,
|
|
157
|
+
max_res_body=config.max_res_body,
|
|
158
|
+
)
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
emit_error(f"Failed to start mitmproxy: {exc}")
|
|
161
|
+
return {
|
|
162
|
+
"success": False,
|
|
163
|
+
"error": str(exc),
|
|
164
|
+
"command": command,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if duration_seconds and duration_seconds > 0:
|
|
168
|
+
emit_info(
|
|
169
|
+
f"mitmproxy capturing for {duration_seconds}s on port {port}..."
|
|
170
|
+
)
|
|
171
|
+
await _async_sleep(duration_seconds)
|
|
172
|
+
else:
|
|
173
|
+
emit_info(
|
|
174
|
+
f"mitmproxy capturing on port {port}. "
|
|
175
|
+
"Call mitmproxy with command='stop' when finished."
|
|
176
|
+
)
|
|
177
|
+
return MitmproxyStatus(
|
|
178
|
+
running=True,
|
|
179
|
+
port=port,
|
|
180
|
+
target=config.target_domain,
|
|
181
|
+
flow_count=0,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
data = manager.stop()
|
|
185
|
+
flow_count = 0
|
|
186
|
+
output_path = ""
|
|
187
|
+
captured_at = ""
|
|
188
|
+
if data and isinstance(data, dict):
|
|
189
|
+
meta = data.get("meta", {})
|
|
190
|
+
flow_count = meta.get("total_flows", 0)
|
|
191
|
+
captured_at = meta.get("captured_at", "")
|
|
192
|
+
import tempfile
|
|
193
|
+
|
|
194
|
+
fd, output_path = tempfile.mkstemp(
|
|
195
|
+
prefix="mitmproxy_result_", suffix=".json"
|
|
196
|
+
)
|
|
197
|
+
with open(fd, "w", encoding="utf-8") as f:
|
|
198
|
+
json.dump(data, f, indent=2, default=str)
|
|
199
|
+
|
|
200
|
+
if data:
|
|
201
|
+
emit_success(
|
|
202
|
+
f"mitmproxy capture complete. {flow_count} flow(s). "
|
|
203
|
+
f"Data: {output_path}"
|
|
204
|
+
)
|
|
205
|
+
return MitmproxyResult(
|
|
206
|
+
proxy_port=port,
|
|
207
|
+
flow_count=flow_count,
|
|
208
|
+
output_path=output_path,
|
|
209
|
+
captured_at=captured_at,
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
emit_info("mitmproxy capture complete. No data captured.")
|
|
213
|
+
return MitmproxyResult(
|
|
214
|
+
proxy_port=port,
|
|
215
|
+
flow_count=0,
|
|
216
|
+
output_path="",
|
|
217
|
+
captured_at="",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
else:
|
|
221
|
+
return {
|
|
222
|
+
"success": False,
|
|
223
|
+
"error": (
|
|
224
|
+
f"Unknown command: {command}. Use start, stop, status, or capture."
|
|
225
|
+
),
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return mitmproxy
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
async def _async_sleep(seconds: float) -> None:
|
|
232
|
+
"""Async sleep helper."""
|
|
233
|
+
import asyncio
|
|
234
|
+
|
|
235
|
+
await asyncio.sleep(seconds)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# Register startup callback for mitmproxy availability check
|
|
239
|
+
from code_muse.tools.meetin_proxy.register_callbacks import ( # noqa: E402
|
|
240
|
+
register as _register_mitmproxy_callbacks,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
_register_mitmproxy_callbacks()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""mitmproxy capture addon — injected into mitmdump via -s flag."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TrafficCapture:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.flows = []
|
|
11
|
+
self.target = os.environ.get("MITMPROXY_TARGET", "")
|
|
12
|
+
self.output = os.environ.get("MITMPROXY_OUTPUT", "/tmp/mitmproxy_capture.json")
|
|
13
|
+
self.max_req_body = int(os.environ.get("MITMPROXY_MAX_REQ_BODY", "10000"))
|
|
14
|
+
self.max_res_body = int(os.environ.get("MITMPROXY_MAX_RES_BODY", "100000"))
|
|
15
|
+
|
|
16
|
+
def _should_capture(self, flow) -> bool:
|
|
17
|
+
if not self.target:
|
|
18
|
+
return True
|
|
19
|
+
return self.target in flow.request.pretty_host
|
|
20
|
+
|
|
21
|
+
def response(self, flow):
|
|
22
|
+
if not self._should_capture(flow):
|
|
23
|
+
return
|
|
24
|
+
try:
|
|
25
|
+
req_body = flow.request.get_text(strict=False) or ""
|
|
26
|
+
res_body = flow.response.get_text(strict=False) or ""
|
|
27
|
+
except Exception:
|
|
28
|
+
req_body = f"<binary: {len(flow.request.content)} bytes>"
|
|
29
|
+
res_body = f"<binary: {len(flow.response.content)} bytes>"
|
|
30
|
+
|
|
31
|
+
if len(req_body) > self.max_req_body:
|
|
32
|
+
req_body = req_body[: self.max_req_body] + "\n... <truncated>"
|
|
33
|
+
if len(res_body) > self.max_res_body:
|
|
34
|
+
res_body = res_body[: self.max_res_body] + "\n... <truncated>"
|
|
35
|
+
|
|
36
|
+
entry = {
|
|
37
|
+
"url": flow.request.pretty_url,
|
|
38
|
+
"method": flow.request.method,
|
|
39
|
+
"host": flow.request.pretty_host,
|
|
40
|
+
"path": flow.request.path,
|
|
41
|
+
"request_headers": dict(flow.request.headers),
|
|
42
|
+
"request_body": req_body,
|
|
43
|
+
"status_code": flow.response.status_code,
|
|
44
|
+
"response_headers": dict(flow.response.headers),
|
|
45
|
+
"response_body": res_body,
|
|
46
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
47
|
+
"content_type": flow.response.headers.get("content-type", ""),
|
|
48
|
+
}
|
|
49
|
+
self.flows.append(entry)
|
|
50
|
+
|
|
51
|
+
def error(self, flow):
|
|
52
|
+
if not self._should_capture(flow):
|
|
53
|
+
return
|
|
54
|
+
entry = {
|
|
55
|
+
"url": flow.request.pretty_url if flow.request else "N/A",
|
|
56
|
+
"method": flow.request.method if flow.request else "N/A",
|
|
57
|
+
"error": str(flow.error.msg) if flow.error else "Unknown error",
|
|
58
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
59
|
+
}
|
|
60
|
+
self.flows.append(entry)
|
|
61
|
+
|
|
62
|
+
def done(self):
|
|
63
|
+
out_dir = os.path.dirname(self.output)
|
|
64
|
+
if out_dir:
|
|
65
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
66
|
+
with open(self.output, "w") as f:
|
|
67
|
+
json.dump(
|
|
68
|
+
{
|
|
69
|
+
"meta": {
|
|
70
|
+
"captured_at": datetime.now(UTC).isoformat(),
|
|
71
|
+
"target_filter": self.target,
|
|
72
|
+
"total_flows": len(self.flows),
|
|
73
|
+
},
|
|
74
|
+
"flows": self.flows,
|
|
75
|
+
},
|
|
76
|
+
f,
|
|
77
|
+
indent=2,
|
|
78
|
+
default=str,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
addons = [TrafficCapture()]
|