tunacode-cli 0.0.15__tar.gz → 0.0.16__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 (74) hide show
  1. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/PKG-INFO +1 -1
  2. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/pyproject.toml +1 -1
  3. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/constants.py +1 -1
  4. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode_cli.egg-info/PKG-INFO +1 -1
  5. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode_cli.egg-info/SOURCES.txt +2 -1
  6. tunacode_cli-0.0.16/tests/test_escape_mechanism.py +184 -0
  7. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/LICENSE +0 -0
  8. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/README.md +0 -0
  9. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/setup.cfg +0 -0
  10. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/setup.py +0 -0
  11. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/__init__.py +0 -0
  12. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/cli/__init__.py +0 -0
  13. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/cli/commands.py +0 -0
  14. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/cli/main.py +0 -0
  15. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/cli/repl.py +0 -0
  16. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/cli/textual_app.py +0 -0
  17. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/cli/textual_bridge.py +0 -0
  18. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/configuration/__init__.py +0 -0
  19. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/configuration/defaults.py +0 -0
  20. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/configuration/models.py +0 -0
  21. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/configuration/settings.py +0 -0
  22. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/context.py +0 -0
  23. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/__init__.py +0 -0
  24. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/agents/__init__.py +0 -0
  25. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/agents/main.py +0 -0
  26. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/setup/__init__.py +0 -0
  27. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/setup/agent_setup.py +0 -0
  28. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/setup/base.py +0 -0
  29. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/setup/config_setup.py +0 -0
  30. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/setup/coordinator.py +0 -0
  31. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/setup/environment_setup.py +0 -0
  32. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  33. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/state.py +0 -0
  34. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/core/tool_handler.py +0 -0
  35. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/exceptions.py +0 -0
  36. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/prompts/system.txt +0 -0
  37. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/py.typed +0 -0
  38. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/services/__init__.py +0 -0
  39. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/services/mcp.py +0 -0
  40. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/setup.py +0 -0
  41. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/tools/__init__.py +0 -0
  42. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/tools/base.py +0 -0
  43. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/tools/bash.py +0 -0
  44. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/tools/grep.py +0 -0
  45. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/tools/read_file.py +0 -0
  46. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/tools/run_command.py +0 -0
  47. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/tools/update_file.py +0 -0
  48. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/tools/write_file.py +0 -0
  49. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/types.py +0 -0
  50. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/__init__.py +0 -0
  51. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/completers.py +0 -0
  52. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/console.py +0 -0
  53. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/constants.py +0 -0
  54. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/decorators.py +0 -0
  55. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/input.py +0 -0
  56. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/keybindings.py +0 -0
  57. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/lexers.py +0 -0
  58. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/output.py +0 -0
  59. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/panels.py +0 -0
  60. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/prompt_manager.py +0 -0
  61. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/tool_ui.py +0 -0
  62. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/ui/validators.py +0 -0
  63. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/utils/__init__.py +0 -0
  64. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/utils/bm25.py +0 -0
  65. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/utils/diff_utils.py +0 -0
  66. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/utils/file_utils.py +0 -0
  67. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/utils/ripgrep.py +0 -0
  68. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/utils/system.py +0 -0
  69. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/utils/text_utils.py +0 -0
  70. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode/utils/user_configuration.py +0 -0
  71. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  72. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  73. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/src/tunacode_cli.egg-info/requires.txt +0 -0
  74. {tunacode_cli-0.0.15 → tunacode_cli-0.0.16}/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.15
3
+ Version: 0.0.16
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tunacode-cli"
7
- version = "0.0.15"
7
+ version = "0.0.16"
8
8
  description = "Your agentic CLI developer."
9
9
  keywords = ["cli", "agent", "development", "automation"]
10
10
  readme = "README.md"
@@ -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.15"
10
+ APP_VERSION = "0.0.16"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.15
3
+ Version: 0.0.16
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -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