code-puppy 0.0.83__py3-none-any.whl → 0.0.85__py3-none-any.whl

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.
@@ -1,14 +1,117 @@
1
+ import os
2
+ import signal
1
3
  import subprocess
4
+ import threading
2
5
  import time
3
- from typing import Any, Dict
6
+ import traceback
7
+ import sys
8
+ from typing import Set
4
9
 
5
10
  from pydantic import BaseModel
6
11
  from pydantic_ai import RunContext
7
12
  from rich.markdown import Markdown
8
- from rich.syntax import Syntax
13
+ from rich.text import Text
9
14
 
10
15
  from code_puppy.tools.common import console
11
16
 
17
+ _AWAITING_USER_INPUT = False
18
+
19
+ _CONFIRMATION_LOCK = threading.Lock()
20
+
21
+ # Track running shell processes so we can kill them on Ctrl-C from the UI
22
+ _RUNNING_PROCESSES: Set[subprocess.Popen] = set()
23
+ _RUNNING_PROCESSES_LOCK = threading.Lock()
24
+ _USER_KILLED_PROCESSES = set()
25
+
26
+ def _register_process(proc: subprocess.Popen) -> None:
27
+ with _RUNNING_PROCESSES_LOCK:
28
+ _RUNNING_PROCESSES.add(proc)
29
+
30
+
31
+ def _unregister_process(proc: subprocess.Popen) -> None:
32
+ with _RUNNING_PROCESSES_LOCK:
33
+ _RUNNING_PROCESSES.discard(proc)
34
+
35
+
36
+ def _kill_process_group(proc: subprocess.Popen) -> None:
37
+ """Attempt to aggressively terminate a process and its group.
38
+
39
+ Cross-platform best-effort. On POSIX, uses process groups. On Windows, tries CTRL_BREAK_EVENT, then terminate().
40
+ """
41
+ try:
42
+ if sys.platform.startswith("win"):
43
+ try:
44
+ # Try a soft break first if the group exists
45
+ proc.send_signal(signal.CTRL_BREAK_EVENT) # type: ignore[attr-defined]
46
+ time.sleep(0.8)
47
+ except Exception:
48
+ pass
49
+ if proc.poll() is None:
50
+ try:
51
+ proc.terminate()
52
+ time.sleep(0.8)
53
+ except Exception:
54
+ pass
55
+ if proc.poll() is None:
56
+ try:
57
+ proc.kill()
58
+ except Exception:
59
+ pass
60
+ return
61
+
62
+ # POSIX
63
+ pid = proc.pid
64
+ try:
65
+ pgid = os.getpgid(pid)
66
+ os.killpg(pgid, signal.SIGTERM)
67
+ time.sleep(1.0)
68
+ if proc.poll() is None:
69
+ os.killpg(pgid, signal.SIGINT)
70
+ time.sleep(0.6)
71
+ if proc.poll() is None:
72
+ os.killpg(pgid, signal.SIGKILL)
73
+ time.sleep(0.5)
74
+ except (OSError, ProcessLookupError):
75
+ # Fall back to direct kill of the process
76
+ try:
77
+ if proc.poll() is None:
78
+ proc.kill()
79
+ except (OSError, ProcessLookupError):
80
+ pass
81
+
82
+ if proc.poll() is None:
83
+ # Last ditch attempt; may be unkillable zombie
84
+ try:
85
+ for _ in range(3):
86
+ os.kill(proc.pid, signal.SIGKILL)
87
+ time.sleep(0.2)
88
+ if proc.poll() is not None:
89
+ break
90
+ except Exception:
91
+ pass
92
+ except Exception as e:
93
+ console.print(f"Kill process error: {e}")
94
+
95
+
96
+ def kill_all_running_shell_processes() -> int:
97
+ """Kill all currently tracked running shell processes.
98
+
99
+ Returns the number of processes signaled.
100
+ """
101
+ procs: list[subprocess.Popen]
102
+ with _RUNNING_PROCESSES_LOCK:
103
+ procs = list(_RUNNING_PROCESSES)
104
+ count = 0
105
+ for p in procs:
106
+ try:
107
+ if p.poll() is None:
108
+ _kill_process_group(p)
109
+ count += 1
110
+ _USER_KILLED_PROCESSES.add(p.pid)
111
+ finally:
112
+ _unregister_process(p)
113
+ return count
114
+
12
115
 
13
116
  class ShellCommandOutput(BaseModel):
14
117
  success: bool
@@ -19,35 +122,250 @@ class ShellCommandOutput(BaseModel):
19
122
  exit_code: int | None
20
123
  execution_time: float | None
21
124
  timeout: bool | None = False
125
+ user_interrupted: bool | None = False
126
+
127
+
128
+ def run_shell_command_streaming(
129
+ process: subprocess.Popen, timeout: int = 60, command: str = ""
130
+ ):
131
+ start_time = time.time()
132
+ last_output_time = [start_time]
133
+
134
+ ABSOLUTE_TIMEOUT_SECONDS = 270
135
+
136
+ stdout_lines = []
137
+ stderr_lines = []
138
+
139
+ stdout_thread = None
140
+ stderr_thread = None
141
+
142
+ def read_stdout():
143
+ try:
144
+ for line in iter(process.stdout.readline, ""):
145
+ if line:
146
+ line = line.rstrip("\n\r")
147
+ stdout_lines.append(line)
148
+ console.log(line)
149
+ last_output_time[0] = time.time()
150
+ except Exception:
151
+ pass
152
+
153
+ def read_stderr():
154
+ try:
155
+ for line in iter(process.stderr.readline, ""):
156
+ if line:
157
+ line = line.rstrip("\n\r")
158
+ stderr_lines.append(line)
159
+ console.log(line)
160
+ last_output_time[0] = time.time()
161
+ except Exception:
162
+ pass
163
+
164
+ def cleanup_process_and_threads(timeout_type: str = "unknown"):
165
+ nonlocal stdout_thread, stderr_thread
166
+
167
+ def nuclear_kill(proc):
168
+ _kill_process_group(proc)
169
+
170
+ try:
171
+ if process.poll() is None:
172
+ nuclear_kill(process)
173
+
174
+ try:
175
+ if process.stdout and not process.stdout.closed:
176
+ process.stdout.close()
177
+ if process.stderr and not process.stderr.closed:
178
+ process.stderr.close()
179
+ if process.stdin and not process.stdin.closed:
180
+ process.stdin.close()
181
+ except (OSError, ValueError):
182
+ pass
183
+
184
+ # Unregister once we're done cleaning up
185
+ _unregister_process(process)
186
+
187
+ if stdout_thread and stdout_thread.is_alive():
188
+ stdout_thread.join(timeout=3)
189
+ if stdout_thread.is_alive():
190
+ console.print(
191
+ f"stdout reader thread failed to terminate after {timeout_type} seconds"
192
+ )
193
+
194
+ if stderr_thread and stderr_thread.is_alive():
195
+ stderr_thread.join(timeout=3)
196
+ if stderr_thread.is_alive():
197
+ console.print(
198
+ f"stderr reader thread failed to terminate after {timeout_type} seconds"
199
+ )
200
+
201
+ except Exception as e:
202
+ console.log(f"Error during process cleanup {e}")
203
+
204
+ execution_time = time.time() - start_time
205
+ return ShellCommandOutput(
206
+ **{
207
+ "success": False,
208
+ "command": command,
209
+ "stdout": "\n".join(stdout_lines[-1000:]),
210
+ "stderr": "\n".join(stderr_lines[-1000:]),
211
+ "exit_code": -9,
212
+ "execution_time": execution_time,
213
+ "timeout": True,
214
+ "error": f"Command timed out after {timeout} seconds",
215
+ }
216
+ )
217
+
218
+ try:
219
+ stdout_thread = threading.Thread(target=read_stdout, daemon=True)
220
+ stderr_thread = threading.Thread(target=read_stderr, daemon=True)
221
+
222
+ stdout_thread.start()
223
+ stderr_thread.start()
224
+
225
+ while process.poll() is None:
226
+ current_time = time.time()
227
+
228
+ if current_time - start_time > ABSOLUTE_TIMEOUT_SECONDS:
229
+ error_msg = Text()
230
+ error_msg.append(
231
+ "Process killed: inactivity timeout reached", style="bold red"
232
+ )
233
+ console.print(error_msg)
234
+ return cleanup_process_and_threads("absolute")
235
+
236
+ if current_time - last_output_time[0] > timeout:
237
+ error_msg = Text()
238
+ error_msg.append(
239
+ "Process killed: inactivity timeout reached", style="bold red"
240
+ )
241
+ console.print(error_msg)
242
+ return cleanup_process_and_threads("inactivity")
243
+
244
+ time.sleep(0.1)
245
+
246
+ if stdout_thread:
247
+ stdout_thread.join(timeout=5)
248
+ if stderr_thread:
249
+ stderr_thread.join(timeout=5)
250
+
251
+ exit_code = process.returncode
252
+ execution_time = time.time() - start_time
253
+
254
+ try:
255
+ if process.stdout and not process.stdout.closed:
256
+ process.stdout.close()
257
+ if process.stderr and not process.stderr.closed:
258
+ process.stderr.close()
259
+ if process.stdin and not process.stdin.closed:
260
+ process.stdin.close()
261
+ except (OSError, ValueError):
262
+ pass
263
+
264
+ _unregister_process(process)
265
+
266
+ if exit_code != 0:
267
+ console.print(
268
+ f"Command failed with exit code {exit_code}", style="bold red"
269
+ )
270
+ console.print(f"Took {execution_time:.2f}s", style="dim")
271
+ time.sleep(1)
272
+ return ShellCommandOutput(
273
+ success=False,
274
+ command=command,
275
+ error="""The process didn't exit cleanly! If the user_interrupted flag is true,
276
+ please stop all execution and ask the user for clarification!""",
277
+ stdout="\n".join(stdout_lines[-1000:]),
278
+ stderr="\n".join(stderr_lines[-1000:]),
279
+ exit_code=exit_code,
280
+ execution_time=execution_time,
281
+ timeout=False,
282
+ user_interrupted=process.pid in _USER_KILLED_PROCESSES
283
+ )
284
+ return ShellCommandOutput(
285
+ success=exit_code == 0,
286
+ command=command,
287
+ stdout="\n".join(stdout_lines[-1000:]),
288
+ stderr="\n".join(stderr_lines[-1000:]),
289
+ exit_code=exit_code,
290
+ execution_time=execution_time,
291
+ timeout=False,
292
+ )
293
+
294
+ except Exception as e:
295
+ return ShellCommandOutput(
296
+ success=False,
297
+ command=command,
298
+ error=f"Error durign streaming execution {str(e)}",
299
+ stdout="\n".join(stdout_lines[-1000:]),
300
+ stderr="\n".join(stderr_lines[-1000:]),
301
+ exit_code=-1,
302
+ timeout=False,
303
+ )
304
+
22
305
 
23
306
  def run_shell_command(
24
307
  context: RunContext, command: str, cwd: str = None, timeout: int = 60
25
308
  ) -> ShellCommandOutput:
309
+ command_displayed = False
26
310
  if not command or not command.strip():
27
311
  console.print("[bold red]Error:[/bold red] Command cannot be empty")
28
- return ShellCommandOutput(**{"success": False, "error": "Command cannot be empty"})
312
+ return ShellCommandOutput(
313
+ **{"success": False, "error": "Command cannot be empty"}
314
+ )
29
315
  console.print(
30
316
  f"\n[bold white on blue] SHELL COMMAND [/bold white on blue] \U0001f4c2 [bold green]$ {command}[/bold green]"
31
317
  )
32
- if cwd:
33
- console.print(f"[dim]Working directory: {cwd}[/dim]")
34
- console.print("[dim]" + "-" * 60 + "[/dim]")
35
318
  from code_puppy.config import get_yolo_mode
36
319
 
37
320
  yolo_mode = get_yolo_mode()
38
- if not yolo_mode:
39
- user_input = input("Are you sure you want to run this command? (yes/no): ")
40
- if user_input.strip().lower() not in {"yes", "y"}:
41
- console.print(
42
- "[bold yellow]Command execution canceled by user.[/bold yellow]"
321
+
322
+ confirmation_lock_acquired = False
323
+
324
+ # Only ask for confirmation if we're in an interactive TTY and not in yolo mode.
325
+ if not yolo_mode and sys.stdin.isatty():
326
+ confirmation_lock_acquired = _CONFIRMATION_LOCK.acquire(blocking=False)
327
+ if not confirmation_lock_acquired:
328
+ return ShellCommandOutput(
329
+ success=False,
330
+ command=command,
331
+ error="Another command is currently awaiting confirmation",
43
332
  )
44
- return ShellCommandOutput(**{
45
- "success": False,
46
- "command": command,
47
- "error": "User canceled command execution",
48
- })
49
- try:
333
+
334
+ command_displayed = True
335
+
336
+ if cwd:
337
+ console.print(f"[dim] Working directory: {cwd} [/dim]")
338
+ time.sleep(0.2)
339
+ sys.stdout.write("Are you sure you want to run this command? (y(es)/n(o))\n")
340
+ sys.stdout.flush()
341
+
342
+ try:
343
+ user_input = input()
344
+ confirmed = user_input.strip().lower() in {"yes", "y"}
345
+ except (KeyboardInterrupt, EOFError):
346
+ console.print("\n Cancelled by user")
347
+ confirmed = False
348
+ finally:
349
+ if confirmation_lock_acquired:
350
+ _CONFIRMATION_LOCK.release()
351
+
352
+ if not confirmed:
353
+ result = ShellCommandOutput(
354
+ success=False, command=command, error="User rejected the command!"
355
+ )
356
+ return result
357
+ else:
50
358
  start_time = time.time()
359
+ try:
360
+ creationflags = 0
361
+ preexec_fn = None
362
+ if sys.platform.startswith("win"):
363
+ try:
364
+ creationflags = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore[attr-defined]
365
+ except Exception:
366
+ creationflags = 0
367
+ else:
368
+ preexec_fn = os.setsid if hasattr(os, "setsid") else None
51
369
  process = subprocess.Popen(
52
370
  command,
53
371
  shell=True,
@@ -55,112 +373,33 @@ def run_shell_command(
55
373
  stderr=subprocess.PIPE,
56
374
  text=True,
57
375
  cwd=cwd,
376
+ bufsize=1,
377
+ universal_newlines=True,
378
+ preexec_fn=preexec_fn,
379
+ creationflags=creationflags,
58
380
  )
381
+ _register_process(process)
59
382
  try:
60
- stdout, stderr = process.communicate(timeout=timeout)
61
- exit_code = process.returncode
62
- execution_time = time.time() - start_time
63
- if stdout.strip():
64
- console.print("[bold white]STDOUT:[/bold white]")
65
- console.print(
66
- Syntax(
67
- stdout.strip(),
68
- "bash",
69
- theme="monokai",
70
- background_color="default",
71
- )
72
- )
73
- else:
74
- console.print("[yellow]No STDOUT output[/yellow]")
75
- if stderr.strip():
76
- console.print("[bold yellow]STDERR:[/bold yellow]")
77
- console.print(
78
- Syntax(
79
- stderr.strip(),
80
- "bash",
81
- theme="monokai",
82
- background_color="default",
83
- )
84
- )
85
- if exit_code == 0:
86
- console.print(
87
- f"[bold green]✓ Command completed successfully[/bold green] [dim](took {execution_time:.2f}s)[/dim]"
88
- )
89
- else:
90
- console.print(
91
- f"[bold red]✗ Command failed with exit code {exit_code}[/bold red] [dim](took {execution_time:.2f}s)[/dim]"
92
- )
93
- if not stdout.strip() and not stderr.strip():
94
- console.print(
95
- "[bold yellow]This command produced no output at all![/bold yellow]"
96
- )
97
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
98
- return ShellCommandOutput(**{
99
- "success": exit_code == 0,
100
- "command": command,
101
- "stdout": stdout,
102
- "stderr": stderr,
103
- "exit_code": exit_code,
104
- "execution_time": execution_time,
105
- "timeout": False,
106
- })
107
- except subprocess.TimeoutExpired:
108
- process.kill()
109
- stdout, stderr = process.communicate()
110
- execution_time = time.time() - start_time
111
- if stdout.strip():
112
- console.print(
113
- "[bold white]STDOUT (incomplete due to timeout):[/bold white]"
114
- )
115
- console.print(
116
- Syntax(
117
- stdout.strip(),
118
- "bash",
119
- theme="monokai",
120
- background_color="default",
121
- )
122
- )
123
- if stderr.strip():
124
- console.print("[bold yellow]STDERR:[/bold yellow]")
125
- console.print(
126
- Syntax(
127
- stderr.strip(),
128
- "bash",
129
- theme="monokai",
130
- background_color="default",
131
- )
132
- )
133
- console.print(
134
- f"[bold red]⏱ Command timed out after {timeout} seconds[/bold red] [dim](ran for {execution_time:.2f}s)[/dim]"
135
- )
136
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
137
- return ShellCommandOutput(**{
138
- "success": False,
139
- "command": command,
140
- "stdout": stdout[-1000:],
141
- "stderr": stderr[-1000:],
142
- "exit_code": None,
143
- "execution_time": execution_time,
144
- "timeout": True,
145
- "error": f"Command timed out after {timeout} seconds",
146
- })
383
+ return run_shell_command_streaming(process, timeout=timeout, command=command)
384
+ finally:
385
+ # Ensure unregistration in case streaming returned early or raised
386
+ _unregister_process(process)
147
387
  except Exception as e:
148
- console.print_exception(show_locals=True)
149
- console.print("[dim]" + "-" * 60 + "[/dim]\n")
150
- # Ensure stdout and stderr are always defined
388
+ console.print(traceback.format_exc())
151
389
  if "stdout" not in locals():
152
390
  stdout = None
153
391
  if "stderr" not in locals():
154
392
  stderr = None
155
- return ShellCommandOutput(**{
156
- "success": False,
157
- "command": command,
158
- "error": f"Error executing command: {str(e)}",
159
- "stdout": stdout[-1000:] if stdout else None,
160
- "stderr": stderr[-1000:] if stderr else None,
161
- "exit_code": -1,
162
- "timeout": False,
163
- })
393
+ return ShellCommandOutput(
394
+ success=False,
395
+ command=command,
396
+ error=f"Error executing command {str(e)}",
397
+ stdout="\n".join(stdout[-1000:]) if stdout else None,
398
+ stderr="\n".join(stderr[-1000:]) if stderr else None,
399
+ exit_code=-1,
400
+ timeout=False,
401
+ )
402
+
164
403
 
165
404
  class ReasoningOutput(BaseModel):
166
405
  success: bool = True
@@ -378,7 +378,9 @@ def register_file_modifications_tools(agent):
378
378
  """Attach file-editing tools to *agent* with mandatory diff rendering."""
379
379
 
380
380
  @agent.tool(retries=5)
381
- def edit_file(context: RunContext, path: str = "", diff: str = "") -> EditFileOutput:
381
+ def edit_file(
382
+ context: RunContext, path: str = "", diff: str = ""
383
+ ) -> EditFileOutput:
382
384
  return EditFileOutput(**_edit_file(context, path, diff))
383
385
 
384
386
  @agent.tool(retries=5)
@@ -1,9 +1,9 @@
1
1
  # file_operations.py
2
2
 
3
3
  import os
4
- from typing import Any, Dict, List
4
+ from typing import List
5
5
 
6
- from pydantic import BaseModel, StrictStr, StrictInt
6
+ from pydantic import BaseModel
7
7
  from pydantic_ai import RunContext
8
8
 
9
9
  from code_puppy.tools.common import console
@@ -41,11 +41,15 @@ def _list_files(
41
41
  f"[bold red]Error:[/bold red] Directory '{directory}' does not exist"
42
42
  )
43
43
  console.print("[dim]" + "-" * 60 + "[/dim]\n")
44
- return ListFileOutput(files=[ListedFile(path=None, type=None, full_path=None, depth=None)])
44
+ return ListFileOutput(
45
+ files=[ListedFile(path=None, type=None, full_path=None, depth=None)]
46
+ )
45
47
  if not os.path.isdir(directory):
46
48
  console.print(f"[bold red]Error:[/bold red] '{directory}' is not a directory")
47
49
  console.print("[dim]" + "-" * 60 + "[/dim]\n")
48
- return ListFileOutput(files=[ListedFile(path=None, type=None, full_path=None, depth=None)])
50
+ return ListFileOutput(
51
+ files=[ListedFile(path=None, type=None, full_path=None, depth=None)]
52
+ )
49
53
  folder_structure = {}
50
54
  file_list = []
51
55
  for root, dirs, files in os.walk(directory):
@@ -57,13 +61,15 @@ def _list_files(
57
61
  if rel_path:
58
62
  dir_path = os.path.join(directory, rel_path)
59
63
  results.append(
60
- ListedFile(**{
61
- "path": rel_path,
62
- "type": "directory",
63
- "size": 0,
64
- "full_path": dir_path,
65
- "depth": depth,
66
- })
64
+ ListedFile(
65
+ **{
66
+ "path": rel_path,
67
+ "type": "directory",
68
+ "size": 0,
69
+ "full_path": dir_path,
70
+ "depth": depth,
71
+ }
72
+ )
67
73
  )
68
74
  folder_structure[rel_path] = {
69
75
  "path": rel_path,
@@ -131,9 +137,7 @@ def _list_files(
131
137
  return "\U0001f4c4"
132
138
 
133
139
  if results:
134
- files = sorted(
135
- [f for f in results if f.type == "file"], key=lambda x: x.path
136
- )
140
+ files = sorted([f for f in results if f.type == "file"], key=lambda x: x.path)
137
141
  console.print(
138
142
  f"\U0001f4c1 [bold blue]{os.path.basename(directory) or directory}[/bold blue]"
139
143
  )
@@ -177,6 +181,7 @@ def _list_files(
177
181
  class ReadFileOutput(BaseModel):
178
182
  content: str | None
179
183
 
184
+
180
185
  def _read_file(context: RunContext, file_path: str) -> ReadFileOutput:
181
186
  file_path = os.path.abspath(file_path)
182
187
  console.print(
@@ -191,7 +196,7 @@ def _read_file(context: RunContext, file_path: str) -> ReadFileOutput:
191
196
  with open(file_path, "r", encoding="utf-8") as f:
192
197
  content = f.read()
193
198
  return ReadFileOutput(content=content)
194
- except Exception as exc:
199
+ except Exception:
195
200
  return ReadFileOutput(content="FILE NOT FOUND")
196
201
 
197
202
 
@@ -200,12 +205,12 @@ class MatchInfo(BaseModel):
200
205
  line_number: int | None
201
206
  line_content: str | None
202
207
 
208
+
203
209
  class GrepOutput(BaseModel):
204
210
  matches: List[MatchInfo]
205
211
 
206
- def _grep(
207
- context: RunContext, search_string: str, directory: str = "."
208
- ) -> GrepOutput:
212
+
213
+ def _grep(context: RunContext, search_string: str, directory: str = ".") -> GrepOutput:
209
214
  matches: List[MatchInfo] = []
210
215
  directory = os.path.abspath(directory)
211
216
  console.print(
@@ -229,11 +234,13 @@ def _grep(
229
234
  with open(file_path, "r", encoding="utf-8", errors="ignore") as fh:
230
235
  for line_number, line_content in enumerate(fh, 1):
231
236
  if search_string in line_content:
232
- match_info = MatchInfo(**{
233
- "file_path": file_path,
234
- "line_number": line_number,
235
- "line_content": line_content.strip(),
236
- })
237
+ match_info = MatchInfo(
238
+ **{
239
+ "file_path": file_path,
240
+ "line_number": line_number,
241
+ "line_content": line_content.strip(),
242
+ }
243
+ )
237
244
  matches.append(match_info)
238
245
  # console.print(
239
246
  # f"[green]Match:[/green] {file_path}:{line_number} - {line_content.strip()}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.83
3
+ Version: 0.0.85
4
4
  Summary: Code generation agent
5
5
  Author: Michael Pfaffenberger
6
6
  License: MIT
@@ -0,0 +1,30 @@
1
+ code_puppy/__init__.py,sha256=CWH46ZAmJRmHAbOiAhG07OrWYEcEt4yvDTkZU341Wag,169
2
+ code_puppy/agent.py,sha256=7_1FpGPnw8U632OXP0hLmFIozfVvllF491q8gCpaa8c,3284
3
+ code_puppy/agent_prompts.py,sha256=wTah_TvakCMhkb_KwuWCsw4_UR1QsjTZeOT1I8at_nc,6593
4
+ code_puppy/config.py,sha256=r5nw5ChOP8xd_K5yo8U5OtO2gy2bFhARiyNtDp1JrwQ,5013
5
+ code_puppy/main.py,sha256=qtBokZZfPQRGF91KNl_n_ywutONwF2f-zTwmt_ROsEU,11080
6
+ code_puppy/message_history_processor.py,sha256=o5RNa-i7q_btYWJVaJFhRB2np58kObIDhQW2hZSRiKw,9244
7
+ code_puppy/model_factory.py,sha256=HXuFHNkVjkCcorAd3ScFmSvBILO932UTq6OmNAqisT8,10898
8
+ code_puppy/models.json,sha256=jr0-LW87aJS79GosVwoZdHeeq5eflPzgdPoMbcqpVA8,2728
9
+ code_puppy/state_management.py,sha256=JkTkmq6f9rl_RHPDoBqJvbAzgaMsIkJf-k38ragItIo,1692
10
+ code_puppy/summarization_agent.py,sha256=jHUQe6iYJsMT0ywEwO7CrhUIKEamO5imhAsDwvNuvow,2684
11
+ code_puppy/version_checker.py,sha256=aRGulzuY4C4CdFvU1rITduyL-1xTFsn4GiD1uSfOl_Y,396
12
+ code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
13
+ code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
14
+ code_puppy/command_line/meta_command_handler.py,sha256=L7qP2g0Faz0V7bMH4YK3s03OWWuQFtK7Sh-Kt2zmmEQ,6182
15
+ code_puppy/command_line/model_picker_completion.py,sha256=NkyZZG7IhcVWSJ3ADytwCA5f8DpNeVs759Qtqs4fQtY,3733
16
+ code_puppy/command_line/motd.py,sha256=FoZsiVpXGF8WpAmEJX4O895W7MDuzCtNWvFAOShxUXY,1572
17
+ code_puppy/command_line/prompt_toolkit_completion.py,sha256=_gP0FIOgHDNHTTWLNL0XNzr6sO0ISe7Mec1uQNo9kcM,8337
18
+ code_puppy/command_line/utils.py,sha256=7eyxDHjPjPB9wGDJQQcXV_zOsGdYsFgI0SGCetVmTqE,1251
19
+ code_puppy/tools/__init__.py,sha256=WTHYIfRk2KMmk6o45TELpbB3GIiAm8s7GmfJ7Zy_tww,503
20
+ code_puppy/tools/command_runner.py,sha256=iYXAauCmntHgRcPOoMavslQ7oVFQhL0hYmjVUu6ezpk,14354
21
+ code_puppy/tools/common.py,sha256=M53zhiXZAmPdvi1Y_bzCxgvEmifOvRRJvYPARYRZqHw,2253
22
+ code_puppy/tools/file_modifications.py,sha256=BzQrGEacS2NZr2ru9N30x_Qd70JDudBKOAPO1XjBohg,13861
23
+ code_puppy/tools/file_operations.py,sha256=ypk4yL90LDSVRr0xyWafttzt956J_nXhhenCXhOOit8,11326
24
+ code_puppy/tools/ts_code_map.py,sha256=o-u8p5vsYwitfDtVEoPS-7MwWn2xHzwtIQLo1_WMhQs,17647
25
+ code_puppy-0.0.85.data/data/code_puppy/models.json,sha256=jr0-LW87aJS79GosVwoZdHeeq5eflPzgdPoMbcqpVA8,2728
26
+ code_puppy-0.0.85.dist-info/METADATA,sha256=mGczXN9Szot3pRImK59UT27eZq4apINNkrZxBTzUY6w,6351
27
+ code_puppy-0.0.85.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
+ code_puppy-0.0.85.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
29
+ code_puppy-0.0.85.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
30
+ code_puppy-0.0.85.dist-info/RECORD,,