shotgun-sh 0.2.7.dev1__tar.gz → 0.2.8.dev1__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.
Potentially problematic release.
This version of shotgun-sh might be problematic. Click here for more details.
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/PKG-INFO +3 -1
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/README.md +76 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/pyproject.toml +3 -1
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/agent_manager.py +191 -13
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/common.py +42 -17
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/file_management.py +55 -9
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/main.py +54 -10
- shotgun_sh-0.2.8.dev1/src/shotgun/prompts/agents/specify.j2 +318 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/app.py +116 -0
- shotgun_sh-0.2.7.dev1/src/shotgun/prompts/agents/specify.j2 +0 -51
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/.gitignore +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/LICENSE +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/README_PYPI.md +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/hatch_build.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/config/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/config/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/config/manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/config/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/config/provider.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/conversation_history.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/conversation_manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/export.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/compaction.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/context_extraction.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/history_building.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/history_processors.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/message_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/token_counting/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/token_counting/anthropic.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/token_counting/base.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/token_counting/openai.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/token_counting/sentencepiece_counter.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/token_counting/tokenizer_cache.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/token_counting/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/history/token_estimation.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/llm.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/messages.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/plan.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/research.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/specify.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tasks.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/codebase/codebase_shell.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/codebase/directory_lister.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/codebase/file_read.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/codebase/query_graph.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/codebase/retrieve_code.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/web_search/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/web_search/anthropic.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/web_search/gemini.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/web_search/openai.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/tools/web_search/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/agents/usage_manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/api_endpoints.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/build_constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/codebase/commands.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/export.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/feedback.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/plan.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/research.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/specify.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/tasks.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/update.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/cli/utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/change_detector.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/code_retrieval.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/cypher_models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/ingestor.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/language_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/manager.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/nl_query.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/core/parser_loader.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/codebase/service.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/llm_proxy/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/llm_proxy/clients.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/llm_proxy/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/logging_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/posthog_telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/export.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/partials/codebase_understanding.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/partials/content_formatting.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/partials/interactive_mode.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/plan.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/research.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/state/system_state.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/agents/tasks.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/codebase/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/codebase/cypher_query_patterns.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/codebase/cypher_system.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/codebase/enhanced_query_context.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/codebase/partials/cypher_rules.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/codebase/partials/graph_schema.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/codebase/partials/temporal_context.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/history/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/history/incremental_summarization.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/history/summarization.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/loader.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/prompts/tools/web_search.j2 +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/py.typed +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/sdk/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/sdk/codebase.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/sdk/exceptions.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/sdk/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/sdk/services.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/sentry_telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/shotgun_web/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/shotgun_web/client.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/shotgun_web/constants.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/shotgun_web/models.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/telemetry.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/commands/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/components/prompt_input.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/components/spinner.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/components/splash.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/components/vertical_tail.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/filtered_codebase_service.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/chat.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/chat.tcss +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/chat_screen/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/chat_screen/command_providers.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/chat_screen/hint_message.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/chat_screen/history.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/directory_setup.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/feedback.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/model_picker.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/provider_config.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/shotgun_auth.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/splash.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/screens/welcome.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/styles.tcss +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/utils/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/tui/utils/mode_progress.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/utils/__init__.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/utils/datetime_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/utils/env_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/utils/file_system_utils.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/utils/source_detection.py +0 -0
- {shotgun_sh-0.2.7.dev1 → shotgun_sh-0.2.8.dev1}/src/shotgun/utils/update_checker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shotgun-sh
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8.dev1
|
|
4
4
|
Summary: AI-powered research, planning, and task management CLI tool
|
|
5
5
|
Project-URL: Homepage, https://shotgun.sh/
|
|
6
6
|
Project-URL: Repository, https://github.com/shotgun-sh/shotgun
|
|
@@ -34,7 +34,9 @@ Requires-Dist: pydantic-ai>=0.0.14
|
|
|
34
34
|
Requires-Dist: rich>=13.0.0
|
|
35
35
|
Requires-Dist: sentencepiece>=0.2.0
|
|
36
36
|
Requires-Dist: sentry-sdk[pure-eval]>=2.0.0
|
|
37
|
+
Requires-Dist: tenacity>=8.0.0
|
|
37
38
|
Requires-Dist: textual-dev>=1.7.0
|
|
39
|
+
Requires-Dist: textual-serve>=0.1.0
|
|
38
40
|
Requires-Dist: textual>=6.1.0
|
|
39
41
|
Requires-Dist: tiktoken>=0.7.0
|
|
40
42
|
Requires-Dist: tree-sitter-go>=0.23.0
|
|
@@ -408,6 +408,82 @@ export SENTRY_DSN=your-sentry-dsn
|
|
|
408
408
|
- **Opt-in for development**: Telemetry requires explicit environment variables
|
|
409
409
|
- **Automatic in production**: Production builds include telemetry for error tracking
|
|
410
410
|
|
|
411
|
+
## Docker
|
|
412
|
+
|
|
413
|
+
Run Shotgun in a Docker container with web access.
|
|
414
|
+
|
|
415
|
+
### Using Pre-built Images (Recommended)
|
|
416
|
+
|
|
417
|
+
Pull the official image from GitHub Container Registry:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
# Pull latest stable version
|
|
421
|
+
docker pull ghcr.io/shotgun-sh/shotgun:latest
|
|
422
|
+
|
|
423
|
+
# Or pull a specific version
|
|
424
|
+
docker pull ghcr.io/shotgun-sh/shotgun:v0.1.0
|
|
425
|
+
|
|
426
|
+
# Or pull development version
|
|
427
|
+
docker pull ghcr.io/shotgun-sh/shotgun:dev
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Then run:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
docker run -p 8000:8000 \
|
|
434
|
+
-v $(pwd):/workspace \
|
|
435
|
+
-v ~/.shotgun-sh:/home/shotgun/.shotgun-sh \
|
|
436
|
+
ghcr.io/shotgun-sh/shotgun:latest --no-update-check
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Building from Source (Optional)
|
|
440
|
+
|
|
441
|
+
If you prefer to build the image yourself:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
docker build -t shotgun:latest .
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Run the Container
|
|
448
|
+
|
|
449
|
+
The container requires two volume mounts:
|
|
450
|
+
1. Your codebase/workspace directory (mounted to `/workspace`)
|
|
451
|
+
2. Config directory for API keys and settings (mounted to `/home/shotgun/.shotgun-sh`)
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
# Basic usage (serves on port 8000)
|
|
455
|
+
docker run -p 8000:8000 \
|
|
456
|
+
-v $(pwd):/workspace \
|
|
457
|
+
-v ~/.shotgun-sh:/home/shotgun/.shotgun-sh \
|
|
458
|
+
ghcr.io/shotgun-sh/shotgun:latest --no-update-check
|
|
459
|
+
|
|
460
|
+
# Custom port
|
|
461
|
+
docker run -p 3000:3000 \
|
|
462
|
+
-v $(pwd):/workspace \
|
|
463
|
+
-v ~/.shotgun-sh:/home/shotgun/.shotgun-sh \
|
|
464
|
+
ghcr.io/shotgun-sh/shotgun:latest --no-update-check --port 3000
|
|
465
|
+
|
|
466
|
+
# Different codebase directory
|
|
467
|
+
docker run -p 8000:8000 \
|
|
468
|
+
-v /path/to/your/project:/workspace \
|
|
469
|
+
-v ~/.shotgun-sh:/home/shotgun/.shotgun-sh \
|
|
470
|
+
ghcr.io/shotgun-sh/shotgun:latest --no-update-check
|
|
471
|
+
|
|
472
|
+
# Run in background with auto-restart
|
|
473
|
+
docker run -d --restart unless-stopped \
|
|
474
|
+
--name shotgun-web \
|
|
475
|
+
-p 8000:8000 \
|
|
476
|
+
-v $(pwd):/workspace \
|
|
477
|
+
-v ~/.shotgun-sh:/home/shotgun/.shotgun-sh \
|
|
478
|
+
ghcr.io/shotgun-sh/shotgun:latest --no-update-check
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Configuration
|
|
482
|
+
|
|
483
|
+
On first run, configure your API keys through the web UI. The configuration will persist in the mounted `~/.shotgun-sh` directory.
|
|
484
|
+
|
|
485
|
+
Access the web interface at `http://localhost:8000` (or your custom port).
|
|
486
|
+
|
|
411
487
|
## Support
|
|
412
488
|
|
|
413
489
|
Join our discord https://discord.gg/5RmY6J2N7s
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "shotgun-sh"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.8.dev1"
|
|
4
4
|
description = "AI-powered research, planning, and task management CLI tool"
|
|
5
5
|
readme = "README_PYPI.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -32,6 +32,7 @@ dependencies = [
|
|
|
32
32
|
"posthog>=3.0.0",
|
|
33
33
|
"textual>=6.1.0",
|
|
34
34
|
"textual-dev>=1.7.0",
|
|
35
|
+
"textual-serve>=0.1.0",
|
|
35
36
|
"kuzu>=0.7.0",
|
|
36
37
|
"tree-sitter>=0.21.0",
|
|
37
38
|
"tree-sitter-python>=0.23.0",
|
|
@@ -46,6 +47,7 @@ dependencies = [
|
|
|
46
47
|
"sentencepiece>=0.2.0",
|
|
47
48
|
"packaging>=23.0",
|
|
48
49
|
"genai-prices>=0.0.27",
|
|
50
|
+
"tenacity>=8.0.0",
|
|
49
51
|
]
|
|
50
52
|
|
|
51
53
|
[project.urls]
|
|
@@ -4,9 +4,17 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
from collections.abc import AsyncIterable, Sequence
|
|
6
6
|
from dataclasses import dataclass, field, is_dataclass, replace
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
from typing import TYPE_CHECKING, Any, cast
|
|
8
9
|
|
|
9
10
|
import logfire
|
|
11
|
+
from tenacity import (
|
|
12
|
+
before_sleep_log,
|
|
13
|
+
retry,
|
|
14
|
+
retry_if_exception,
|
|
15
|
+
stop_after_attempt,
|
|
16
|
+
wait_exponential,
|
|
17
|
+
)
|
|
10
18
|
|
|
11
19
|
if TYPE_CHECKING:
|
|
12
20
|
from shotgun.agents.conversation_history import ConversationState
|
|
@@ -55,6 +63,35 @@ from .tasks import create_tasks_agent
|
|
|
55
63
|
logger = logging.getLogger(__name__)
|
|
56
64
|
|
|
57
65
|
|
|
66
|
+
def _is_retryable_error(exception: BaseException) -> bool:
|
|
67
|
+
"""Check if exception should trigger a retry.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
exception: The exception to check.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if the exception is a transient error that should be retried.
|
|
74
|
+
"""
|
|
75
|
+
# ValueError for truncated/incomplete JSON
|
|
76
|
+
if isinstance(exception, ValueError):
|
|
77
|
+
error_str = str(exception)
|
|
78
|
+
return "EOF while parsing" in error_str or (
|
|
79
|
+
"JSON" in error_str and "parsing" in error_str
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# API errors (overload, rate limits)
|
|
83
|
+
exception_name = type(exception).__name__
|
|
84
|
+
if "APIStatusError" in exception_name:
|
|
85
|
+
error_str = str(exception)
|
|
86
|
+
return "overload" in error_str.lower() or "rate" in error_str.lower()
|
|
87
|
+
|
|
88
|
+
# Network errors
|
|
89
|
+
if "ConnectionError" in exception_name or "TimeoutError" in exception_name:
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
|
|
58
95
|
class MessageHistoryUpdated(Message):
|
|
59
96
|
"""Event posted when the message history is updated."""
|
|
60
97
|
|
|
@@ -268,6 +305,49 @@ class AgentManager(Widget):
|
|
|
268
305
|
f"Invalid agent type: {agent_type}. Must be one of: {', '.join(e.value for e in AgentType)}"
|
|
269
306
|
) from None
|
|
270
307
|
|
|
308
|
+
@retry(
|
|
309
|
+
stop=stop_after_attempt(3),
|
|
310
|
+
wait=wait_exponential(multiplier=1, min=1, max=8),
|
|
311
|
+
retry=retry_if_exception(_is_retryable_error),
|
|
312
|
+
before_sleep=before_sleep_log(logger, logging.WARNING),
|
|
313
|
+
reraise=True,
|
|
314
|
+
)
|
|
315
|
+
async def _run_agent_with_retry(
|
|
316
|
+
self,
|
|
317
|
+
agent: Agent[AgentDeps, AgentResponse],
|
|
318
|
+
prompt: str | None,
|
|
319
|
+
deps: AgentDeps,
|
|
320
|
+
usage_limits: UsageLimits | None,
|
|
321
|
+
message_history: list[ModelMessage],
|
|
322
|
+
event_stream_handler: Any,
|
|
323
|
+
**kwargs: Any,
|
|
324
|
+
) -> AgentRunResult[AgentResponse]:
|
|
325
|
+
"""Run agent with automatic retry on transient errors.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
agent: The agent to run.
|
|
329
|
+
prompt: Optional prompt to send to the agent.
|
|
330
|
+
deps: Agent dependencies.
|
|
331
|
+
usage_limits: Optional usage limits.
|
|
332
|
+
message_history: Message history to provide to agent.
|
|
333
|
+
event_stream_handler: Event handler for streaming.
|
|
334
|
+
**kwargs: Additional keyword arguments.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
The agent run result.
|
|
338
|
+
|
|
339
|
+
Raises:
|
|
340
|
+
Various exceptions if all retries fail.
|
|
341
|
+
"""
|
|
342
|
+
return await agent.run(
|
|
343
|
+
prompt,
|
|
344
|
+
deps=deps,
|
|
345
|
+
usage_limits=usage_limits,
|
|
346
|
+
message_history=message_history,
|
|
347
|
+
event_stream_handler=event_stream_handler,
|
|
348
|
+
**kwargs,
|
|
349
|
+
)
|
|
350
|
+
|
|
271
351
|
async def run(
|
|
272
352
|
self,
|
|
273
353
|
prompt: str | None = None,
|
|
@@ -394,8 +474,9 @@ class AgentManager(Widget):
|
|
|
394
474
|
)
|
|
395
475
|
|
|
396
476
|
try:
|
|
397
|
-
result: AgentRunResult[AgentResponse] = await self.
|
|
398
|
-
|
|
477
|
+
result: AgentRunResult[AgentResponse] = await self._run_agent_with_retry(
|
|
478
|
+
agent=self.current_agent,
|
|
479
|
+
prompt=prompt,
|
|
399
480
|
deps=deps,
|
|
400
481
|
usage_limits=usage_limits,
|
|
401
482
|
message_history=message_history,
|
|
@@ -404,6 +485,36 @@ class AgentManager(Widget):
|
|
|
404
485
|
else None,
|
|
405
486
|
**kwargs,
|
|
406
487
|
)
|
|
488
|
+
except ValueError as e:
|
|
489
|
+
# Handle truncated/incomplete JSON in tool calls specifically
|
|
490
|
+
error_str = str(e)
|
|
491
|
+
if "EOF while parsing" in error_str or (
|
|
492
|
+
"JSON" in error_str and "parsing" in error_str
|
|
493
|
+
):
|
|
494
|
+
logger.error(
|
|
495
|
+
"Tool call with truncated/incomplete JSON arguments detected",
|
|
496
|
+
extra={
|
|
497
|
+
"agent_mode": self._current_agent_type.value,
|
|
498
|
+
"model_name": model_name,
|
|
499
|
+
"error": error_str,
|
|
500
|
+
},
|
|
501
|
+
)
|
|
502
|
+
logfire.error(
|
|
503
|
+
"Tool call with truncated JSON arguments",
|
|
504
|
+
agent_mode=self._current_agent_type.value,
|
|
505
|
+
model_name=model_name,
|
|
506
|
+
error=error_str,
|
|
507
|
+
)
|
|
508
|
+
# Add helpful hint message for the user
|
|
509
|
+
self.ui_message_history.append(
|
|
510
|
+
HintMessage(
|
|
511
|
+
message="⚠️ The agent attempted an operation with arguments that were too large (truncated JSON). "
|
|
512
|
+
"Try breaking your request into smaller steps or more focused contracts."
|
|
513
|
+
)
|
|
514
|
+
)
|
|
515
|
+
self._post_messages_updated()
|
|
516
|
+
# Re-raise to maintain error visibility
|
|
517
|
+
raise
|
|
407
518
|
except Exception as e:
|
|
408
519
|
# Log the error with full stack trace to shotgun.log and Logfire
|
|
409
520
|
logger.exception(
|
|
@@ -427,13 +538,40 @@ class AgentManager(Widget):
|
|
|
427
538
|
|
|
428
539
|
# Agent ALWAYS returns AgentResponse with structured output
|
|
429
540
|
agent_response = result.output
|
|
430
|
-
logger.debug(
|
|
541
|
+
logger.debug(
|
|
542
|
+
"Agent returned structured AgentResponse",
|
|
543
|
+
extra={
|
|
544
|
+
"has_response": agent_response.response is not None,
|
|
545
|
+
"response_length": len(agent_response.response)
|
|
546
|
+
if agent_response.response
|
|
547
|
+
else 0,
|
|
548
|
+
"response_preview": agent_response.response[:100] + "..."
|
|
549
|
+
if agent_response.response and len(agent_response.response) > 100
|
|
550
|
+
else agent_response.response or "(empty)",
|
|
551
|
+
"has_clarifying_questions": bool(agent_response.clarifying_questions),
|
|
552
|
+
"num_clarifying_questions": len(agent_response.clarifying_questions)
|
|
553
|
+
if agent_response.clarifying_questions
|
|
554
|
+
else 0,
|
|
555
|
+
},
|
|
556
|
+
)
|
|
431
557
|
|
|
432
558
|
# Always add the agent's response messages to maintain conversation history
|
|
433
559
|
self.ui_message_history = original_messages + cast(
|
|
434
560
|
list[ModelRequest | ModelResponse | HintMessage], result.new_messages()
|
|
435
561
|
)
|
|
436
562
|
|
|
563
|
+
# Get file operations early so we can use them for contextual messages
|
|
564
|
+
file_operations = deps.file_tracker.operations.copy()
|
|
565
|
+
self.recently_change_files = file_operations
|
|
566
|
+
|
|
567
|
+
logger.debug(
|
|
568
|
+
"File operations tracked",
|
|
569
|
+
extra={
|
|
570
|
+
"num_file_operations": len(file_operations),
|
|
571
|
+
"operation_files": [Path(op.file_path).name for op in file_operations],
|
|
572
|
+
},
|
|
573
|
+
)
|
|
574
|
+
|
|
437
575
|
# Check if there are clarifying questions
|
|
438
576
|
if agent_response.clarifying_questions:
|
|
439
577
|
logger.info(
|
|
@@ -480,12 +618,50 @@ class AgentManager(Widget):
|
|
|
480
618
|
response_text=agent_response.response,
|
|
481
619
|
)
|
|
482
620
|
)
|
|
621
|
+
|
|
622
|
+
# Post UI update with hint messages and file operations
|
|
623
|
+
logger.debug(
|
|
624
|
+
"Posting UI update for Q&A mode with hint messages and file operations"
|
|
625
|
+
)
|
|
626
|
+
self._post_messages_updated(file_operations)
|
|
483
627
|
else:
|
|
484
|
-
# No clarifying questions -
|
|
628
|
+
# No clarifying questions - show the response or a default success message
|
|
485
629
|
if agent_response.response and agent_response.response.strip():
|
|
630
|
+
logger.debug(
|
|
631
|
+
"Adding agent response as hint",
|
|
632
|
+
extra={
|
|
633
|
+
"response_preview": agent_response.response[:100] + "..."
|
|
634
|
+
if len(agent_response.response) > 100
|
|
635
|
+
else agent_response.response,
|
|
636
|
+
"has_file_operations": len(file_operations) > 0,
|
|
637
|
+
},
|
|
638
|
+
)
|
|
486
639
|
self.ui_message_history.append(
|
|
487
640
|
HintMessage(message=agent_response.response)
|
|
488
641
|
)
|
|
642
|
+
else:
|
|
643
|
+
# Fallback: response is empty or whitespace
|
|
644
|
+
logger.debug(
|
|
645
|
+
"Agent response was empty, using fallback completion message",
|
|
646
|
+
extra={"has_file_operations": len(file_operations) > 0},
|
|
647
|
+
)
|
|
648
|
+
# Show contextual message based on whether files were modified
|
|
649
|
+
if file_operations:
|
|
650
|
+
self.ui_message_history.append(
|
|
651
|
+
HintMessage(
|
|
652
|
+
message="✅ Task completed - files have been modified"
|
|
653
|
+
)
|
|
654
|
+
)
|
|
655
|
+
else:
|
|
656
|
+
self.ui_message_history.append(
|
|
657
|
+
HintMessage(message="✅ Task completed")
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# Post UI update immediately so user sees the response without delay
|
|
661
|
+
logger.debug(
|
|
662
|
+
"Posting immediate UI update with hint message and file operations"
|
|
663
|
+
)
|
|
664
|
+
self._post_messages_updated(file_operations)
|
|
489
665
|
|
|
490
666
|
# Apply compaction to persistent message history to prevent cascading growth
|
|
491
667
|
all_messages = result.all_messages()
|
|
@@ -517,16 +693,18 @@ class AgentManager(Widget):
|
|
|
517
693
|
self.message_history = all_messages
|
|
518
694
|
|
|
519
695
|
usage = result.usage()
|
|
520
|
-
deps.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
696
|
+
if hasattr(deps, "llm_model") and deps.llm_model is not None:
|
|
697
|
+
deps.usage_manager.add_usage(
|
|
698
|
+
usage, model_name=deps.llm_model.name, provider=deps.llm_model.provider
|
|
699
|
+
)
|
|
700
|
+
else:
|
|
701
|
+
logger.warning(
|
|
702
|
+
"llm_model is None, skipping usage tracking",
|
|
703
|
+
extra={"agent_mode": self._current_agent_type.value},
|
|
704
|
+
)
|
|
527
705
|
|
|
528
|
-
#
|
|
529
|
-
|
|
706
|
+
# UI updates are now posted immediately in each branch (Q&A or non-Q&A)
|
|
707
|
+
# before compaction, so no duplicate posting needed here
|
|
530
708
|
|
|
531
709
|
return result
|
|
532
710
|
|
|
@@ -384,23 +384,48 @@ def get_agent_existing_files(agent_mode: AgentType | None = None) -> list[str]:
|
|
|
384
384
|
relative_path = file_path.relative_to(base_path)
|
|
385
385
|
existing_files.append(str(relative_path))
|
|
386
386
|
else:
|
|
387
|
-
# For other agents, check
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
#
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
#
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
387
|
+
# For other agents, check files/directories they have access to
|
|
388
|
+
allowed_paths_raw = AGENT_DIRECTORIES[agent_mode]
|
|
389
|
+
|
|
390
|
+
# Convert single Path/string to list of Paths for uniform handling
|
|
391
|
+
if isinstance(allowed_paths_raw, str):
|
|
392
|
+
# Special case: "*" means export agent (shouldn't reach here but handle it)
|
|
393
|
+
allowed_paths = (
|
|
394
|
+
[Path(allowed_paths_raw)] if allowed_paths_raw != "*" else []
|
|
395
|
+
)
|
|
396
|
+
elif isinstance(allowed_paths_raw, Path):
|
|
397
|
+
allowed_paths = [allowed_paths_raw]
|
|
398
|
+
else:
|
|
399
|
+
# Already a list
|
|
400
|
+
allowed_paths = allowed_paths_raw
|
|
401
|
+
|
|
402
|
+
# Check each allowed path
|
|
403
|
+
for allowed_path in allowed_paths:
|
|
404
|
+
allowed_str = str(allowed_path)
|
|
405
|
+
|
|
406
|
+
# Check if it's a directory (no .md suffix)
|
|
407
|
+
if not allowed_path.suffix or not allowed_str.endswith(".md"):
|
|
408
|
+
# It's a directory - list all files within it
|
|
409
|
+
dir_path = base_path / allowed_str
|
|
410
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
411
|
+
for file_path in dir_path.rglob("*"):
|
|
412
|
+
if file_path.is_file():
|
|
413
|
+
relative_path = file_path.relative_to(base_path)
|
|
414
|
+
existing_files.append(str(relative_path))
|
|
415
|
+
else:
|
|
416
|
+
# It's a file - check if it exists
|
|
417
|
+
file_path = base_path / allowed_str
|
|
418
|
+
if file_path.exists():
|
|
419
|
+
existing_files.append(allowed_str)
|
|
420
|
+
|
|
421
|
+
# Also check for associated directory (e.g., research/ for research.md)
|
|
422
|
+
base_name = allowed_str.replace(".md", "")
|
|
423
|
+
dir_path = base_path / base_name
|
|
424
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
425
|
+
for file_path in dir_path.rglob("*"):
|
|
426
|
+
if file_path.is_file():
|
|
427
|
+
relative_path = file_path.relative_to(base_path)
|
|
428
|
+
existing_files.append(str(relative_path))
|
|
404
429
|
|
|
405
430
|
return existing_files
|
|
406
431
|
|
|
@@ -15,11 +15,18 @@ from shotgun.utils.file_system_utils import get_shotgun_base_path
|
|
|
15
15
|
logger = get_logger(__name__)
|
|
16
16
|
|
|
17
17
|
# Map agent modes to their allowed directories/files (in workflow order)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
# Values can be:
|
|
19
|
+
# - A Path: exact file (e.g., Path("research.md"))
|
|
20
|
+
# - A list of Paths: multiple allowed files/directories (e.g., [Path("specification.md"), Path("contracts")])
|
|
21
|
+
# - "*": any file except protected files (for export agent)
|
|
22
|
+
AGENT_DIRECTORIES: dict[AgentType, str | Path | list[Path]] = {
|
|
23
|
+
AgentType.RESEARCH: Path("research.md"),
|
|
24
|
+
AgentType.SPECIFY: [
|
|
25
|
+
Path("specification.md"),
|
|
26
|
+
Path("contracts"),
|
|
27
|
+
], # Specify can write specs and contract files
|
|
28
|
+
AgentType.PLAN: Path("plan.md"),
|
|
29
|
+
AgentType.TASKS: Path("tasks.md"),
|
|
23
30
|
AgentType.EXPORT: "*", # Export agent can write anywhere except protected files
|
|
24
31
|
}
|
|
25
32
|
|
|
@@ -60,13 +67,52 @@ def _validate_agent_scoped_path(filename: str, agent_mode: AgentType | None) ->
|
|
|
60
67
|
# Allow writing anywhere else in .shotgun directory
|
|
61
68
|
full_path = (base_path / filename).resolve()
|
|
62
69
|
else:
|
|
63
|
-
# For other agents,
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
# For other agents, check if they have access to the requested file
|
|
71
|
+
allowed_paths_raw = AGENT_DIRECTORIES[agent_mode]
|
|
72
|
+
|
|
73
|
+
# Convert single Path/string to list of Paths for uniform handling
|
|
74
|
+
if isinstance(allowed_paths_raw, str):
|
|
75
|
+
# Special case: "*" means export agent
|
|
76
|
+
allowed_paths = (
|
|
77
|
+
[Path(allowed_paths_raw)] if allowed_paths_raw != "*" else []
|
|
78
|
+
)
|
|
79
|
+
elif isinstance(allowed_paths_raw, Path):
|
|
80
|
+
allowed_paths = [allowed_paths_raw]
|
|
81
|
+
else:
|
|
82
|
+
# Already a list
|
|
83
|
+
allowed_paths = allowed_paths_raw
|
|
84
|
+
|
|
85
|
+
# Check if filename matches any allowed path
|
|
86
|
+
is_allowed = False
|
|
87
|
+
for allowed_path in allowed_paths:
|
|
88
|
+
allowed_str = str(allowed_path)
|
|
89
|
+
|
|
90
|
+
# Check if it's a directory (no .md extension or suffix)
|
|
91
|
+
# Directories: Path("contracts") has no suffix, files: Path("spec.md") has .md suffix
|
|
92
|
+
if not allowed_path.suffix or (
|
|
93
|
+
allowed_path.suffix and not allowed_str.endswith(".md")
|
|
94
|
+
):
|
|
95
|
+
# Directory - allow any file within this directory
|
|
96
|
+
# Check both "contracts/file.py" and "contracts" prefix
|
|
97
|
+
if (
|
|
98
|
+
filename.startswith(allowed_str + "/")
|
|
99
|
+
or filename == allowed_str
|
|
100
|
+
):
|
|
101
|
+
is_allowed = True
|
|
102
|
+
break
|
|
103
|
+
else:
|
|
104
|
+
# Exact file match
|
|
105
|
+
if filename == allowed_str:
|
|
106
|
+
is_allowed = True
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
if not is_allowed:
|
|
110
|
+
allowed_display = ", ".join(f"'{p}'" for p in allowed_paths)
|
|
66
111
|
raise ValueError(
|
|
67
|
-
f"{agent_mode.value.capitalize()} agent can only write to
|
|
112
|
+
f"{agent_mode.value.capitalize()} agent can only write to {allowed_display}. "
|
|
68
113
|
f"Attempted to write to '{filename}'"
|
|
69
114
|
)
|
|
115
|
+
|
|
70
116
|
full_path = (base_path / filename).resolve()
|
|
71
117
|
else:
|
|
72
118
|
# No agent mode specified, fall back to old validation
|
|
@@ -125,6 +125,34 @@ def main(
|
|
|
125
125
|
help="Continue previous TUI conversation",
|
|
126
126
|
),
|
|
127
127
|
] = False,
|
|
128
|
+
web: Annotated[
|
|
129
|
+
bool,
|
|
130
|
+
typer.Option(
|
|
131
|
+
"--web",
|
|
132
|
+
help="Serve TUI as web application",
|
|
133
|
+
),
|
|
134
|
+
] = False,
|
|
135
|
+
port: Annotated[
|
|
136
|
+
int,
|
|
137
|
+
typer.Option(
|
|
138
|
+
"--port",
|
|
139
|
+
help="Port for web server (only used with --web)",
|
|
140
|
+
),
|
|
141
|
+
] = 8000,
|
|
142
|
+
host: Annotated[
|
|
143
|
+
str,
|
|
144
|
+
typer.Option(
|
|
145
|
+
"--host",
|
|
146
|
+
help="Host address for web server (only used with --web)",
|
|
147
|
+
),
|
|
148
|
+
] = "localhost",
|
|
149
|
+
public_url: Annotated[
|
|
150
|
+
str | None,
|
|
151
|
+
typer.Option(
|
|
152
|
+
"--public-url",
|
|
153
|
+
help="Public URL if behind proxy (only used with --web)",
|
|
154
|
+
),
|
|
155
|
+
] = None,
|
|
128
156
|
) -> None:
|
|
129
157
|
"""Shotgun - AI-powered CLI tool."""
|
|
130
158
|
logger.debug("Starting shotgun CLI application")
|
|
@@ -134,16 +162,32 @@ def main(
|
|
|
134
162
|
perform_auto_update_async(no_update_check=no_update_check)
|
|
135
163
|
|
|
136
164
|
if ctx.invoked_subcommand is None and not ctx.resilient_parsing:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
if web:
|
|
166
|
+
logger.debug("Launching shotgun TUI as web application")
|
|
167
|
+
try:
|
|
168
|
+
tui_app.serve(
|
|
169
|
+
host=host,
|
|
170
|
+
port=port,
|
|
171
|
+
public_url=public_url,
|
|
172
|
+
no_update_check=no_update_check,
|
|
173
|
+
continue_session=continue_session,
|
|
174
|
+
)
|
|
175
|
+
finally:
|
|
176
|
+
# Ensure PostHog is shut down cleanly even if server exits unexpectedly
|
|
177
|
+
from shotgun.posthog_telemetry import shutdown
|
|
178
|
+
|
|
179
|
+
shutdown()
|
|
180
|
+
else:
|
|
181
|
+
logger.debug("Launching shotgun TUI application")
|
|
182
|
+
try:
|
|
183
|
+
tui_app.run(
|
|
184
|
+
no_update_check=no_update_check, continue_session=continue_session
|
|
185
|
+
)
|
|
186
|
+
finally:
|
|
187
|
+
# Ensure PostHog is shut down cleanly even if TUI exits unexpectedly
|
|
188
|
+
from shotgun.posthog_telemetry import shutdown
|
|
189
|
+
|
|
190
|
+
shutdown()
|
|
147
191
|
raise typer.Exit()
|
|
148
192
|
|
|
149
193
|
# For CLI commands, register PostHog shutdown handler
|