deepy-cli 0.1.8__tar.gz → 0.1.9__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.
Files changed (71) hide show
  1. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/PKG-INFO +2 -2
  2. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/README.md +1 -1
  3. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/pyproject.toml +1 -1
  4. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/__init__.py +1 -1
  5. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/tools/builtin.py +80 -9
  6. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/prompt_input.py +86 -1
  7. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/__main__.py +0 -0
  8. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/cli.py +0 -0
  9. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/config/__init__.py +0 -0
  10. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/config/settings.py +0 -0
  11. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/__init__.py +0 -0
  12. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/AskUserQuestion.md +0 -0
  13. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/WebFetch.md +0 -0
  14. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/WebSearch.md +0 -0
  15. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/__init__.py +0 -0
  16. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/edit.md +0 -0
  17. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/modify.md +0 -0
  18. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/read.md +0 -0
  19. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/shell.md +0 -0
  20. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/data/tools/write.md +0 -0
  21. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/errors.py +0 -0
  22. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/__init__.py +0 -0
  23. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/agent.py +0 -0
  24. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/compaction.py +0 -0
  25. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/context.py +0 -0
  26. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/events.py +0 -0
  27. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/model_capabilities.py +0 -0
  28. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/provider.py +0 -0
  29. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/replay.py +0 -0
  30. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/runner.py +0 -0
  31. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/llm/thinking.py +0 -0
  32. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/prompts/__init__.py +0 -0
  33. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/prompts/compact.py +0 -0
  34. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/prompts/rules.py +0 -0
  35. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/prompts/runtime_context.py +0 -0
  36. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/prompts/system.py +0 -0
  37. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/prompts/tool_docs.py +0 -0
  38. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/sessions/__init__.py +0 -0
  39. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/sessions/jsonl.py +0 -0
  40. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/sessions/manager.py +0 -0
  41. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/skills.py +0 -0
  42. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/status.py +0 -0
  43. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/tools/__init__.py +0 -0
  44. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/tools/agents.py +0 -0
  45. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/tools/file_state.py +0 -0
  46. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/tools/result.py +0 -0
  47. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/tools/shell_utils.py +0 -0
  48. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/__init__.py +0 -0
  49. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/app.py +0 -0
  50. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/ask_user_question.py +0 -0
  51. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/exit_summary.py +0 -0
  52. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/loading_text.py +0 -0
  53. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/markdown.py +0 -0
  54. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/message_view.py +0 -0
  55. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/model_picker.py +0 -0
  56. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/prompt_buffer.py +0 -0
  57. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/session_list.py +0 -0
  58. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/session_picker.py +0 -0
  59. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/slash_commands.py +0 -0
  60. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/styles.py +0 -0
  61. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/terminal.py +0 -0
  62. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/theme_picker.py +0 -0
  63. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/thinking_state.py +0 -0
  64. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/ui/welcome.py +0 -0
  65. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/update_check.py +0 -0
  66. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/usage.py +0 -0
  67. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/utils/__init__.py +0 -0
  68. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/utils/debug_logger.py +0 -0
  69. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/utils/error_logger.py +0 -0
  70. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/utils/json.py +0 -0
  71. {deepy_cli-0.1.8 → deepy_cli-0.1.9}/src/deepy/utils/notify.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: deepy-cli
3
- Version: 0.1.8
3
+ Version: 0.1.9
4
4
  Summary: Deepy - Vibe coding for DeepSeek models in your terminal
5
5
  Keywords: deepseek,coding-agent,terminal,cli,agents
6
6
  Author: kirineko
@@ -238,5 +238,5 @@ assets live outside the package directory and are not included in the wheel.
238
238
 
239
239
  ## Release Status
240
240
 
241
- Deepy `0.1.8` is released through GitHub and PyPI. Standalone binaries and npm
241
+ Deepy `0.1.9` is released through GitHub and PyPI. Standalone binaries and npm
242
242
  wrappers can be added later, but the primary distribution is the Python CLI.
@@ -210,5 +210,5 @@ assets live outside the package directory and are not included in the wheel.
210
210
 
211
211
  ## Release Status
212
212
 
213
- Deepy `0.1.8` is released through GitHub and PyPI. Standalone binaries and npm
213
+ Deepy `0.1.9` is released through GitHub and PyPI. Standalone binaries and npm
214
214
  wrappers can be added later, but the primary distribution is the Python CLI.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.1.8"
3
+ version = "0.1.9"
4
4
  description = "Deepy - Vibe coding for DeepSeek models in your terminal"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.1.8"
3
+ __version__ = "0.1.9"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -141,6 +141,13 @@ class MatchOccurrence:
141
141
  end_line: int
142
142
 
143
143
 
144
+ @dataclass(frozen=True)
145
+ class EditMatchResult:
146
+ matches: list[MatchOccurrence]
147
+ matched_text: str
148
+ matched_via: str
149
+
150
+
144
151
  @dataclass(frozen=True)
145
152
  class ClosestMatch:
146
153
  text: str
@@ -232,6 +239,63 @@ def _find_occurrences(text: str, needle: str, scope: tuple[int, int]) -> list[Ma
232
239
  search_index = found + len(needle)
233
240
 
234
241
 
242
+ def _find_edit_occurrences(
243
+ text: str,
244
+ needle: str,
245
+ scope: tuple[int, int],
246
+ line_endings: str,
247
+ *,
248
+ matched_via: str = "exact",
249
+ ) -> EditMatchResult | None:
250
+ matches = _find_occurrences(text, needle, scope)
251
+ if matches:
252
+ return EditMatchResult(matches=matches, matched_text=needle, matched_via=matched_via)
253
+ normalized_needle = _normalize_line_endings(needle, line_endings)
254
+ if normalized_needle == needle:
255
+ return None
256
+ normalized_matches = _find_occurrences(text, normalized_needle, scope)
257
+ if not normalized_matches:
258
+ return None
259
+ return EditMatchResult(
260
+ matches=normalized_matches,
261
+ matched_text=normalized_needle,
262
+ matched_via=_line_ending_matched_via(matched_via),
263
+ )
264
+
265
+
266
+ def _find_loose_escape_edit_occurrences(
267
+ text: str,
268
+ needle: str,
269
+ scope: tuple[int, int],
270
+ line_endings: str,
271
+ ) -> list[tuple[MatchOccurrence, float, str, str]]:
272
+ matches = [
273
+ (occurrence, score, matched_text, "loose_escape")
274
+ for occurrence, score, matched_text in _find_loose_escape_occurrences(text, needle, scope)
275
+ ]
276
+ normalized_needle = _normalize_line_endings(needle, line_endings)
277
+ if normalized_needle == needle:
278
+ return matches
279
+ matches.extend(
280
+ (
281
+ occurrence,
282
+ score,
283
+ matched_text,
284
+ "loose_escape_line_endings",
285
+ )
286
+ for occurrence, score, matched_text in _find_loose_escape_occurrences(
287
+ text,
288
+ normalized_needle,
289
+ scope,
290
+ )
291
+ )
292
+ return matches
293
+
294
+
295
+ def _line_ending_matched_via(matched_via: str) -> str:
296
+ return "line_endings" if matched_via == "exact" else f"{matched_via}_line_endings"
297
+
298
+
235
299
  def _offset_to_line(text: str, offset: int) -> int:
236
300
  if offset <= 0:
237
301
  return 1
@@ -1309,12 +1373,14 @@ class ToolRuntime:
1309
1373
  return ToolResult.error_result(name, error or "File is not writable.").to_json()
1310
1374
  text_metadata = _read_text_metadata(target)
1311
1375
  text = text_metadata.content
1376
+ line_endings = text_metadata.line_endings
1312
1377
  scope = _edit_scope(text, snippet)
1313
- matches = _find_occurrences(text, old, scope)
1314
- matched_via = "exact"
1378
+ match_result = _find_edit_occurrences(text, old, scope, line_endings)
1379
+ matches = match_result.matches if match_result is not None else []
1380
+ matched_via = match_result.matched_via if match_result is not None else "exact"
1315
1381
  replacement_new = new
1316
1382
  if not matches:
1317
- loose_matches = _find_loose_escape_occurrences(text, old, scope)
1383
+ loose_matches = _find_loose_escape_edit_occurrences(text, old, scope, line_endings)
1318
1384
  if len(loose_matches) == 1 and loose_matches[0][1] == 1.0:
1319
1385
  corrected = _correct_escaped_strings_with_llm(
1320
1386
  self.settings,
@@ -1325,14 +1391,20 @@ class ToolRuntime:
1325
1391
  )
1326
1392
  if corrected is not None:
1327
1393
  corrected_old, corrected_new = corrected
1328
- corrected_matches = _find_occurrences(text, corrected_old, scope)
1329
- if corrected_matches:
1330
- matches = corrected_matches
1394
+ corrected_match_result = _find_edit_occurrences(
1395
+ text,
1396
+ corrected_old,
1397
+ scope,
1398
+ line_endings,
1399
+ matched_via="llm_escape_correction",
1400
+ )
1401
+ if corrected_match_result is not None:
1402
+ matches = corrected_match_result.matches
1331
1403
  replacement_new = corrected_new
1332
- matched_via = "llm_escape_correction"
1404
+ matched_via = corrected_match_result.matched_via
1333
1405
  if not matches:
1334
1406
  matches = [loose_matches[0][0]]
1335
- matched_via = "loose_escape"
1407
+ matched_via = loose_matches[0][3]
1336
1408
  if not matches:
1337
1409
  closest_match = _find_closest_match(text, old, scope)
1338
1410
  metadata = {"scope": _format_scope_metadata(target, snippet, scope, text)}
@@ -1364,7 +1436,6 @@ class ToolRuntime:
1364
1436
  ),
1365
1437
  },
1366
1438
  ).to_json()
1367
- line_endings = text_metadata.line_endings
1368
1439
  normalized_new = _normalize_line_endings(replacement_new, line_endings)
1369
1440
  updated = _apply_replacements(text, matches, normalized_new, replace_all)
1370
1441
  _write_text_with_encoding(target, updated, text_metadata.encoding)
@@ -34,6 +34,7 @@ SHIFT_ENTER_SEQUENCES = (
34
34
  "\x1b[13;2u", # Kitty/fixterms CSI-u format, used by modern terminals.
35
35
  )
36
36
  _WINDOWS_SHIFT_ENTER_PATCH_ATTR = "_deepy_shift_enter_patched"
37
+ _WINDOWS_SHIFT_ENTER_VT100_PATCH_ATTR = "_deepy_shift_enter_vt100_patched"
37
38
 
38
39
 
39
40
  @dataclass(frozen=True)
@@ -92,6 +93,12 @@ def build_prompt_key_bindings(
92
93
  def _(event) -> None: # pragma: no cover - prompt_toolkit calls this callback
93
94
  event.current_buffer.insert_text("\n")
94
95
 
96
+ if is_windows_newline_fallback_enabled():
97
+
98
+ @bindings.add("c-j")
99
+ def _(event) -> None: # pragma: no cover - prompt_toolkit calls this callback
100
+ event.current_buffer.insert_text("\n")
101
+
95
102
  return bindings
96
103
 
97
104
 
@@ -111,16 +118,40 @@ def install_windows_shift_enter_key_sequence_override(
111
118
  *,
112
119
  platform_name: str | None = None,
113
120
  console_input_reader_cls: type | None = None,
121
+ vt100_console_input_reader_cls: type | None = None,
122
+ event_types: object | None = None,
123
+ key_event_record_cls: type | None = None,
114
124
  ) -> bool:
115
125
  resolved_platform = platform_name or sys.platform
116
126
  if not resolved_platform.startswith("win"):
117
127
  return False
118
- if console_input_reader_cls is None:
128
+ if console_input_reader_cls is None and vt100_console_input_reader_cls is None:
119
129
  try:
120
130
  from prompt_toolkit.input import win32
121
131
  except (AssertionError, ImportError):
122
132
  return False
123
133
  console_input_reader_cls = win32.ConsoleInputReader
134
+ vt100_console_input_reader_cls = getattr(win32, "Vt100ConsoleInputReader", None)
135
+ if event_types is None:
136
+ event_types = getattr(win32, "EventTypes", None)
137
+ if key_event_record_cls is None:
138
+ key_event_record_cls = getattr(win32, "KEY_EVENT_RECORD", None)
139
+ patched = False
140
+ if console_input_reader_cls is not None:
141
+ patched = _patch_windows_console_input_reader(console_input_reader_cls) or patched
142
+ if vt100_console_input_reader_cls is not None:
143
+ patched = (
144
+ _patch_windows_vt100_console_input_reader(
145
+ vt100_console_input_reader_cls,
146
+ event_types=event_types,
147
+ key_event_record_cls=key_event_record_cls,
148
+ )
149
+ or patched
150
+ )
151
+ return patched
152
+
153
+
154
+ def _patch_windows_console_input_reader(console_input_reader_cls: type) -> bool:
124
155
  if getattr(console_input_reader_cls, _WINDOWS_SHIFT_ENTER_PATCH_ATTR, False):
125
156
  return True
126
157
 
@@ -145,6 +176,46 @@ def install_windows_shift_enter_key_sequence_override(
145
176
  return True
146
177
 
147
178
 
179
+ def _patch_windows_vt100_console_input_reader(
180
+ vt100_console_input_reader_cls: type,
181
+ *,
182
+ event_types: object | None,
183
+ key_event_record_cls: type | None,
184
+ ) -> bool:
185
+ if event_types is None or key_event_record_cls is None:
186
+ return False
187
+ if getattr(vt100_console_input_reader_cls, _WINDOWS_SHIFT_ENTER_VT100_PATCH_ATTR, False):
188
+ return True
189
+
190
+ original_get_keys = vt100_console_input_reader_cls._get_keys
191
+ shift_pressed = getattr(vt100_console_input_reader_cls, "SHIFT_PRESSED", 0x0010)
192
+
193
+ def patched_get_keys(self, read, input_records):
194
+ for index in range(read.value):
195
+ input_record = input_records[index]
196
+ if input_record.EventType not in event_types:
197
+ continue
198
+ event_name = event_types[input_record.EventType]
199
+ event = getattr(input_record.Event, event_name)
200
+ if not isinstance(event, key_event_record_cls) or not event.KeyDown:
201
+ continue
202
+ if _is_windows_shift_enter_event(event, shift_pressed=shift_pressed):
203
+ yield SHIFT_ENTER_SEQUENCES[0]
204
+ continue
205
+ u_char = event.uChar.UnicodeChar
206
+ if u_char != "\x00":
207
+ yield u_char
208
+
209
+ setattr(
210
+ vt100_console_input_reader_cls,
211
+ "_deepy_original_get_keys",
212
+ original_get_keys,
213
+ )
214
+ vt100_console_input_reader_cls._get_keys = patched_get_keys
215
+ setattr(vt100_console_input_reader_cls, _WINDOWS_SHIFT_ENTER_VT100_PATCH_ATTR, True)
216
+ return True
217
+
218
+
148
219
  def _is_windows_shift_enter_key_press(
149
220
  ev,
150
221
  key_presses: list,
@@ -160,6 +231,20 @@ def _is_windows_shift_enter_key_press(
160
231
  return key in {Keys.ControlM, Keys.ControlJ, Keys.Enter}
161
232
 
162
233
 
234
+ def _is_windows_shift_enter_event(ev, *, shift_pressed: int) -> bool:
235
+ control_key_state = getattr(ev, "ControlKeyState", 0)
236
+ if not control_key_state & shift_pressed:
237
+ return False
238
+ u_char = getattr(getattr(ev, "uChar", None), "UnicodeChar", "")
239
+ virtual_key_code = getattr(ev, "VirtualKeyCode", None)
240
+ return u_char in {"\r", "\n"} or virtual_key_code == 13
241
+
242
+
243
+ def is_windows_newline_fallback_enabled(platform_name: str | None = None) -> bool:
244
+ resolved_platform = platform_name or sys.platform
245
+ return resolved_platform.startswith("win")
246
+
247
+
163
248
  def prompt_for_input(
164
249
  session: PromptSession[str],
165
250
  message: AnyFormattedText | None = None,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes