code-puppy 0.0.152__py3-none-any.whl → 0.0.154__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.
code_puppy/config.py CHANGED
@@ -76,7 +76,7 @@ def get_allow_recursion() -> bool:
76
76
  """
77
77
  val = get_value("allow_recursion")
78
78
  if val is None:
79
- return False # Default to False for safety
79
+ return True # Default to False for safety
80
80
  return str(val).lower() in ("1", "true", "yes", "on")
81
81
 
82
82
 
@@ -26,14 +26,6 @@ from code_puppy.summarization_agent import run_summarization_sync
26
26
  # Default is 50000 but can be customized in ~/.code_puppy/puppy.cfg
27
27
 
28
28
 
29
- def estimate_token_count(text: str) -> int:
30
- """
31
- Simple token estimation using len(message) - 4.
32
- This replaces tiktoken with a much simpler approach.
33
- """
34
- return max(1, len(text) - 4)
35
-
36
-
37
29
  def stringify_message_part(part) -> str:
38
30
  """
39
31
  Convert a message part to a string representation for token estimation or other uses.
@@ -84,9 +76,9 @@ def estimate_tokens_for_message(message: ModelMessage) -> int:
84
76
  for part in message.parts:
85
77
  part_str = stringify_message_part(part)
86
78
  if part_str:
87
- total_tokens += estimate_token_count(part_str)
79
+ total_tokens += len(part_str)
88
80
 
89
- return max(1, total_tokens)
81
+ return int(max(1, total_tokens) / 4)
90
82
 
91
83
 
92
84
  def filter_huge_messages(messages: List[ModelMessage]) -> List[ModelMessage]:
@@ -1,6 +1,7 @@
1
1
  # file_operations.py
2
2
 
3
3
  import os
4
+ import tempfile
4
5
  from typing import List
5
6
 
6
7
  from pydantic import BaseModel, conint
@@ -46,7 +47,7 @@ class ListedFile(BaseModel):
46
47
 
47
48
 
48
49
  class ListFileOutput(BaseModel):
49
- files: List[ListedFile]
50
+ content: str
50
51
  error: str | None = None
51
52
 
52
53
 
@@ -125,116 +126,182 @@ def is_project_directory(directory):
125
126
  def _list_files(
126
127
  context: RunContext, directory: str = ".", recursive: bool = True
127
128
  ) -> ListFileOutput:
128
-
129
+ import subprocess
130
+ import tempfile
131
+ import shutil
132
+ import sys
133
+
129
134
  results = []
130
135
  directory = os.path.abspath(directory)
131
136
 
132
- # Generate group_id for this tool execution
133
- group_id = generate_group_id("list_files", directory)
134
-
135
- emit_info(
136
- "\n[bold white on blue] DIRECTORY LISTING [/bold white on blue]",
137
- message_group=group_id,
138
- )
139
- emit_info(
140
- f"\U0001f4c2 [bold cyan]{directory}[/bold cyan] [dim](recursive={recursive})[/dim]\n",
141
- message_group=group_id,
142
- )
143
- emit_divider(message_group=group_id)
137
+ # Build string representation
138
+ output_lines = []
139
+
140
+ directory_listing_header = "\n[bold white on blue] DIRECTORY LISTING [/bold white on blue]"
141
+ output_lines.append(directory_listing_header)
142
+
143
+ directory_info = f"\U0001f4c2 [bold cyan]{directory}[/bold cyan] [dim](recursive={recursive})[/dim]\n"
144
+ output_lines.append(directory_info)
145
+
146
+ divider = "[dim]" + "─" * 100 + "\n" + "[/dim]"
147
+ output_lines.append(divider)
148
+
144
149
  if not os.path.exists(directory):
145
- emit_error(f"Directory '{directory}' does not exist", message_group=group_id)
146
- emit_divider(message_group=group_id)
147
- return ListFileOutput(
148
- files=[ListedFile(path=None, type=None, full_path=None, depth=None)]
149
- )
150
+ error_msg = f"[red bold]Error:[/red bold] Directory '{directory}' does not exist"
151
+ output_lines.append(error_msg)
152
+
153
+ output_lines.append(divider)
154
+ return ListFileOutput(content="\n".join(output_lines))
150
155
  if not os.path.isdir(directory):
151
- emit_error(f"'{directory}' is not a directory", message_group=group_id)
152
- emit_divider(message_group=group_id)
153
- return ListFileOutput(
154
- files=[ListedFile(path=None, type=None, full_path=None, depth=None)]
155
- )
156
+ error_msg = f"[red bold]Error:[/red bold] '{directory}' is not a directory"
157
+ output_lines.append(error_msg)
158
+
159
+ output_lines.append(divider)
160
+ return ListFileOutput(content="\n".join(output_lines))
156
161
 
157
162
  # Smart home directory detection - auto-limit recursion for performance
158
163
  # But allow recursion in tests (when context=None) or when explicitly requested
159
164
  if context is not None and is_likely_home_directory(directory) and recursive:
160
165
  if not is_project_directory(directory):
161
- emit_warning(
162
- "🏠 Detected home directory - limiting to non-recursive listing for performance",
163
- message_group=group_id,
164
- )
165
- emit_info(
166
- f"💡 To force recursive listing in home directory, use list_files('{directory}', recursive=True) explicitly",
167
- message_group=group_id,
168
- )
166
+ warning_msg = "[yellow bold]Warning:[/yellow bold] 🏠 Detected home directory - limiting to non-recursive listing for performance"
167
+ output_lines.append(warning_msg)
168
+
169
+ info_msg = f"[dim]💡 To force recursive listing in home directory, use list_files('{directory}', recursive=True) explicitly[/dim]"
170
+ output_lines.append(info_msg)
169
171
  recursive = False
170
- folder_structure = {}
171
- file_list = []
172
- for root, dirs, files in os.walk(directory):
173
- # Filter out ignored directories
174
- dirs[:] = [d for d in dirs if not should_ignore_path(os.path.join(root, d))]
172
+
173
+ # Create a temporary ignore file with our ignore patterns
174
+ ignore_file = None
175
+ try:
176
+ # Find ripgrep executable - first check system PATH, then virtual environment
177
+ rg_path = shutil.which("rg")
178
+ if not rg_path:
179
+ # Try to find it in the virtual environment
180
+ # Use sys.executable to determine the Python environment path
181
+ python_dir = os.path.dirname(sys.executable)
182
+ # Check both 'bin' (Unix) and 'Scripts' (Windows) directories
183
+ for rg_dir in ["bin", "Scripts"]:
184
+ venv_rg_path = os.path.join(python_dir, "rg")
185
+ if os.path.exists(venv_rg_path):
186
+ rg_path = venv_rg_path
187
+ break
188
+ # Also check with .exe extension for Windows
189
+ venv_rg_exe_path = os.path.join(python_dir, "rg.exe")
190
+ if os.path.exists(venv_rg_exe_path):
191
+ rg_path = venv_rg_exe_path
192
+ break
175
193
 
176
- rel_path = os.path.relpath(root, directory)
177
- depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
178
- if rel_path == ".":
179
- rel_path = ""
194
+ if not rg_path:
195
+ error_msg = f"[red bold]Error:[/red bold] ripgrep (rg) not found. Please install ripgrep to use this tool."
196
+ output_lines.append(error_msg)
197
+ return ListFileOutput(content="\n".join(output_lines))
198
+
199
+ # Build command for ripgrep --files
200
+ cmd = [rg_path, "--files"]
180
201
 
181
- # Add directory entry for subdirectories (except root)
182
- if rel_path:
183
- dir_path = os.path.join(directory, rel_path)
184
- results.append(
185
- ListedFile(
186
- path=rel_path,
187
- type="directory",
188
- size=0,
189
- full_path=dir_path,
190
- depth=depth,
191
- )
192
- )
193
- folder_structure[rel_path] = {
194
- "path": rel_path,
195
- "depth": depth,
196
- "full_path": dir_path,
197
- }
198
- else: # Root directory - add both directories and files
199
- # Add directories
200
- for d in dirs:
201
- dir_path = os.path.join(root, d)
202
+ # For non-recursive mode, we'll limit depth after getting results
203
+ if not recursive:
204
+ cmd.extend(["--max-depth", "1"])
205
+
206
+ # Add ignore patterns to the command via a temporary file
207
+ from code_puppy.tools.common import IGNORE_PATTERNS
208
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ignore') as f:
209
+ ignore_file = f.name
210
+ for pattern in IGNORE_PATTERNS:
211
+ f.write(f"{pattern}\n")
212
+
213
+ cmd.extend(["--ignore-file", ignore_file])
214
+ cmd.append(directory)
215
+
216
+ # Run ripgrep to get file listing
217
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
218
+
219
+ # Process the output lines
220
+ files = result.stdout.strip().split('\n') if result.stdout.strip() else []
221
+
222
+ # Create ListedFile objects with metadata
223
+ for file_path in files:
224
+ if not file_path: # Skip empty lines
225
+ continue
226
+
227
+ full_path = os.path.join(directory, file_path)
228
+
229
+ # Skip if file doesn't exist (though it should)
230
+ if not os.path.exists(full_path):
231
+ continue
232
+
233
+ # For non-recursive mode, skip files in subdirectories
234
+ if not recursive and os.sep in file_path:
235
+ continue
236
+
237
+ # Check if path is a file or directory
238
+ if os.path.isfile(full_path):
239
+ entry_type = "file"
240
+ size = os.path.getsize(full_path)
241
+ elif os.path.isdir(full_path):
242
+ entry_type = "directory"
243
+ size = 0
244
+ else:
245
+ # Skip if it's neither a file nor directory
246
+ continue
247
+
248
+ try:
249
+ # Get stats for the entry
250
+ stat_info = os.stat(full_path)
251
+ actual_size = stat_info.st_size
252
+
253
+ # For files, we use the actual size; for directories, we keep size=0
254
+ if entry_type == "file":
255
+ size = actual_size
256
+
257
+ # Calculate depth
258
+ depth = file_path.count(os.sep)
259
+
260
+ # Add directory entries if needed for files
261
+ if entry_type == "file":
262
+ dir_path = os.path.dirname(file_path)
263
+ if dir_path:
264
+ # Add directory path components if they don't exist
265
+ path_parts = dir_path.split(os.sep)
266
+ for i in range(len(path_parts)):
267
+ partial_path = os.sep.join(path_parts[:i+1])
268
+ # Check if we already added this directory
269
+ if not any(f.path == partial_path and f.type == "directory" for f in results):
270
+ results.append(
271
+ ListedFile(
272
+ path=partial_path,
273
+ type="directory",
274
+ size=0,
275
+ full_path=os.path.join(directory, partial_path),
276
+ depth=partial_path.count(os.sep),
277
+ )
278
+ )
279
+
280
+ # Add the entry (file or directory)
202
281
  results.append(
203
282
  ListedFile(
204
- path=d,
205
- type="directory",
206
- size=0,
207
- full_path=dir_path,
283
+ path=file_path,
284
+ type=entry_type,
285
+ size=size,
286
+ full_path=full_path,
208
287
  depth=depth,
209
288
  )
210
289
  )
211
- folder_structure[d] = {
212
- "path": d,
213
- "depth": depth,
214
- "full_path": dir_path,
215
- }
216
-
217
- # Add files to results
218
- for file in files:
219
- file_path = os.path.join(root, file)
220
- if should_ignore_path(file_path):
290
+ except (FileNotFoundError, PermissionError, OSError):
291
+ # Skip files we can't access
221
292
  continue
222
- rel_file_path = os.path.join(rel_path, file) if rel_path else file
223
- try:
224
- size = os.path.getsize(file_path)
225
- file_info = {
226
- "path": rel_file_path,
227
- "type": "file",
228
- "size": size,
229
- "full_path": file_path,
230
- "depth": depth,
231
- }
232
- results.append(ListedFile(**file_info))
233
- file_list.append(file_info)
234
- except (FileNotFoundError, PermissionError):
235
- continue
236
- if not recursive:
237
- break
293
+ except subprocess.TimeoutExpired:
294
+ error_msg = f"[red bold]Error:[/red bold] List files command timed out after 30 seconds"
295
+ output_lines.append(error_msg)
296
+ return ListFileOutput(content="\n".join(output_lines))
297
+ except Exception as e:
298
+ error_msg = f"[red bold]Error:[/red bold] Error during list files operation: {e}"
299
+ output_lines.append(error_msg)
300
+ return ListFileOutput(content="\n".join(output_lines))
301
+ finally:
302
+ # Clean up the temporary ignore file
303
+ if ignore_file and os.path.exists(ignore_file):
304
+ os.unlink(ignore_file)
238
305
 
239
306
  def format_size(size_bytes):
240
307
  if size_bytes < 1024:
@@ -275,20 +342,31 @@ def _list_files(
275
342
  else:
276
343
  return "\U0001f4c4"
277
344
 
278
- if results:
279
- files = sorted([f for f in results if f.type == "file"], key=lambda x: x.path)
280
- emit_info(
281
- f"\U0001f4c1 [bold blue]{os.path.basename(directory) or directory}[/bold blue]",
282
- message_group=group_id,
283
- )
345
+ dir_count = sum(1 for item in results if item.type == "directory")
346
+ file_count = sum(1 for item in results if item.type == "file")
347
+ total_size = sum(item.size for item in results if item.type == "file")
348
+
349
+ # Build the directory header section
350
+ dir_name = os.path.basename(directory) or directory
351
+ dir_header = f"\U0001f4c1 [bold blue]{dir_name}[/bold blue]"
352
+ output_lines.append(dir_header)
353
+
354
+ # Sort all items by path for consistent display
284
355
  all_items = sorted(results, key=lambda x: x.path)
356
+
357
+ # Build file and directory tree representation
285
358
  parent_dirs_with_content = set()
286
- for i, item in enumerate(all_items):
359
+ for item in all_items:
360
+ # Skip root directory entries with no path
287
361
  if item.type == "directory" and not item.path:
288
362
  continue
363
+
364
+ # Track parent directories that contain files/dirs
289
365
  if os.sep in item.path:
290
366
  parent_path = os.path.dirname(item.path)
291
367
  parent_dirs_with_content.add(parent_path)
368
+
369
+ # Calculate indentation depth based on path separators
292
370
  depth = item.path.count(os.sep) + 1 if item.path else 0
293
371
  prefix = ""
294
372
  for d in range(depth):
@@ -296,29 +374,32 @@ def _list_files(
296
374
  prefix += "\u2514\u2500\u2500 "
297
375
  else:
298
376
  prefix += " "
377
+
378
+ # Get the display name (basename) of the item
299
379
  name = os.path.basename(item.path) or item.path
380
+
381
+ # Add directory or file line with appropriate formatting
300
382
  if item.type == "directory":
301
- emit_info(
302
- f"{prefix}\U0001f4c1 [bold blue]{name}/[/bold blue]",
303
- message_group=group_id,
304
- )
383
+ dir_line = f"{prefix}\U0001f4c1 [bold blue]{name}/[/bold blue]"
384
+ output_lines.append(dir_line)
305
385
  else:
306
386
  icon = get_file_icon(item.path)
307
387
  size_str = format_size(item.size)
308
- emit_info(
309
- f"{prefix}{icon} [green]{name}[/green] [dim]({size_str})[/dim]",
310
- message_group=group_id,
311
- )
312
- dir_count = sum(1 for item in results if item.type == "directory")
313
- file_count = sum(1 for item in results if item.type == "file")
314
- total_size = sum(item.size for item in results if item.type == "file")
315
- emit_info("\n[bold cyan]Summary:[/bold cyan]", message_group=group_id)
316
- emit_info(
317
- f"\U0001f4c1 [blue]{dir_count} directories[/blue], \U0001f4c4 [green]{file_count} files[/green] [dim]({format_size(total_size)} total)[/dim]",
318
- message_group=group_id,
319
- )
320
- emit_divider(message_group=group_id)
321
- return ListFileOutput(files=results)
388
+ file_line = f"{prefix}{icon} [green]{name}[/green] [dim]({size_str})[/dim]"
389
+ output_lines.append(file_line)
390
+
391
+ # Add summary information
392
+ summary_header = "\n[bold cyan]Summary:[/bold cyan]"
393
+ output_lines.append(summary_header)
394
+
395
+ summary_line = f"\U0001f4c1 [blue]{dir_count} directories[/blue], \U0001f4c4 [green]{file_count} files[/green] [dim]({format_size(total_size)} total)[/dim]"
396
+ output_lines.append(summary_line)
397
+
398
+ final_divider = "[dim]" + "─" * 100 + "\n" + "[/dim]"
399
+ output_lines.append(final_divider)
400
+
401
+ # Return both the content string and the list of ListedFile objects
402
+ return ListFileOutput(content="\n".join(output_lines), files=results)
322
403
 
323
404
 
324
405
  def _read_file(
@@ -530,25 +611,18 @@ def register_list_files(agent):
530
611
  Defaults to True.
531
612
 
532
613
  Returns:
533
- ListFileOutput: A structured response containing:
534
- - files (List[ListedFile]): List of files and directories found, where
535
- each ListedFile contains:
536
- - path (str | None): Relative path from the listing directory
537
- - type (str | None): "file" or "directory"
538
- - size (int): File size in bytes (0 for directories)
539
- - full_path (str | None): Absolute path to the item
540
- - depth (int | None): Nesting depth from the root directory
614
+ ListFileOutput: A response containing:
615
+ - content (str): String representation of the directory listing
541
616
  - error (str | None): Error message if listing failed
542
617
 
543
618
  Examples:
544
619
  >>> # List current directory
545
620
  >>> result = list_files(ctx)
546
- >>> for file in result.files:
547
- ... print(f"{file.type}: {file.path} ({file.size} bytes)")
621
+ >>> print(result.content)
548
622
 
549
623
  >>> # List specific directory non-recursively
550
624
  >>> result = list_files(ctx, "/path/to/project", recursive=False)
551
- >>> print(f"Found {len(result.files)} items")
625
+ >>> print(result.content)
552
626
 
553
627
  >>> # Handle potential errors
554
628
  >>> result = list_files(ctx, "/nonexistent/path")
@@ -566,9 +640,13 @@ def register_list_files(agent):
566
640
  warning = "Recursion disabled globally for list_files - returning non-recursive results"
567
641
  recursive = False
568
642
  result = _list_files(context, directory, recursive)
643
+
644
+ # Emit the content directly to ensure it's displayed to the user
645
+ emit_info(result.content, message_group=generate_group_id("list_files", directory))
646
+
569
647
  if warning:
570
648
  result.error = warning
571
- return result
649
+ return result
572
650
 
573
651
 
574
652
  def register_read_file(agent):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.152
3
+ Version: 0.0.154
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -2,10 +2,10 @@ code_puppy/__init__.py,sha256=ehbM1-wMjNmOXk_DBhhJECFyBv2dRHwwo7ucjHeM68E,107
2
2
  code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
3
3
  code_puppy/agent.py,sha256=rWqJ2849HSrWPX-IzI31v2sH6_j5pnBRfuum3yxUNt8,7256
4
4
  code_puppy/callbacks.py,sha256=6wYB6K_fGSCkKKEFaYOYkJT45WaV5W_NhUIzcvVH_nU,5060
5
- code_puppy/config.py,sha256=-RaEpG-k5ObjaSYY8SNpVI1dXzujZxKWj-vlLRTsyHY,16166
5
+ code_puppy/config.py,sha256=oJCVEp6cK4u4ynKe-1TGlpB_TW1Q4pp14oFhtGEWhbk,16165
6
6
  code_puppy/http_utils.py,sha256=BAvt4hed7fVMXglA7eS9gOb08h2YTuOyai6VmQq09fg,3432
7
7
  code_puppy/main.py,sha256=Vv5HSJnkgZhCvvOoXrJ2zqM5P-i47-RcYAU00Z1Pfx0,21733
8
- code_puppy/message_history_processor.py,sha256=O2rKp7W6YeIg93W8b0XySTUEQgIZm0f_06--_kzHugM,16145
8
+ code_puppy/message_history_processor.py,sha256=aV-vcRcOQJPZPlrokB4CaLMxEU3Y4nDiabb9Ov_sJeU,15933
9
9
  code_puppy/model_factory.py,sha256=-_0kJ7qYmRTDk0v3mfp-UrYf0IaDLyG8IMBweDECwJg,11917
10
10
  code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
11
11
  code_puppy/reopenable_async_client.py,sha256=4UJRaMp5np8cbef9F0zKQ7TPKOfyf5U-Kv-0zYUWDho,8274
@@ -82,7 +82,7 @@ code_puppy/tools/agent_tools.py,sha256=tG11PMmjuU-pYG1MFCgqsYiC1Q8C-zPsitAYXxl3m
82
82
  code_puppy/tools/command_runner.py,sha256=GVNsgwjTFD9tkNlycgMNmMoVPdmMkZkbAcHH5y6iMww,26070
83
83
  code_puppy/tools/common.py,sha256=pL-9xcRs3rxU7Fl9X9EUgbDp2-csh2LLJ5DHH_KAHKY,10596
84
84
  code_puppy/tools/file_modifications.py,sha256=oeNEQItqwMhGOeEN2TzGR7TjmgLsfFFdPaVMzWbfXIQ,30398
85
- code_puppy/tools/file_operations.py,sha256=rH0pOH0u7ZsQ7bUxaIH38nAiYF9RHmtR_mBNFYBe4ws,27334
85
+ code_puppy/tools/file_operations.py,sha256=IxXEzNsotz3DHUV2YfslNf1_gSJrrOMUPEpI2b3TFw0,31093
86
86
  code_puppy/tools/token_check.py,sha256=cNrGOOKahXsnWsvh5xnMkL1NS9FjYur9QIRZGQFW-pE,1189
87
87
  code_puppy/tools/tools_content.py,sha256=bsBqW-ppd1XNAS_g50B3UHDQBWEALC1UneH6-afz1zo,2365
88
88
  code_puppy/tui/__init__.py,sha256=XesAxIn32zLPOmvpR2wIDxDAnnJr81a5pBJB4cZp1Xs,321
@@ -126,9 +126,9 @@ code_puppy/tui/tests/test_sidebar_history_navigation.py,sha256=JGiyua8A2B8dLfwiE
126
126
  code_puppy/tui/tests/test_status_bar.py,sha256=nYT_FZGdmqnnbn6o0ZuOkLtNUtJzLSmtX8P72liQ5Vo,1797
127
127
  code_puppy/tui/tests/test_timestamped_history.py,sha256=nVXt9hExZZ_8MFP-AZj4L4bB_1Eo_mc-ZhVICzTuw3I,1799
128
128
  code_puppy/tui/tests/test_tools.py,sha256=kgzzAkK4r0DPzQwHHD4cePpVNgrHor6cFr05Pg6DBWg,2687
129
- code_puppy-0.0.152.data/data/code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
130
- code_puppy-0.0.152.dist-info/METADATA,sha256=uXmPOqNxbL-NC_0hEvvn4ns4jX3GoyGOSmHMrS5QdBw,19516
131
- code_puppy-0.0.152.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
132
- code_puppy-0.0.152.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
133
- code_puppy-0.0.152.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
134
- code_puppy-0.0.152.dist-info/RECORD,,
129
+ code_puppy-0.0.154.data/data/code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
130
+ code_puppy-0.0.154.dist-info/METADATA,sha256=r4J6tLEqJwVVG7j5eMrQRI1zMENfwZKdNo2KrOI_pBQ,19516
131
+ code_puppy-0.0.154.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
132
+ code_puppy-0.0.154.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
133
+ code_puppy-0.0.154.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
134
+ code_puppy-0.0.154.dist-info/RECORD,,