tunacode-cli 0.0.15__tar.gz → 0.0.17__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.
- {tunacode_cli-0.0.15/src/tunacode_cli.egg-info → tunacode_cli-0.0.17}/PKG-INFO +14 -1
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/README.md +14 -1
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/pyproject.toml +1 -1
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/constants.py +1 -1
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/console.py +3 -2
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/output.py +5 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17/src/tunacode_cli.egg-info}/PKG-INFO +14 -1
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode_cli.egg-info/SOURCES.txt +2 -1
- tunacode_cli-0.0.17/tests/test_escape_mechanism.py +184 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/LICENSE +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/setup.cfg +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/setup.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/cli/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/cli/commands.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/cli/main.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/cli/repl.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/cli/textual_app.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/cli/textual_bridge.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/configuration/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/configuration/defaults.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/configuration/models.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/configuration/settings.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/context.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/agents/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/agents/main.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/setup/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/setup/agent_setup.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/setup/base.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/setup/config_setup.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/setup/coordinator.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/setup/environment_setup.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/setup/git_safety_setup.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/state.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/core/tool_handler.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/exceptions.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/prompts/system.txt +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/py.typed +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/services/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/services/mcp.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/setup.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/tools/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/tools/base.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/tools/bash.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/tools/grep.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/tools/read_file.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/tools/run_command.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/tools/update_file.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/tools/write_file.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/types.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/completers.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/constants.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/decorators.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/input.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/keybindings.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/lexers.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/panels.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/prompt_manager.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/tool_ui.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/ui/validators.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/utils/__init__.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/utils/bm25.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/utils/diff_utils.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/utils/file_utils.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/utils/ripgrep.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/utils/system.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/utils/text_utils.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode/utils/user_configuration.py +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode_cli.egg-info/requires.txt +0 -0
- {tunacode_cli-0.0.15 → tunacode_cli-0.0.17}/src/tunacode_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.17
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -475,3 +475,16 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
475
475
|
## Acknowledgments
|
|
476
476
|
|
|
477
477
|
TunaCode is a fork of [sidekick-cli](https://github.com/geekforbrains/sidekick-cli). Special thanks to the sidekick-cli team for creating the foundation that made TunaCode possible.
|
|
478
|
+
|
|
479
|
+
### Key Differences from sidekick-cli
|
|
480
|
+
|
|
481
|
+
While TunaCode builds on the foundation of sidekick-cli, we've made several architectural changes for our workflow:
|
|
482
|
+
|
|
483
|
+
- **JSON Tool Parsing Fallback**: Added fallback parsing for when API providers fail with structured tool calling
|
|
484
|
+
- **Parallel Search Tools**: New `bash` and `grep` tools with parallel execution for codebase navigation
|
|
485
|
+
- **ReAct Reasoning**: Implemented ReAct (Reasoning + Acting) patterns with configurable iteration limits
|
|
486
|
+
- **Dynamic Configuration**: Added `/refresh` command and modified configuration management
|
|
487
|
+
- **Safety Changes**: Removed automatic git commits and `/undo` command - requires explicit git usage
|
|
488
|
+
- **Error Recovery**: Multiple fallback mechanisms and orphaned tool call recovery
|
|
489
|
+
- **Tool System Rewrite**: Complete overhaul of internal tools with atomic operations and different confirmation UIs
|
|
490
|
+
- **Debug Commands**: Added `/parsetools`, `/thoughts`, `/iterations` for debugging
|
|
@@ -438,4 +438,17 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
438
438
|
|
|
439
439
|
## Acknowledgments
|
|
440
440
|
|
|
441
|
-
TunaCode is a fork of [sidekick-cli](https://github.com/geekforbrains/sidekick-cli). Special thanks to the sidekick-cli team for creating the foundation that made TunaCode possible.
|
|
441
|
+
TunaCode is a fork of [sidekick-cli](https://github.com/geekforbrains/sidekick-cli). Special thanks to the sidekick-cli team for creating the foundation that made TunaCode possible.
|
|
442
|
+
|
|
443
|
+
### Key Differences from sidekick-cli
|
|
444
|
+
|
|
445
|
+
While TunaCode builds on the foundation of sidekick-cli, we've made several architectural changes for our workflow:
|
|
446
|
+
|
|
447
|
+
- **JSON Tool Parsing Fallback**: Added fallback parsing for when API providers fail with structured tool calling
|
|
448
|
+
- **Parallel Search Tools**: New `bash` and `grep` tools with parallel execution for codebase navigation
|
|
449
|
+
- **ReAct Reasoning**: Implemented ReAct (Reasoning + Acting) patterns with configurable iteration limits
|
|
450
|
+
- **Dynamic Configuration**: Added `/refresh` command and modified configuration management
|
|
451
|
+
- **Safety Changes**: Removed automatic git commits and `/undo` command - requires explicit git usage
|
|
452
|
+
- **Error Recovery**: Multiple fallback mechanisms and orphaned tool call recovery
|
|
453
|
+
- **Tool System Rewrite**: Complete overhaul of internal tools with atomic operations and different confirmation UIs
|
|
454
|
+
- **Debug Commands**: Added `/parsetools`, `/thoughts`, `/iterations` for debugging
|
|
@@ -9,8 +9,8 @@ from rich.markdown import Markdown
|
|
|
9
9
|
# Import and re-export all functions from specialized modules
|
|
10
10
|
from .input import formatted_text, input, multiline_input
|
|
11
11
|
from .keybindings import create_key_bindings
|
|
12
|
-
from .output import (banner, clear, info, line, muted, print, spinner, success,
|
|
13
|
-
update_available, usage, version, warning)
|
|
12
|
+
from .output import (banner, clear, info, line, muted, print, show_update_message, spinner, success,
|
|
13
|
+
sync_print, update_available, usage, version, warning)
|
|
14
14
|
from .panels import (agent, dump_messages, error, help, models, panel, sync_panel,
|
|
15
15
|
sync_tool_confirm, tool_confirm)
|
|
16
16
|
from .prompt_manager import PromptConfig, PromptManager
|
|
@@ -46,6 +46,7 @@ __all__ = [
|
|
|
46
46
|
"line",
|
|
47
47
|
"muted",
|
|
48
48
|
"print",
|
|
49
|
+
"show_update_message",
|
|
49
50
|
"spinner",
|
|
50
51
|
"success",
|
|
51
52
|
"sync_print",
|
|
@@ -89,6 +89,11 @@ async def update_available(latest_version: str) -> None:
|
|
|
89
89
|
await muted(MSG_UPDATE_INSTRUCTION)
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
async def show_update_message(latest_version: str) -> None:
|
|
93
|
+
"""Display update available message (alias for update_available)."""
|
|
94
|
+
await update_available(latest_version)
|
|
95
|
+
|
|
96
|
+
|
|
92
97
|
async def spinner(show: bool = True, spinner_obj=None, state_manager: StateManager = None):
|
|
93
98
|
"""Manage a spinner display."""
|
|
94
99
|
icon = SPINNER_TYPE
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.17
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -475,3 +475,16 @@ MIT License - see [LICENSE](LICENSE) file for details.
|
|
|
475
475
|
## Acknowledgments
|
|
476
476
|
|
|
477
477
|
TunaCode is a fork of [sidekick-cli](https://github.com/geekforbrains/sidekick-cli). Special thanks to the sidekick-cli team for creating the foundation that made TunaCode possible.
|
|
478
|
+
|
|
479
|
+
### Key Differences from sidekick-cli
|
|
480
|
+
|
|
481
|
+
While TunaCode builds on the foundation of sidekick-cli, we've made several architectural changes for our workflow:
|
|
482
|
+
|
|
483
|
+
- **JSON Tool Parsing Fallback**: Added fallback parsing for when API providers fail with structured tool calling
|
|
484
|
+
- **Parallel Search Tools**: New `bash` and `grep` tools with parallel execution for codebase navigation
|
|
485
|
+
- **ReAct Reasoning**: Implemented ReAct (Reasoning + Acting) patterns with configurable iteration limits
|
|
486
|
+
- **Dynamic Configuration**: Added `/refresh` command and modified configuration management
|
|
487
|
+
- **Safety Changes**: Removed automatic git commits and `/undo` command - requires explicit git usage
|
|
488
|
+
- **Error Recovery**: Multiple fallback mechanisms and orphaned tool call recovery
|
|
489
|
+
- **Tool System Rewrite**: Complete overhaul of internal tools with atomic operations and different confirmation UIs
|
|
490
|
+
- **Debug Commands**: Added `/parsetools`, `/thoughts`, `/iterations` for debugging
|
|
@@ -68,4 +68,5 @@ src/tunacode_cli.egg-info/SOURCES.txt
|
|
|
68
68
|
src/tunacode_cli.egg-info/dependency_links.txt
|
|
69
69
|
src/tunacode_cli.egg-info/entry_points.txt
|
|
70
70
|
src/tunacode_cli.egg-info/requires.txt
|
|
71
|
-
src/tunacode_cli.egg-info/top_level.txt
|
|
71
|
+
src/tunacode_cli.egg-info/top_level.txt
|
|
72
|
+
tests/test_escape_mechanism.py
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from tunacode.types import EscapeState
|
|
7
|
+
from tunacode.exceptions import EscapeInterrupt
|
|
8
|
+
from tunacode.core.state import StateManager
|
|
9
|
+
from tunacode.core.tool_handler import ToolHandler
|
|
10
|
+
from tunacode.tools.write_file import WriteFileTool, write_file
|
|
11
|
+
from tunacode.tools.update_file import UpdateFileTool, update_file
|
|
12
|
+
try:
|
|
13
|
+
from tunacode.ui.keybindings import create_key_bindings
|
|
14
|
+
except ImportError:
|
|
15
|
+
create_key_bindings = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture(autouse=True)
|
|
19
|
+
def freeze_time(monkeypatch):
|
|
20
|
+
"""Freeze time.time() to a mutable value for predictable testing."""
|
|
21
|
+
t = [1000.0]
|
|
22
|
+
monkeypatch.setattr(time, 'time', lambda: t[0])
|
|
23
|
+
return t
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_escape_state_initial_window_inactive():
|
|
27
|
+
state = EscapeState()
|
|
28
|
+
assert not state.is_window_active()
|
|
29
|
+
assert state.last_escape_time is None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_escape_state_window_active_and_reset(freeze_time):
|
|
33
|
+
state = EscapeState()
|
|
34
|
+
# First press sets last_escape_time
|
|
35
|
+
freeze_time[0] = 1000.0
|
|
36
|
+
state.last_escape_time = freeze_time[0]
|
|
37
|
+
# Within 1 second window it should be active
|
|
38
|
+
freeze_time[0] = 1000.5
|
|
39
|
+
assert state.is_window_active()
|
|
40
|
+
# After window expires it should reset state
|
|
41
|
+
freeze_time[0] = 2000.0
|
|
42
|
+
assert not state.is_window_active()
|
|
43
|
+
assert state.last_escape_time is None
|
|
44
|
+
assert not state.escape_pending
|
|
45
|
+
assert not state.message_shown
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_escape_state_reset():
|
|
49
|
+
state = EscapeState(last_escape_time=1.0, escape_pending=True, message_shown=True)
|
|
50
|
+
state.reset_escape_state()
|
|
51
|
+
assert state.last_escape_time is None
|
|
52
|
+
assert not state.escape_pending
|
|
53
|
+
assert not state.message_shown
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_tool_handler_check_escape_noop():
|
|
57
|
+
manager = StateManager()
|
|
58
|
+
handler = ToolHandler(manager)
|
|
59
|
+
handler.check_escape()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_tool_handler_check_escape_triggers(freeze_time):
|
|
63
|
+
manager = StateManager()
|
|
64
|
+
manager.escape_state.last_escape_time = freeze_time[0]
|
|
65
|
+
manager.escape_state.escape_pending = True
|
|
66
|
+
handler = ToolHandler(manager)
|
|
67
|
+
with pytest.raises(EscapeInterrupt):
|
|
68
|
+
handler.check_escape()
|
|
69
|
+
assert manager.escape_state.last_escape_time is None
|
|
70
|
+
assert not manager.escape_state.escape_pending
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class _DummyBuffer:
|
|
74
|
+
def __init__(self):
|
|
75
|
+
self.text_inserted = ''
|
|
76
|
+
|
|
77
|
+
def validate_and_handle(self):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def insert_text(self, text):
|
|
81
|
+
self.text_inserted += text
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _DummyOutput:
|
|
85
|
+
def __init__(self):
|
|
86
|
+
self.messages = []
|
|
87
|
+
def write(self, txt):
|
|
88
|
+
self.messages.append(txt)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class _DummyApp:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _make_event(manager):
|
|
96
|
+
app = _DummyApp()
|
|
97
|
+
app.state_manager = manager
|
|
98
|
+
app.output = _DummyOutput()
|
|
99
|
+
return type('Evt', (), {'app': app, 'current_buffer': _DummyBuffer()})()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_double_escape_binding(freeze_time):
|
|
103
|
+
if create_key_bindings is None:
|
|
104
|
+
pytest.skip("prompt_toolkit not installed, skipping keybinding test")
|
|
105
|
+
manager = StateManager()
|
|
106
|
+
# Simulate a running task so ESC ESC hint is enabled
|
|
107
|
+
manager.session.current_task = type('DummyTask', (), {'done': lambda self: False})()
|
|
108
|
+
kb = create_key_bindings()
|
|
109
|
+
esc_binding = next(b for b in kb.bindings if b.keys == ('escape',))
|
|
110
|
+
event = _make_event(manager)
|
|
111
|
+
# first ESC press
|
|
112
|
+
freeze_time[0] = 1000.0
|
|
113
|
+
esc_binding.handler(event)
|
|
114
|
+
assert manager.escape_state.escape_pending
|
|
115
|
+
assert event.app.output.messages == ['Press ESC again to stop\n']
|
|
116
|
+
# second ESC press within window
|
|
117
|
+
freeze_time[0] = 1000.5
|
|
118
|
+
with pytest.raises(EscapeInterrupt):
|
|
119
|
+
esc_binding.handler(event)
|
|
120
|
+
|
|
121
|
+
def test_escape_enter_binding(freeze_time):
|
|
122
|
+
if create_key_bindings is None:
|
|
123
|
+
pytest.skip("prompt_toolkit not installed, skipping keybinding test")
|
|
124
|
+
manager = StateManager()
|
|
125
|
+
kb = create_key_bindings()
|
|
126
|
+
# Keys.ControlM is used for Enter key
|
|
127
|
+
from prompt_toolkit.keys import Keys
|
|
128
|
+
ee_binding = next(b for b in kb.bindings if b.keys == (Keys.Escape, Keys.ControlM))
|
|
129
|
+
event = _make_event(manager)
|
|
130
|
+
ee_binding.handler(event)
|
|
131
|
+
buf = event.current_buffer
|
|
132
|
+
assert buf.text_inserted == "\n"
|
|
133
|
+
assert not manager.escape_state.escape_pending
|
|
134
|
+
|
|
135
|
+
def test_ctrl_c_not_overridden():
|
|
136
|
+
if create_key_bindings is None:
|
|
137
|
+
pytest.skip("prompt_toolkit not installed, skipping Ctrl+C compatibility test")
|
|
138
|
+
kb = create_key_bindings()
|
|
139
|
+
# Ensure no custom binding overrides Ctrl+C
|
|
140
|
+
assert not any(binding.keys == ('c-c',) for binding in kb.bindings)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.mark.asyncio
|
|
144
|
+
async def test_write_file_tool_atomic(tmp_path):
|
|
145
|
+
filepath = tmp_path / 'out.txt'
|
|
146
|
+
content = 'Hello, Tuna!'
|
|
147
|
+
tool = WriteFileTool(None)
|
|
148
|
+
result = await tool._execute(str(filepath), content)
|
|
149
|
+
assert filepath.read_text(encoding='utf-8') == content
|
|
150
|
+
assert 'Successfully wrote to new file' in result
|
|
151
|
+
assert not os.path.exists(str(filepath) + '.tuna_tmp')
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@pytest.mark.asyncio
|
|
155
|
+
async def test_write_file_convenience(tmp_path):
|
|
156
|
+
filepath = tmp_path / 'out2.txt'
|
|
157
|
+
content = 'TunaCode!'
|
|
158
|
+
result = await write_file(str(filepath), content)
|
|
159
|
+
assert filepath.read_text(encoding='utf-8') == content
|
|
160
|
+
assert 'Successfully wrote to new file' in result
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@pytest.mark.asyncio
|
|
164
|
+
async def test_update_file_tool_atomic(tmp_path):
|
|
165
|
+
filepath = tmp_path / 'in.txt'
|
|
166
|
+
original = 'foo\nbar\nbaz\n'
|
|
167
|
+
filepath.write_text(original, encoding='utf-8')
|
|
168
|
+
tool = UpdateFileTool(None)
|
|
169
|
+
result = await tool._execute(str(filepath), 'bar', 'qux')
|
|
170
|
+
updated = filepath.read_text(encoding='utf-8')
|
|
171
|
+
assert 'bar' not in updated and 'qux' in updated
|
|
172
|
+
assert 'updated successfully' in result
|
|
173
|
+
assert not os.path.exists(str(filepath) + '.tuna_tmp')
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@pytest.mark.asyncio
|
|
177
|
+
async def test_update_file_convenience(tmp_path):
|
|
178
|
+
filepath = tmp_path / 'in2.txt'
|
|
179
|
+
original = 'alpha\nbeta\n'
|
|
180
|
+
filepath.write_text(original, encoding='utf-8')
|
|
181
|
+
result = await update_file(str(filepath), 'alpha', 'gamma')
|
|
182
|
+
updated = filepath.read_text(encoding='utf-8')
|
|
183
|
+
assert 'alpha' not in updated and 'gamma' in updated
|
|
184
|
+
assert 'updated successfully' in result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|