tunacode-cli 0.0.22__tar.gz → 0.0.24__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 tunacode-cli might be problematic. Click here for more details.

Files changed (100) hide show
  1. tunacode_cli-0.0.24/CLAUDE.md +120 -0
  2. tunacode_cli-0.0.24/MANIFEST.in +19 -0
  3. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/PKG-INFO +20 -1
  4. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/README.md +18 -0
  5. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/pyproject.toml +2 -1
  6. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/setup.py +1 -1
  7. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/cli/commands.py +1 -1
  8. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/cli/main.py +5 -0
  9. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/cli/repl.py +23 -8
  10. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/cli/textual_app.py +19 -25
  11. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/cli/textual_bridge.py +2 -3
  12. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/constants.py +1 -1
  13. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/context.py +0 -2
  14. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/agents/main.py +21 -18
  15. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/llm/planner.py +0 -1
  16. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/setup/config_setup.py +6 -2
  17. tunacode_cli-0.0.24/src/tunacode/prompts/system.md +92 -0
  18. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/tools/__init__.py +1 -1
  19. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/tools/grep.py +2 -2
  20. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/types.py +4 -0
  21. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/completers.py +5 -4
  22. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/input.py +1 -2
  23. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/lexers.py +0 -1
  24. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/output.py +2 -2
  25. tunacode_cli-0.0.24/src/tunacode/utils/__init__.py +0 -0
  26. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/PKG-INFO +20 -1
  27. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/SOURCES.txt +6 -0
  28. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/requires.txt +1 -0
  29. tunacode_cli-0.0.24/tests/test_agent_initialization.py +147 -0
  30. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_architect_integration.py +22 -46
  31. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_architect_simple.py +5 -37
  32. tunacode_cli-0.0.24/tests/test_config_setup_async.py +85 -0
  33. tunacode_cli-0.0.24/tests/test_fast_glob_search.py +111 -0
  34. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_orchestrator_planning_visibility.py +23 -15
  35. tunacode_cli-0.0.24/tests/test_react_thoughts.py +92 -0
  36. tunacode_cli-0.0.22/tests/test_fast_glob_search.py +0 -191
  37. tunacode_cli-0.0.22/tests/test_react_thoughts.py +0 -149
  38. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/LICENSE +0 -0
  39. /tunacode_cli-0.0.22/src/tunacode/__init__.py → /tunacode_cli-0.0.24/TUNACODE.md +0 -0
  40. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/setup.cfg +0 -0
  41. {tunacode_cli-0.0.22/src/tunacode/core → tunacode_cli-0.0.24/src/tunacode}/__init__.py +0 -0
  42. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/cli/__init__.py +0 -0
  43. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/configuration/__init__.py +0 -0
  44. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/configuration/defaults.py +0 -0
  45. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/configuration/models.py +0 -0
  46. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/configuration/settings.py +0 -0
  47. {tunacode_cli-0.0.22/src/tunacode/core/background → tunacode_cli-0.0.24/src/tunacode/core}/__init__.py +0 -0
  48. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/agents/__init__.py +0 -0
  49. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/agents/orchestrator.py +0 -0
  50. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/agents/planner_schema.py +0 -0
  51. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/agents/readonly.py +0 -0
  52. {tunacode_cli-0.0.22/src/tunacode/core/llm → tunacode_cli-0.0.24/src/tunacode/core/background}/__init__.py +0 -0
  53. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/background/manager.py +0 -0
  54. {tunacode_cli-0.0.22/src/tunacode/utils → tunacode_cli-0.0.24/src/tunacode/core/llm}/__init__.py +0 -0
  55. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/setup/__init__.py +0 -0
  56. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/setup/agent_setup.py +0 -0
  57. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/setup/base.py +0 -0
  58. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/setup/coordinator.py +0 -0
  59. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/setup/environment_setup.py +0 -0
  60. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  61. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/state.py +0 -0
  62. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/core/tool_handler.py +0 -0
  63. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/exceptions.py +0 -0
  64. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/prompts/system.txt +0 -0
  65. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/py.typed +0 -0
  66. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/services/__init__.py +0 -0
  67. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/services/mcp.py +0 -0
  68. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/setup.py +0 -0
  69. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/tools/base.py +0 -0
  70. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/tools/bash.py +0 -0
  71. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/tools/read_file.py +0 -0
  72. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/tools/run_command.py +0 -0
  73. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/tools/update_file.py +0 -0
  74. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/tools/write_file.py +0 -0
  75. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/__init__.py +0 -0
  76. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/console.py +0 -0
  77. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/constants.py +0 -0
  78. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/decorators.py +0 -0
  79. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/keybindings.py +0 -0
  80. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/panels.py +0 -0
  81. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/prompt_manager.py +0 -0
  82. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/tool_ui.py +0 -0
  83. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/ui/validators.py +0 -0
  84. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/utils/bm25.py +0 -0
  85. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/utils/diff_utils.py +0 -0
  86. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/utils/file_utils.py +0 -0
  87. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/utils/import_cache.py +0 -0
  88. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/utils/ripgrep.py +0 -0
  89. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/utils/system.py +0 -0
  90. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/utils/text_utils.py +0 -0
  91. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode/utils/user_configuration.py +0 -0
  92. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  93. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  94. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/src/tunacode_cli.egg-info/top_level.txt +0 -0
  95. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_background_manager.py +0 -0
  96. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_file_reference_expansion.py +0 -0
  97. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_json_tool_parsing.py +0 -0
  98. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_orchestrator_file_references.py +0 -0
  99. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_orchestrator_import.py +0 -0
  100. {tunacode_cli-0.0.22 → tunacode_cli-0.0.24}/tests/test_update_command.py +0 -0
@@ -0,0 +1,120 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ### Development Commands
8
+ ```bash
9
+ # Install development environment
10
+ pip install -e ".[dev]"
11
+
12
+ # Run linting (black, isort, flake8)
13
+ make lint
14
+
15
+ # Run tests
16
+ make test
17
+ pytest tests/ # Run all tests
18
+ pytest tests/test_import.py # Run single test file
19
+ pytest -k "test_name" # Run specific test
20
+
21
+ # Run tests with coverage
22
+ make coverage
23
+
24
+ # Build distribution packages
25
+ make build
26
+
27
+ # Clean build artifacts
28
+ make clean
29
+ ```
30
+
31
+ ### Version Management
32
+ When updating versions, modify both:
33
+ - `pyproject.toml`: version field
34
+ - `src/tunacode/constants.py`: VERSION constant
35
+
36
+ ## Architecture
37
+
38
+ TunaCode is a CLI tool that provides an AI-powered coding assistant using pydantic-ai. Key architectural decisions:
39
+
40
+ ### Agent System
41
+ - Uses `pydantic-ai` for LLM agent implementation
42
+ - Central agent in `src/tunacode/core/agents/main.py` with retryable tools
43
+ - Supports multiple LLM providers (Anthropic, OpenAI, Google, OpenRouter) through unified interface
44
+ - Model format: `provider:model-name` (e.g., `openai:gpt-4`, `anthropic:claude-3-opus`)
45
+
46
+ ### Tool System
47
+ Four internal tools with confirmation UI:
48
+ 1. `read_file` - Read file contents
49
+ 2. `write_file` - Create new files (fails if exists)
50
+ 3. `update_file` - Update existing files with target/patch pattern
51
+ 4. `run_command` - Execute shell commands
52
+
53
+ Tools extend `BaseTool` or `FileBasedTool` base classes. External tools supported via MCP (Model Context Protocol).
54
+
55
+ ### State Management
56
+ - `StateManager` (core/state.py) maintains all session state
57
+ - Includes user config, agent instances, message history, costs, permissions
58
+ - Single source of truth passed throughout the application
59
+
60
+ ### Command System
61
+ - Command registry pattern in `cli/commands.py`
62
+ - Commands implement `BaseCommand` with `matches()` and `execute()` methods
63
+ - Registered via `@CommandRegistry.register` decorator
64
+ - Process flow: REPL → CommandRegistry → Command → Action
65
+
66
+ ### Setup Coordinator
67
+ Modular setup with validation steps:
68
+ 1. Environment detection
69
+ 2. Model validation
70
+ 3. Configuration setup
71
+ 4. Git safety checks
72
+ Each step implements `BaseSetupStep` interface.
73
+
74
+ ### UI Components
75
+ - REPL uses `prompt_toolkit` for multiline input with syntax highlighting
76
+ - Output formatting via `rich` library
77
+ - Tool confirmations show diffs for file operations
78
+ - Spinner during agent processing
79
+
80
+ ## Configuration
81
+
82
+ ### User Configuration
83
+ Location: `~/.config/tunacode.json`
84
+ ```json
85
+ {
86
+ "default_model": "provider:model-name",
87
+ "env": {
88
+ "ANTHROPIC_API_KEY": "...",
89
+ "OPENAI_API_KEY": "..."
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### Project Guide
95
+ Location: `TUNACODE.md` in project root
96
+ - Project-specific context for the AI assistant
97
+ - Loaded automatically when present
98
+ - Can include codebase conventions, architecture notes
99
+
100
+ ## Key Design Patterns
101
+
102
+ ### Error Handling
103
+ - Custom exceptions in `exceptions.py`
104
+ - `ModelRetry` from pydantic-ai for retryable errors
105
+ - Graceful degradation for missing features
106
+
107
+ ### Permissions
108
+ - File operation permissions tracked per session
109
+ - "Yolo mode" to skip confirmations: `/yolo`
110
+ - Permissions stored in StateManager
111
+
112
+ ### Async Architecture
113
+ - All agent operations are async
114
+ - Tool executions use async/await
115
+ - REPL handles async with prompt_toolkit integration
116
+
117
+ ### Safety Notes
118
+ - No automatic git commits (removed for safety)
119
+ - File operations require explicit confirmation
120
+ - Encourages git branches for experiments: `/branch <name>`
@@ -0,0 +1,19 @@
1
+ # Include all prompt files
2
+ include src/tunacode/prompts/*.txt
3
+ include src/tunacode/prompts/*.md
4
+
5
+ # Include other important files
6
+ include README.md
7
+ include LICENSE
8
+ include pyproject.toml
9
+ include CLAUDE.md
10
+ include TUNACODE.md
11
+
12
+ # Include test files (optional, but good for development)
13
+ recursive-include tests *.py
14
+
15
+ # Exclude compiled files
16
+ global-exclude *.pyc
17
+ global-exclude __pycache__
18
+ global-exclude *.so
19
+ global-exclude *.egg-info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.22
3
+ Version: 0.0.24
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -30,6 +30,7 @@ Requires-Dist: black; extra == "dev"
30
30
  Requires-Dist: flake8; extra == "dev"
31
31
  Requires-Dist: isort; extra == "dev"
32
32
  Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-asyncio; extra == "dev"
33
34
  Requires-Dist: pytest-cov; extra == "dev"
34
35
  Requires-Dist: textual-dev; extra == "dev"
35
36
  Dynamic: license-file
@@ -131,6 +132,24 @@ Dynamic: license-file
131
132
  pip install tunacode-cli
132
133
  ```
133
134
 
135
+ #### Development Installation
136
+
137
+ ```bash
138
+ # Clone the repository
139
+ git clone https://github.com/larock22/tunacode.git
140
+ cd tunacode
141
+
142
+ # Run the setup script
143
+ ./scripts/setup_dev_env.sh
144
+
145
+ # Or manually:
146
+ python3 -m venv venv
147
+ source venv/bin/activate
148
+ pip install -e ".[dev]"
149
+ ```
150
+
151
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed development setup.
152
+
134
153
  #### One-line Install (Linux/macOS)
135
154
 
136
155
  ```bash
@@ -95,6 +95,24 @@
95
95
  pip install tunacode-cli
96
96
  ```
97
97
 
98
+ #### Development Installation
99
+
100
+ ```bash
101
+ # Clone the repository
102
+ git clone https://github.com/larock22/tunacode.git
103
+ cd tunacode
104
+
105
+ # Run the setup script
106
+ ./scripts/setup_dev_env.sh
107
+
108
+ # Or manually:
109
+ python3 -m venv venv
110
+ source venv/bin/activate
111
+ pip install -e ".[dev]"
112
+ ```
113
+
114
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed development setup.
115
+
98
116
  #### One-line Install (Linux/macOS)
99
117
 
100
118
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tunacode-cli"
7
- version = "0.0.22"
7
+ version = "0.0.24"
8
8
  description = "Your agentic CLI developer."
9
9
  keywords = ["cli", "agent", "development", "automation"]
10
10
  readme = "README.md"
@@ -42,6 +42,7 @@ dev = [
42
42
  "flake8",
43
43
  "isort",
44
44
  "pytest",
45
+ "pytest-asyncio",
45
46
  "pytest-cov",
46
47
  "textual-dev",
47
48
  ]
@@ -5,6 +5,6 @@ setup(
5
5
  packages=find_namespace_packages(where="src"),
6
6
  include_package_data=True,
7
7
  package_data={
8
- "tunacode": ["prompts/*.txt"],
8
+ "tunacode": ["prompts/*.txt", "prompts/*.md"],
9
9
  },
10
10
  )
@@ -192,7 +192,7 @@ class ArchitectCommand(SimpleCommand):
192
192
  await ui.error("Usage: /architect [on|off]")
193
193
  return
194
194
  else:
195
- state.architect_mode = not getattr(state, 'architect_mode', False)
195
+ state.architect_mode = not getattr(state, "architect_mode", False)
196
196
  status = "ON" if state.architect_mode else "OFF"
197
197
  if state.architect_mode:
198
198
  await ui.success(f"Architect mode {status} - Requests will be planned before execution")
@@ -14,6 +14,7 @@ from tunacode.core.state import StateManager
14
14
  from tunacode.setup import setup
15
15
  from tunacode.ui import console as ui
16
16
  from tunacode.utils.system import check_for_updates
17
+ from tunacode.exceptions import UserAbortError
17
18
 
18
19
  app_settings = ApplicationSettings()
19
20
  app = typer.Typer(help="🐟 TunaCode - Your AI-powered development assistant")
@@ -49,8 +50,12 @@ def main(
49
50
  try:
50
51
  await setup(run_setup, state_manager, cli_config)
51
52
  await repl(state_manager)
53
+ except (KeyboardInterrupt, UserAbortError):
54
+ update_task.cancel()
55
+ return
52
56
  except Exception as e:
53
57
  from tunacode.exceptions import ConfigurationError
58
+
54
59
  if isinstance(e, ConfigurationError):
55
60
  # ConfigurationError already printed helpful message, just exit cleanly
56
61
  update_task.cancel() # Cancel the update check
@@ -172,7 +172,7 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
172
172
  return _tool_handler(part, node, state_manager)
173
173
 
174
174
  # Check if architect mode is enabled
175
- if getattr(state_manager.session, 'architect_mode', False):
175
+ if getattr(state_manager.session, "architect_mode", False):
176
176
  # Expand @file references before sending to the orchestrator
177
177
  try:
178
178
  from tunacode.utils.text_utils import expand_file_refs
@@ -184,14 +184,18 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
184
184
  # Use orchestrator for planning and execution
185
185
  orchestrator = OrchestratorAgent(state_manager)
186
186
  results = await orchestrator.run(text, state_manager.session.current_model)
187
-
187
+
188
188
  if output:
189
189
  # Process results from all sub-agents
190
190
  for res in results:
191
191
  # Check if result exists and has output
192
- if hasattr(res, "result") and res.result is not None and hasattr(res.result, "output"):
192
+ if (
193
+ hasattr(res, "result")
194
+ and res.result is not None
195
+ and hasattr(res.result, "output")
196
+ ):
193
197
  await ui.agent(res.result.output)
194
-
198
+
195
199
  if not results:
196
200
  # Fallback: show that the request was processed
197
201
  await ui.muted("Request completed")
@@ -204,7 +208,7 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
204
208
  except ValueError as e:
205
209
  await ui.error(str(e))
206
210
  return
207
-
211
+
208
212
  # Use normal agent processing
209
213
  res = await agent.process_request(
210
214
  state_manager.session.current_model,
@@ -219,7 +223,11 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
219
223
  if isinstance(msg, dict) and "thought" in msg:
220
224
  await ui.muted(f"THOUGHT: {msg['thought']}")
221
225
  # Check if result exists and has output
222
- if hasattr(res, "result") and res.result is not None and hasattr(res.result, "output"):
226
+ if (
227
+ hasattr(res, "result")
228
+ and res.result is not None
229
+ and hasattr(res.result, "output")
230
+ ):
223
231
  await ui.agent(res.result.output)
224
232
  else:
225
233
  # Fallback: show that the request was processed
@@ -274,6 +282,7 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
274
282
 
275
283
  async def repl(state_manager: StateManager):
276
284
  action = None
285
+ ctrl_c_pressed = False
277
286
 
278
287
  # Professional startup information
279
288
  await ui.muted(f"• Model: {state_manager.session.current_model}")
@@ -286,12 +295,18 @@ async def repl(state_manager: StateManager):
286
295
  while True:
287
296
  try:
288
297
  line = await ui.multiline_input(state_manager, _command_registry)
289
- except (EOFError, KeyboardInterrupt):
290
- break
298
+ except UserAbortError:
299
+ if ctrl_c_pressed:
300
+ break
301
+ ctrl_c_pressed = True
302
+ await ui.warning("Hit Ctrl+C again to exit")
303
+ continue
291
304
 
292
305
  if not line:
293
306
  continue
294
307
 
308
+ ctrl_c_pressed = False
309
+
295
310
  if line.lower() in ["exit", "quit"]:
296
311
  break
297
312
 
@@ -11,18 +11,13 @@ Provides a rich, interactive terminal user interface with:
11
11
  import asyncio
12
12
  from typing import Optional
13
13
 
14
- from rich.markdown import Markdown
15
- from textual import on, work
14
+ from textual import on
16
15
  from textual.app import App, ComposeResult
17
16
  from textual.binding import Binding
18
17
  from textual.containers import Container, Horizontal, Vertical, VerticalScroll
19
18
  from textual.message import Message
20
- from textual.reactive import reactive
21
- from textual.widgets import Button, Footer, Header, Input, Label
22
- from textual.widgets import Markdown as MarkdownWidget
23
- from textual.widgets import Static, TabbedContent, TabPane, TextArea
19
+ from textual.widgets import Button, Footer, Header, Static, TextArea
24
20
 
25
- from tunacode.configuration.settings import ApplicationSettings
26
21
  from tunacode.core.state import StateManager
27
22
  from tunacode.setup import setup
28
23
  from tunacode.utils.system import check_for_updates
@@ -130,78 +125,78 @@ class TunaCodeApp(App):
130
125
  border: thick $primary;
131
126
  padding: 1;
132
127
  }
133
-
128
+
134
129
  .sidebar-title {
135
130
  text-align: center;
136
131
  color: $primary;
137
132
  margin-bottom: 1;
138
133
  }
139
-
134
+
140
135
  .section-title {
141
136
  color: $accent;
142
137
  margin: 1 0;
143
138
  }
144
-
139
+
145
140
  .command-item {
146
141
  color: $text-muted;
147
142
  margin-left: 1;
148
143
  }
149
-
144
+
150
145
  .status-ready {
151
146
  color: $success;
152
147
  }
153
-
148
+
154
149
  .status-busy {
155
150
  color: $warning;
156
151
  }
157
-
152
+
158
153
  .status-error {
159
154
  color: $error;
160
155
  }
161
-
156
+
162
157
  ChatHistory {
163
158
  border: thick $primary;
164
159
  padding: 1;
165
160
  height: 1fr;
166
161
  }
167
-
162
+
168
163
  .user-message {
169
164
  background: $surface;
170
165
  border-left: thick $primary;
171
166
  padding: 1;
172
167
  margin: 1 0;
173
168
  }
174
-
169
+
175
170
  .agent-message {
176
171
  background: $surface;
177
172
  border-left: thick $success;
178
173
  padding: 1;
179
174
  margin: 1 0;
180
175
  }
181
-
176
+
182
177
  .system-message {
183
178
  background: $surface;
184
179
  border-left: thick $warning;
185
180
  padding: 1;
186
181
  margin: 1 0;
187
182
  }
188
-
183
+
189
184
  .tool-message {
190
185
  background: $surface;
191
186
  border-left: thick $accent;
192
187
  padding: 1;
193
188
  margin: 1 0;
194
189
  }
195
-
190
+
196
191
  InputArea {
197
192
  height: 5;
198
193
  padding: 1;
199
194
  }
200
-
195
+
201
196
  #message-input {
202
197
  height: 3;
203
198
  }
204
-
199
+
205
200
  #send-button {
206
201
  width: 10;
207
202
  margin-left: 1;
@@ -250,7 +245,8 @@ class TunaCodeApp(App):
250
245
  )
251
246
  self.chat_history.add_message(
252
247
  "System",
253
- "⚠️ IMPORTANT: Always use git branches before making major changes\nType '/help' for available commands",
248
+ "⚠️ IMPORTANT: Always use git branches before making major changes\n"
249
+ "Type '/help' for available commands",
254
250
  "system",
255
251
  )
256
252
 
@@ -280,7 +276,7 @@ class TunaCodeApp(App):
280
276
  """Handle slash commands."""
281
277
  if command == "/help":
282
278
  help_text = """Available Commands:
283
-
279
+
284
280
  /help - Show this help message
285
281
  /clear - Clear chat history
286
282
  /model - Show current model info
@@ -371,8 +367,6 @@ def main():
371
367
 
372
368
  # Initialize state manager
373
369
  state_manager = StateManager()
374
- app_settings = ApplicationSettings()
375
-
376
370
  # Show banner
377
371
  print("🐟 TunaCode - Modern AI Development Assistant")
378
372
  print("=" * 50)
@@ -5,9 +5,8 @@ This module adapts the existing REPL and agent processing logic to work
5
5
  with the new Textual-based interface while maintaining compatibility.
6
6
  """
7
7
 
8
- import asyncio
9
8
  from asyncio.exceptions import CancelledError
10
- from typing import Callable, Optional
9
+ from typing import Callable
11
10
 
12
11
  from pydantic_ai.exceptions import UnexpectedModelBehavior
13
12
 
@@ -129,7 +128,7 @@ class TextualAgentBridge:
129
128
  # Check if confirmation is needed
130
129
  if tool_handler.should_confirm(part.tool_name):
131
130
  # Create confirmation request
132
- request = tool_handler.create_confirmation_request(part.tool_name, args)
131
+ tool_handler.create_confirmation_request(part.tool_name, args)
133
132
 
134
133
  # For now, show a simple confirmation in the UI
135
134
  # In a full implementation, this would show a proper modal dialog
@@ -7,7 +7,7 @@ Centralizes all magic strings, UI text, error messages, and application constant
7
7
 
8
8
  # Application info
9
9
  APP_NAME = "TunaCode"
10
- APP_VERSION = "0.0.22"
10
+ APP_VERSION = "0.0.24"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -1,5 +1,3 @@
1
- import json
2
- import os
3
1
  import subprocess
4
2
  from pathlib import Path
5
3
  from typing import Dict, List
@@ -7,11 +7,22 @@ Handles agent creation, configuration, and request processing.
7
7
  import json
8
8
  import re
9
9
  from datetime import datetime, timezone
10
+ from pathlib import Path
10
11
  from typing import Optional
11
12
 
12
- # Lazy import for Agent and Tool
13
+ from tunacode.core.state import StateManager
14
+ from tunacode.services.mcp import get_mcp_servers
15
+ from tunacode.tools.bash import bash
16
+ from tunacode.tools.grep import grep
17
+ from tunacode.tools.read_file import read_file
18
+ from tunacode.tools.run_command import run_command
19
+ from tunacode.tools.update_file import update_file
20
+ from tunacode.tools.write_file import write_file
21
+ from tunacode.types import (AgentRun, ErrorMessage, ModelName, PydanticAgent, ToolCallback,
22
+ ToolCallId, ToolName)
13
23
 
14
24
 
25
+ # Lazy import for Agent and Tool
15
26
  def get_agent_tool():
16
27
  import importlib
17
28
 
@@ -26,18 +37,6 @@ def get_model_messages():
26
37
  return messages.ModelRequest, messages.ToolReturnPart
27
38
 
28
39
 
29
- from tunacode.core.state import StateManager
30
- from tunacode.services.mcp import get_mcp_servers
31
- from tunacode.tools.bash import bash
32
- from tunacode.tools.grep import grep
33
- from tunacode.tools.read_file import read_file
34
- from tunacode.tools.run_command import run_command
35
- from tunacode.tools.update_file import update_file
36
- from tunacode.tools.write_file import write_file
37
- from tunacode.types import (AgentRun, ErrorMessage, ModelName, PydanticAgent, ToolCallback,
38
- ToolCallId, ToolName)
39
-
40
-
41
40
  async def _process_node(node, tool_callback: Optional[ToolCallback], state_manager: StateManager):
42
41
  if hasattr(node, "request"):
43
42
  state_manager.session.messages.append(node.request)
@@ -125,21 +124,25 @@ async def _process_node(node, tool_callback: Optional[ToolCallback], state_manag
125
124
 
126
125
  def get_or_create_agent(model: ModelName, state_manager: StateManager) -> PydanticAgent:
127
126
  if model not in state_manager.session.agents:
128
- max_retries = state_manager.session.user_config["settings"]["max_retries"]
127
+ max_retries = state_manager.session.user_config.get("settings", {}).get("max_retries", 3)
129
128
 
130
129
  # Lazy import Agent and Tool
131
130
  Agent, Tool = get_agent_tool()
132
131
 
133
132
  # Load system prompt
134
- import os
135
- from pathlib import Path
136
-
137
133
  prompt_path = Path(__file__).parent.parent.parent / "prompts" / "system.md"
138
134
  try:
139
135
  with open(prompt_path, "r", encoding="utf-8") as f:
140
136
  system_prompt = f.read().strip()
141
137
  except FileNotFoundError:
142
- system_prompt = None
138
+ # Fallback to system.txt if system.md not found
139
+ prompt_path = Path(__file__).parent.parent.parent / "prompts" / "system.txt"
140
+ try:
141
+ with open(prompt_path, "r", encoding="utf-8") as f:
142
+ system_prompt = f.read().strip()
143
+ except FileNotFoundError:
144
+ # Use a default system prompt if neither file exists
145
+ system_prompt = "You are a helpful AI assistant for software development tasks."
143
146
 
144
147
  state_manager.session.agents[model] = Agent(
145
148
  model=model,
@@ -1,4 +1,3 @@
1
- import json
2
1
  from typing import List
3
2
 
4
3
  from ...types import ModelName
@@ -99,11 +99,15 @@ class ConfigSetup(BaseSetup):
99
99
  " [green]tunacode --model 'anthropic:claude-3-opus' --key 'your-key'[/green]"
100
100
  )
101
101
  console.print(
102
- " [green]tunacode --model 'openrouter:anthropic/claude-3.5-sonnet' --key 'your-key' --baseurl 'https://openrouter.ai/api/v1'[/green]"
102
+ " [green]tunacode --model 'openrouter:anthropic/claude-3.5-sonnet' "
103
+ "--key 'your-key' --baseurl 'https://openrouter.ai/api/v1'[/green]"
103
104
  )
104
105
  console.print("\n[yellow]Run 'tunacode --help' for more options[/yellow]\n")
105
106
  from tunacode.exceptions import ConfigurationError
106
- raise ConfigurationError("No configuration found. Please use CLI flags to configure.")
107
+
108
+ raise ConfigurationError(
109
+ "No configuration found. Please use CLI flags to configure."
110
+ )
107
111
 
108
112
  if not self.state_manager.session.user_config.get("default_model"):
109
113
  raise ConfigurationError(