code-puppy 0.0.122__py3-none-any.whl → 0.0.124__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/callbacks.py +18 -0
- code_puppy/command_line/command_handler.py +35 -9
- code_puppy/config.py +56 -10
- code_puppy/main.py +7 -3
- code_puppy/message_history_processor.py +54 -7
- code_puppy/tools/command_runner.py +3 -1
- code_puppy/tools/common.py +48 -0
- code_puppy/tools/file_modifications.py +3 -0
- code_puppy/tui/app.py +8 -2
- code_puppy/tui/components/status_bar.py +4 -4
- code_puppy/tui/screens/settings.py +53 -18
- {code_puppy-0.0.122.dist-info → code_puppy-0.0.124.dist-info}/METADATA +1 -1
- {code_puppy-0.0.122.dist-info → code_puppy-0.0.124.dist-info}/RECORD +17 -17
- {code_puppy-0.0.122.data → code_puppy-0.0.124.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.122.dist-info → code_puppy-0.0.124.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.122.dist-info → code_puppy-0.0.124.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.122.dist-info → code_puppy-0.0.124.dist-info}/licenses/LICENSE +0 -0
code_puppy/callbacks.py
CHANGED
|
@@ -9,6 +9,9 @@ PhaseType = Literal[
|
|
|
9
9
|
"invoke_agent",
|
|
10
10
|
"agent_exception",
|
|
11
11
|
"version_check",
|
|
12
|
+
"edit_file",
|
|
13
|
+
"delete_file",
|
|
14
|
+
"run_shell_command",
|
|
12
15
|
"load_model_config",
|
|
13
16
|
"load_prompt",
|
|
14
17
|
]
|
|
@@ -20,6 +23,9 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
|
|
|
20
23
|
"invoke_agent": [],
|
|
21
24
|
"agent_exception": [],
|
|
22
25
|
"version_check": [],
|
|
26
|
+
"edit_file": [],
|
|
27
|
+
"delete_file": [],
|
|
28
|
+
"run_shell_command": [],
|
|
23
29
|
"load_model_config": [],
|
|
24
30
|
"load_prompt": [],
|
|
25
31
|
}
|
|
@@ -148,5 +154,17 @@ def on_load_model_config(*args, **kwargs) -> List[Any]:
|
|
|
148
154
|
return _trigger_callbacks_sync("load_model_config", *args, **kwargs)
|
|
149
155
|
|
|
150
156
|
|
|
157
|
+
def on_edit_file(*args, **kwargs) -> Any:
|
|
158
|
+
return _trigger_callbacks_sync("edit_file", *args, **kwargs)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def on_delete_file(*args, **kwargs) -> Any:
|
|
162
|
+
return _trigger_callbacks_sync("delete_file", *args, **kwargs)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def on_run_shell_command(*args, **kwargs) -> Any:
|
|
166
|
+
return _trigger_callbacks_sync("run_shell_command", *args, **kwargs)
|
|
167
|
+
|
|
168
|
+
|
|
151
169
|
def on_load_prompt():
|
|
152
170
|
return _trigger_callbacks_sync("load_prompt")
|
|
@@ -22,7 +22,7 @@ COMMANDS_HELP = """
|
|
|
22
22
|
/compact Summarize and compact current chat history
|
|
23
23
|
/dump_context <name> Save current message history to file
|
|
24
24
|
/load_context <name> Load message history from file
|
|
25
|
-
/set Set puppy config key-values (e.g., /set yolo_mode true)
|
|
25
|
+
/set Set puppy config key-values (e.g., /set yolo_mode true, /set compaction_strategy truncation)
|
|
26
26
|
/tools Show available tools and capabilities
|
|
27
27
|
/<unknown> Show unknown command warning
|
|
28
28
|
"""
|
|
@@ -47,9 +47,12 @@ def handle_command(command: str):
|
|
|
47
47
|
return True
|
|
48
48
|
|
|
49
49
|
if command.strip().startswith("/compact"):
|
|
50
|
+
from code_puppy.config import get_compaction_strategy
|
|
50
51
|
from code_puppy.message_history_processor import (
|
|
51
52
|
estimate_tokens_for_message,
|
|
52
53
|
summarize_messages,
|
|
54
|
+
truncation,
|
|
55
|
+
get_protected_token_count,
|
|
53
56
|
)
|
|
54
57
|
from code_puppy.messaging import (
|
|
55
58
|
emit_error,
|
|
@@ -66,13 +69,23 @@ def handle_command(command: str):
|
|
|
66
69
|
return True
|
|
67
70
|
|
|
68
71
|
before_tokens = sum(estimate_tokens_for_message(m) for m in history)
|
|
72
|
+
compaction_strategy = get_compaction_strategy()
|
|
69
73
|
emit_info(
|
|
70
|
-
f"🤔 Compacting {len(history)} messages... (~{before_tokens} tokens)"
|
|
74
|
+
f"🤔 Compacting {len(history)} messages using {compaction_strategy} strategy... (~{before_tokens} tokens)"
|
|
71
75
|
)
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
if compaction_strategy == "truncation":
|
|
78
|
+
protected_tokens = get_protected_token_count()
|
|
79
|
+
compacted = truncation(history, protected_tokens)
|
|
80
|
+
summarized_messages = [] # No summarization in truncation mode
|
|
81
|
+
else:
|
|
82
|
+
# Default to summarization
|
|
83
|
+
compacted, summarized_messages = summarize_messages(
|
|
84
|
+
history, with_protection=False
|
|
85
|
+
)
|
|
86
|
+
|
|
74
87
|
if not compacted:
|
|
75
|
-
emit_error("
|
|
88
|
+
emit_error("Compaction failed. History unchanged.")
|
|
76
89
|
return True
|
|
77
90
|
|
|
78
91
|
set_message_history(compacted)
|
|
@@ -83,8 +96,14 @@ def handle_command(command: str):
|
|
|
83
96
|
if before_tokens > 0
|
|
84
97
|
else 0
|
|
85
98
|
)
|
|
99
|
+
|
|
100
|
+
strategy_info = (
|
|
101
|
+
f"using {compaction_strategy} strategy"
|
|
102
|
+
if compaction_strategy == "truncation"
|
|
103
|
+
else "via summarization"
|
|
104
|
+
)
|
|
86
105
|
emit_success(
|
|
87
|
-
f"✨ Done! History: {len(history)} → {len(compacted)} messages\n"
|
|
106
|
+
f"✨ Done! History: {len(history)} → {len(compacted)} messages {strategy_info}\n"
|
|
88
107
|
f"🏦 Tokens: {before_tokens:,} → {after_tokens:,} ({reduction_pct:.1f}% reduction)"
|
|
89
108
|
)
|
|
90
109
|
return True
|
|
@@ -119,16 +138,19 @@ def handle_command(command: str):
|
|
|
119
138
|
get_owner_name,
|
|
120
139
|
get_protected_token_count,
|
|
121
140
|
get_puppy_name,
|
|
122
|
-
|
|
141
|
+
get_compaction_threshold,
|
|
123
142
|
get_yolo_mode,
|
|
124
143
|
)
|
|
125
144
|
|
|
145
|
+
from code_puppy.config import get_compaction_strategy
|
|
146
|
+
|
|
126
147
|
puppy_name = get_puppy_name()
|
|
127
148
|
owner_name = get_owner_name()
|
|
128
149
|
model = get_active_model()
|
|
129
150
|
yolo_mode = get_yolo_mode()
|
|
130
151
|
protected_tokens = get_protected_token_count()
|
|
131
|
-
|
|
152
|
+
compaction_threshold = get_compaction_threshold()
|
|
153
|
+
compaction_strategy = get_compaction_strategy()
|
|
132
154
|
|
|
133
155
|
status_msg = f"""[bold magenta]🐶 Puppy Status[/bold magenta]
|
|
134
156
|
|
|
@@ -137,7 +159,8 @@ def handle_command(command: str):
|
|
|
137
159
|
[bold]model:[/bold] [green]{model}[/green]
|
|
138
160
|
[bold]YOLO_MODE:[/bold] {"[red]ON[/red]" if yolo_mode else "[yellow]off[/yellow]"}
|
|
139
161
|
[bold]protected_tokens:[/bold] [cyan]{protected_tokens:,}[/cyan] recent tokens preserved
|
|
140
|
-
[bold]
|
|
162
|
+
[bold]compaction_threshold:[/bold] [cyan]{compaction_threshold:.1%}[/cyan] context usage triggers compaction
|
|
163
|
+
[bold]compaction_strategy:[/bold] [cyan]{compaction_strategy}[/cyan] (summarization or truncation)
|
|
141
164
|
|
|
142
165
|
"""
|
|
143
166
|
emit_info(status_msg)
|
|
@@ -162,8 +185,11 @@ def handle_command(command: str):
|
|
|
162
185
|
key = tokens[1]
|
|
163
186
|
value = ""
|
|
164
187
|
else:
|
|
188
|
+
config_keys = get_config_keys()
|
|
189
|
+
if "compaction_strategy" not in config_keys:
|
|
190
|
+
config_keys.append("compaction_strategy")
|
|
165
191
|
emit_warning(
|
|
166
|
-
f"Usage: /set KEY=VALUE or /set KEY VALUE\nConfig keys: {', '.join(
|
|
192
|
+
f"Usage: /set KEY=VALUE or /set KEY VALUE\nConfig keys: {', '.join(config_keys)}\n[dim]Note: compaction_strategy can be 'summarization' or 'truncation'[/dim]"
|
|
167
193
|
)
|
|
168
194
|
return True
|
|
169
195
|
if key:
|
code_puppy/config.py
CHANGED
|
@@ -68,13 +68,33 @@ def get_owner_name():
|
|
|
68
68
|
# using get_protected_token_count() and get_summarization_threshold()
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def get_model_context_length() -> int:
|
|
72
|
+
"""
|
|
73
|
+
Get the context length for the currently configured model from models.json
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
from code_puppy.model_factory import ModelFactory
|
|
77
|
+
|
|
78
|
+
model_configs = ModelFactory.load_config()
|
|
79
|
+
model_name = get_model_name()
|
|
80
|
+
|
|
81
|
+
# Get context length from model config
|
|
82
|
+
model_config = model_configs.get(model_name, {})
|
|
83
|
+
context_length = model_config.get("context_length", 128000) # Default value
|
|
84
|
+
|
|
85
|
+
return int(context_length)
|
|
86
|
+
except Exception:
|
|
87
|
+
# Fallback to default context length if anything goes wrong
|
|
88
|
+
return 128000
|
|
89
|
+
|
|
90
|
+
|
|
71
91
|
# --- CONFIG SETTER STARTS HERE ---
|
|
72
92
|
def get_config_keys():
|
|
73
93
|
"""
|
|
74
94
|
Returns the list of all config keys currently in puppy.cfg,
|
|
75
|
-
plus certain preset expected keys (e.g. "yolo_mode", "model").
|
|
95
|
+
plus certain preset expected keys (e.g. "yolo_mode", "model", "compaction_strategy").
|
|
76
96
|
"""
|
|
77
|
-
default_keys = ["yolo_mode", "model"]
|
|
97
|
+
default_keys = ["yolo_mode", "model", "compaction_strategy"]
|
|
78
98
|
config = configparser.ConfigParser()
|
|
79
99
|
config.read(CONFIG_FILE)
|
|
80
100
|
keys = set(config[DEFAULT_SECTION].keys()) if DEFAULT_SECTION in config else set()
|
|
@@ -354,30 +374,56 @@ def get_protected_token_count():
|
|
|
354
374
|
This is the number of tokens in recent messages that won't be summarized.
|
|
355
375
|
Defaults to 50000 if unset or misconfigured.
|
|
356
376
|
Configurable by 'protected_token_count' key.
|
|
377
|
+
Enforces that protected tokens don't exceed 75% of model context length.
|
|
357
378
|
"""
|
|
358
379
|
val = get_value("protected_token_count")
|
|
359
380
|
try:
|
|
360
|
-
|
|
381
|
+
# Get the model context length to enforce the 75% limit
|
|
382
|
+
model_context_length = get_model_context_length()
|
|
383
|
+
max_protected_tokens = int(model_context_length * 0.75)
|
|
384
|
+
|
|
385
|
+
# Parse the configured value
|
|
386
|
+
configured_value = int(val) if val else 50000
|
|
387
|
+
|
|
388
|
+
# Apply constraints: minimum 1000, maximum 75% of context length
|
|
389
|
+
return max(1000, min(configured_value, max_protected_tokens))
|
|
361
390
|
except (ValueError, TypeError):
|
|
362
|
-
return
|
|
391
|
+
# If parsing fails, return a reasonable default that respects the 75% limit
|
|
392
|
+
model_context_length = get_model_context_length()
|
|
393
|
+
max_protected_tokens = int(model_context_length * 0.75)
|
|
394
|
+
return min(50000, max_protected_tokens)
|
|
363
395
|
|
|
364
396
|
|
|
365
|
-
def
|
|
397
|
+
def get_compaction_threshold():
|
|
366
398
|
"""
|
|
367
|
-
Returns the user-configured
|
|
368
|
-
This is the proportion of model context that triggers
|
|
399
|
+
Returns the user-configured compaction threshold as a float between 0.0 and 1.0.
|
|
400
|
+
This is the proportion of model context that triggers compaction.
|
|
369
401
|
Defaults to 0.85 (85%) if unset or misconfigured.
|
|
370
|
-
Configurable by '
|
|
402
|
+
Configurable by 'compaction_threshold' key.
|
|
371
403
|
"""
|
|
372
|
-
val = get_value("
|
|
404
|
+
val = get_value("compaction_threshold")
|
|
373
405
|
try:
|
|
374
406
|
threshold = float(val) if val else 0.85
|
|
375
407
|
# Clamp between reasonable bounds
|
|
376
|
-
return max(0.
|
|
408
|
+
return max(0.8, min(0.95, threshold))
|
|
377
409
|
except (ValueError, TypeError):
|
|
378
410
|
return 0.85
|
|
379
411
|
|
|
380
412
|
|
|
413
|
+
def get_compaction_strategy() -> str:
|
|
414
|
+
"""
|
|
415
|
+
Returns the user-configured compaction strategy.
|
|
416
|
+
Options are 'summarization' or 'truncation'.
|
|
417
|
+
Defaults to 'summarization' if not set or misconfigured.
|
|
418
|
+
Configurable by 'compaction_strategy' key.
|
|
419
|
+
"""
|
|
420
|
+
val = get_value("compaction_strategy")
|
|
421
|
+
if val and val.lower() in ["summarization", "truncation"]:
|
|
422
|
+
return val.lower()
|
|
423
|
+
# Default to summarization
|
|
424
|
+
return "summarization"
|
|
425
|
+
|
|
426
|
+
|
|
381
427
|
def save_command_to_history(command: str):
|
|
382
428
|
"""Save a command to the history file with an ISO format timestamp.
|
|
383
429
|
|
code_puppy/main.py
CHANGED
|
@@ -321,12 +321,14 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
321
321
|
initial_command, usage_limits=get_custom_usage_limits()
|
|
322
322
|
)
|
|
323
323
|
finally:
|
|
324
|
-
set_message_history(
|
|
324
|
+
set_message_history(
|
|
325
|
+
prune_interrupted_tool_calls(get_message_history())
|
|
326
|
+
)
|
|
325
327
|
|
|
326
328
|
agent_response = response.output
|
|
327
329
|
|
|
328
330
|
emit_system_message(
|
|
329
|
-
f"\n[bold purple]AGENT RESPONSE: [/bold purple]\n{agent_response
|
|
331
|
+
f"\n[bold purple]AGENT RESPONSE: [/bold purple]\n{agent_response}"
|
|
330
332
|
)
|
|
331
333
|
new_msgs = response.all_messages()
|
|
332
334
|
message_history_accumulator(new_msgs)
|
|
@@ -466,7 +468,9 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
466
468
|
usage_limits=get_custom_usage_limits(),
|
|
467
469
|
)
|
|
468
470
|
finally:
|
|
469
|
-
set_message_history(
|
|
471
|
+
set_message_history(
|
|
472
|
+
prune_interrupted_tool_calls(get_message_history())
|
|
473
|
+
)
|
|
470
474
|
|
|
471
475
|
# Create the task
|
|
472
476
|
agent_task = asyncio.create_task(run_agent_task())
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import queue
|
|
2
3
|
from typing import Any, List, Set, Tuple
|
|
3
4
|
|
|
4
5
|
import pydantic
|
|
@@ -7,7 +8,8 @@ from pydantic_ai.messages import ModelMessage, ModelRequest, TextPart, ToolCallP
|
|
|
7
8
|
from code_puppy.config import (
|
|
8
9
|
get_model_name,
|
|
9
10
|
get_protected_token_count,
|
|
10
|
-
|
|
11
|
+
get_compaction_threshold,
|
|
12
|
+
get_compaction_strategy,
|
|
11
13
|
)
|
|
12
14
|
from code_puppy.messaging import emit_error, emit_info, emit_warning
|
|
13
15
|
from code_puppy.model_factory import ModelFactory
|
|
@@ -87,6 +89,12 @@ def estimate_tokens_for_message(message: ModelMessage) -> int:
|
|
|
87
89
|
return max(1, total_tokens)
|
|
88
90
|
|
|
89
91
|
|
|
92
|
+
def filter_huge_messages(messages: List[ModelMessage]) -> List[ModelMessage]:
|
|
93
|
+
filtered = [m for m in messages if estimate_tokens_for_message(m) < 50000]
|
|
94
|
+
pruned = prune_interrupted_tool_calls(filtered)
|
|
95
|
+
return pruned
|
|
96
|
+
|
|
97
|
+
|
|
90
98
|
def split_messages_for_protected_summarization(
|
|
91
99
|
messages: List[ModelMessage],
|
|
92
100
|
) -> Tuple[List[ModelMessage], List[ModelMessage]]:
|
|
@@ -306,7 +314,8 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
306
314
|
status_bar.update_token_info(
|
|
307
315
|
total_current_tokens, model_max, proportion_used
|
|
308
316
|
)
|
|
309
|
-
except Exception:
|
|
317
|
+
except Exception as e:
|
|
318
|
+
emit_error(e)
|
|
310
319
|
# Fallback to chat message if status bar update fails
|
|
311
320
|
emit_info(
|
|
312
321
|
f"\n[bold white on blue] Tokens in context: {total_current_tokens}, total model capacity: {model_max}, proportion used: {proportion_used:.2f} [/bold white on blue] \n",
|
|
@@ -323,12 +332,26 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
323
332
|
emit_info(
|
|
324
333
|
f"\n[bold white on blue] Tokens in context: {total_current_tokens}, total model capacity: {model_max}, proportion used: {proportion_used:.2f} [/bold white on blue] \n"
|
|
325
334
|
)
|
|
335
|
+
# Get the configured compaction threshold
|
|
336
|
+
compaction_threshold = get_compaction_threshold()
|
|
337
|
+
|
|
338
|
+
# Get the configured compaction strategy
|
|
339
|
+
compaction_strategy = get_compaction_strategy()
|
|
340
|
+
|
|
341
|
+
if proportion_used > compaction_threshold:
|
|
342
|
+
if compaction_strategy == "truncation":
|
|
343
|
+
# Use truncation instead of summarization
|
|
344
|
+
protected_tokens = get_protected_token_count()
|
|
345
|
+
result_messages = truncation(
|
|
346
|
+
filter_huge_messages(messages), protected_tokens
|
|
347
|
+
)
|
|
348
|
+
summarized_messages = [] # No summarization in truncation mode
|
|
349
|
+
else:
|
|
350
|
+
# Default to summarization
|
|
351
|
+
result_messages, summarized_messages = summarize_messages(
|
|
352
|
+
filter_huge_messages(messages)
|
|
353
|
+
)
|
|
326
354
|
|
|
327
|
-
# Get the configured summarization threshold
|
|
328
|
-
summarization_threshold = get_summarization_threshold()
|
|
329
|
-
|
|
330
|
-
if proportion_used > summarization_threshold:
|
|
331
|
-
result_messages, summarized_messages = summarize_messages(messages)
|
|
332
355
|
final_token_count = sum(
|
|
333
356
|
estimate_tokens_for_message(msg) for msg in result_messages
|
|
334
357
|
)
|
|
@@ -360,6 +383,30 @@ def message_history_processor(messages: List[ModelMessage]) -> List[ModelMessage
|
|
|
360
383
|
return messages
|
|
361
384
|
|
|
362
385
|
|
|
386
|
+
def truncation(
|
|
387
|
+
messages: List[ModelMessage], protected_tokens: int
|
|
388
|
+
) -> List[ModelMessage]:
|
|
389
|
+
emit_info("Truncating message history to manage token usage")
|
|
390
|
+
result = [messages[0]] # Always keep the first message (system prompt)
|
|
391
|
+
num_tokens = 0
|
|
392
|
+
stack = queue.LifoQueue()
|
|
393
|
+
|
|
394
|
+
# Put messages in reverse order (most recent first) into the stack
|
|
395
|
+
# but break when we exceed protected_tokens
|
|
396
|
+
for idx, msg in enumerate(reversed(messages[1:])): # Skip the first message
|
|
397
|
+
num_tokens += estimate_tokens_for_message(msg)
|
|
398
|
+
if num_tokens > protected_tokens:
|
|
399
|
+
break
|
|
400
|
+
stack.put(msg)
|
|
401
|
+
|
|
402
|
+
# Pop messages from stack to get them in chronological order
|
|
403
|
+
while not stack.empty():
|
|
404
|
+
result.append(stack.get())
|
|
405
|
+
|
|
406
|
+
result = prune_interrupted_tool_calls(result)
|
|
407
|
+
return result
|
|
408
|
+
|
|
409
|
+
|
|
363
410
|
def message_history_accumulator(messages: List[Any]):
|
|
364
411
|
_message_history = get_message_history()
|
|
365
412
|
message_history_hashes = set([hash_message(m) for m in _message_history])
|
|
@@ -12,6 +12,7 @@ from pydantic_ai import RunContext
|
|
|
12
12
|
from rich.markdown import Markdown
|
|
13
13
|
from rich.text import Text
|
|
14
14
|
|
|
15
|
+
from code_puppy.callbacks import on_run_shell_command
|
|
15
16
|
from code_puppy.messaging import (
|
|
16
17
|
emit_divider,
|
|
17
18
|
emit_error,
|
|
@@ -543,7 +544,8 @@ def register_command_runner_tools(agent):
|
|
|
543
544
|
This tool can execute arbitrary shell commands. Exercise caution when
|
|
544
545
|
running untrusted commands, especially those that modify system state.
|
|
545
546
|
"""
|
|
546
|
-
|
|
547
|
+
result = run_shell_command(context, command, cwd, timeout)
|
|
548
|
+
on_run_shell_command(result)
|
|
547
549
|
|
|
548
550
|
@agent.tool
|
|
549
551
|
def agent_share_your_reasoning(
|
code_puppy/tools/common.py
CHANGED
|
@@ -304,6 +304,54 @@ IGNORE_PATTERNS = [
|
|
|
304
304
|
"**/*.save",
|
|
305
305
|
# Hidden files (but be careful with this one)
|
|
306
306
|
"**/.*", # Commented out as it might be too aggressive
|
|
307
|
+
# Binary image formats
|
|
308
|
+
"**/*.png",
|
|
309
|
+
"**/*.jpg",
|
|
310
|
+
"**/*.jpeg",
|
|
311
|
+
"**/*.gif",
|
|
312
|
+
"**/*.bmp",
|
|
313
|
+
"**/*.tiff",
|
|
314
|
+
"**/*.tif",
|
|
315
|
+
"**/*.webp",
|
|
316
|
+
"**/*.ico",
|
|
317
|
+
"**/*.svg",
|
|
318
|
+
# Binary document formats
|
|
319
|
+
"**/*.pdf",
|
|
320
|
+
"**/*.doc",
|
|
321
|
+
"**/*.docx",
|
|
322
|
+
"**/*.xls",
|
|
323
|
+
"**/*.xlsx",
|
|
324
|
+
"**/*.ppt",
|
|
325
|
+
"**/*.pptx",
|
|
326
|
+
# Archive formats
|
|
327
|
+
"**/*.zip",
|
|
328
|
+
"**/*.tar",
|
|
329
|
+
"**/*.gz",
|
|
330
|
+
"**/*.bz2",
|
|
331
|
+
"**/*.xz",
|
|
332
|
+
"**/*.rar",
|
|
333
|
+
"**/*.7z",
|
|
334
|
+
# Media files
|
|
335
|
+
"**/*.mp3",
|
|
336
|
+
"**/*.mp4",
|
|
337
|
+
"**/*.avi",
|
|
338
|
+
"**/*.mov",
|
|
339
|
+
"**/*.wmv",
|
|
340
|
+
"**/*.flv",
|
|
341
|
+
"**/*.wav",
|
|
342
|
+
"**/*.ogg",
|
|
343
|
+
# Font files
|
|
344
|
+
"**/*.ttf",
|
|
345
|
+
"**/*.otf",
|
|
346
|
+
"**/*.woff",
|
|
347
|
+
"**/*.woff2",
|
|
348
|
+
"**/*.eot",
|
|
349
|
+
# Other binary formats
|
|
350
|
+
"**/*.bin",
|
|
351
|
+
"**/*.dat",
|
|
352
|
+
"**/*.db",
|
|
353
|
+
"**/*.sqlite",
|
|
354
|
+
"**/*.sqlite3",
|
|
307
355
|
]
|
|
308
356
|
|
|
309
357
|
|
|
@@ -20,6 +20,7 @@ import json_repair
|
|
|
20
20
|
from pydantic import BaseModel
|
|
21
21
|
from pydantic_ai import RunContext
|
|
22
22
|
|
|
23
|
+
from code_puppy.callbacks import on_delete_file, on_edit_file
|
|
23
24
|
from code_puppy.messaging import emit_error, emit_info, emit_warning
|
|
24
25
|
from code_puppy.tools.common import _find_best_window, generate_group_id
|
|
25
26
|
|
|
@@ -542,6 +543,7 @@ def register_file_modifications_tools(agent):
|
|
|
542
543
|
}
|
|
543
544
|
group_id = generate_group_id("edit_file", payload.file_path)
|
|
544
545
|
result = _edit_file(context, payload, group_id)
|
|
546
|
+
on_edit_file(result)
|
|
545
547
|
if "diff" in result:
|
|
546
548
|
del result["diff"]
|
|
547
549
|
return result
|
|
@@ -600,6 +602,7 @@ def register_file_modifications_tools(agent):
|
|
|
600
602
|
# Generate group_id for delete_file tool execution
|
|
601
603
|
group_id = generate_group_id("delete_file", file_path)
|
|
602
604
|
result = _delete_file(context, file_path, message_group=group_id)
|
|
605
|
+
on_delete_file(result)
|
|
603
606
|
if "diff" in result:
|
|
604
607
|
del result["diff"]
|
|
605
608
|
return result
|
code_puppy/tui/app.py
CHANGED
|
@@ -27,7 +27,11 @@ from code_puppy.message_history_processor import (
|
|
|
27
27
|
|
|
28
28
|
# Import our message queue system
|
|
29
29
|
from code_puppy.messaging import TUIRenderer, get_global_queue
|
|
30
|
-
from code_puppy.state_management import
|
|
30
|
+
from code_puppy.state_management import (
|
|
31
|
+
clear_message_history,
|
|
32
|
+
get_message_history,
|
|
33
|
+
set_message_history,
|
|
34
|
+
)
|
|
31
35
|
from code_puppy.tui.components import (
|
|
32
36
|
ChatView,
|
|
33
37
|
CustomTextArea,
|
|
@@ -499,7 +503,9 @@ class CodePuppyTUI(App):
|
|
|
499
503
|
# Handle regular exceptions
|
|
500
504
|
self.add_error_message(f"MCP/Agent error: {str(eg)}")
|
|
501
505
|
finally:
|
|
502
|
-
set_message_history(
|
|
506
|
+
set_message_history(
|
|
507
|
+
prune_interrupted_tool_calls(get_message_history())
|
|
508
|
+
)
|
|
503
509
|
except Exception as agent_error:
|
|
504
510
|
# Handle any other errors in agent processing
|
|
505
511
|
self.add_error_message(
|
|
@@ -101,15 +101,15 @@ class StatusBar(Static):
|
|
|
101
101
|
token_color = "green"
|
|
102
102
|
if self.token_count > 0 and self.token_capacity > 0:
|
|
103
103
|
# Import here to avoid circular import
|
|
104
|
-
from code_puppy.config import
|
|
104
|
+
from code_puppy.config import get_compaction_threshold
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
get_compaction_threshold = get_compaction_threshold()
|
|
107
107
|
|
|
108
|
-
if self.token_proportion >
|
|
108
|
+
if self.token_proportion > get_compaction_threshold:
|
|
109
109
|
token_color = "red"
|
|
110
110
|
token_status = f"🔴 {self.token_count}/{self.token_capacity} ({self.token_proportion:.1%})"
|
|
111
111
|
elif self.token_proportion > (
|
|
112
|
-
|
|
112
|
+
get_compaction_threshold - 0.15
|
|
113
113
|
): # 15% before summarization threshold
|
|
114
114
|
token_color = "yellow"
|
|
115
115
|
token_status = f"🟡 {self.token_count}/{self.token_capacity} ({self.token_proportion:.1%})"
|
|
@@ -100,9 +100,20 @@ class SettingsScreen(ModalScreen):
|
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
with Container(classes="setting-row"):
|
|
103
|
-
yield Static("
|
|
103
|
+
yield Static("Compaction Strategy:", classes="setting-label")
|
|
104
|
+
yield Select(
|
|
105
|
+
[
|
|
106
|
+
("Summarization", "summarization"),
|
|
107
|
+
("Truncation", "truncation"),
|
|
108
|
+
],
|
|
109
|
+
id="compaction-strategy-select",
|
|
110
|
+
classes="setting-input",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
with Container(classes="setting-row"):
|
|
114
|
+
yield Static("Compaction Threshold:", classes="setting-label")
|
|
104
115
|
yield Input(
|
|
105
|
-
id="
|
|
116
|
+
id="compaction-threshold-input",
|
|
106
117
|
classes="setting-input",
|
|
107
118
|
placeholder="e.g., 0.85",
|
|
108
119
|
)
|
|
@@ -118,7 +129,8 @@ class SettingsScreen(ModalScreen):
|
|
|
118
129
|
get_owner_name,
|
|
119
130
|
get_protected_token_count,
|
|
120
131
|
get_puppy_name,
|
|
121
|
-
|
|
132
|
+
get_compaction_strategy,
|
|
133
|
+
get_compaction_threshold,
|
|
122
134
|
)
|
|
123
135
|
|
|
124
136
|
# Load current values
|
|
@@ -126,12 +138,18 @@ class SettingsScreen(ModalScreen):
|
|
|
126
138
|
owner_name_input = self.query_one("#owner-name-input", Input)
|
|
127
139
|
model_select = self.query_one("#model-select", Select)
|
|
128
140
|
protected_tokens_input = self.query_one("#protected-tokens-input", Input)
|
|
129
|
-
|
|
141
|
+
compaction_threshold_input = self.query_one(
|
|
142
|
+
"#compaction-threshold-input", Input
|
|
143
|
+
)
|
|
144
|
+
compaction_strategy_select = self.query_one(
|
|
145
|
+
"#compaction-strategy-select", Select
|
|
146
|
+
)
|
|
130
147
|
|
|
131
148
|
puppy_name_input.value = get_puppy_name() or ""
|
|
132
149
|
owner_name_input.value = get_owner_name() or ""
|
|
133
150
|
protected_tokens_input.value = str(get_protected_token_count())
|
|
134
|
-
|
|
151
|
+
compaction_threshold_input.value = str(get_compaction_threshold())
|
|
152
|
+
compaction_strategy_select.value = get_compaction_strategy()
|
|
135
153
|
|
|
136
154
|
# Load available models
|
|
137
155
|
self.load_model_options(model_select)
|
|
@@ -146,9 +164,7 @@ class SettingsScreen(ModalScreen):
|
|
|
146
164
|
"""Load available models into the model select widget."""
|
|
147
165
|
try:
|
|
148
166
|
# Use the same method that interactive mode uses to load models
|
|
149
|
-
import os
|
|
150
167
|
|
|
151
|
-
from code_puppy.config import CONFIG_DIR
|
|
152
168
|
from code_puppy.model_factory import ModelFactory
|
|
153
169
|
|
|
154
170
|
# Load models using the same path and method as interactive mode
|
|
@@ -171,7 +187,11 @@ class SettingsScreen(ModalScreen):
|
|
|
171
187
|
@on(Button.Pressed, "#save-button")
|
|
172
188
|
def save_settings(self) -> None:
|
|
173
189
|
"""Save the modified settings."""
|
|
174
|
-
from code_puppy.config import
|
|
190
|
+
from code_puppy.config import (
|
|
191
|
+
set_config_value,
|
|
192
|
+
set_model_name,
|
|
193
|
+
get_model_context_length,
|
|
194
|
+
)
|
|
175
195
|
|
|
176
196
|
try:
|
|
177
197
|
# Get values from inputs
|
|
@@ -182,8 +202,8 @@ class SettingsScreen(ModalScreen):
|
|
|
182
202
|
protected_tokens = self.query_one(
|
|
183
203
|
"#protected-tokens-input", Input
|
|
184
204
|
).value.strip()
|
|
185
|
-
|
|
186
|
-
"#
|
|
205
|
+
compaction_threshold = self.query_one(
|
|
206
|
+
"#compaction-threshold-input", Input
|
|
187
207
|
).value.strip()
|
|
188
208
|
|
|
189
209
|
# Validate and save
|
|
@@ -201,31 +221,46 @@ class SettingsScreen(ModalScreen):
|
|
|
201
221
|
# Validate and save protected tokens
|
|
202
222
|
if protected_tokens.isdigit():
|
|
203
223
|
tokens_value = int(protected_tokens)
|
|
224
|
+
model_context_length = get_model_context_length()
|
|
225
|
+
max_protected_tokens = int(model_context_length * 0.75)
|
|
226
|
+
|
|
204
227
|
if tokens_value >= 1000: # Minimum validation
|
|
205
|
-
|
|
228
|
+
if tokens_value <= max_protected_tokens: # Maximum validation
|
|
229
|
+
set_config_value("protected_token_count", protected_tokens)
|
|
230
|
+
else:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"Protected tokens must not exceed 75% of model context length ({max_protected_tokens} tokens for current model)"
|
|
233
|
+
)
|
|
206
234
|
else:
|
|
207
235
|
raise ValueError("Protected tokens must be at least 1000")
|
|
208
236
|
elif protected_tokens: # If not empty but not digit
|
|
209
237
|
raise ValueError("Protected tokens must be a valid number")
|
|
210
238
|
|
|
211
|
-
# Validate and save
|
|
212
|
-
if
|
|
239
|
+
# Validate and save compaction threshold
|
|
240
|
+
if compaction_threshold:
|
|
213
241
|
try:
|
|
214
|
-
threshold_value = float(
|
|
215
|
-
if 0.
|
|
216
|
-
set_config_value("
|
|
242
|
+
threshold_value = float(compaction_threshold)
|
|
243
|
+
if 0.8 <= threshold_value <= 0.95: # Same bounds as config function
|
|
244
|
+
set_config_value("compaction_threshold", compaction_threshold)
|
|
217
245
|
else:
|
|
218
246
|
raise ValueError(
|
|
219
|
-
"
|
|
247
|
+
"Compaction threshold must be between 0.8 and 0.95"
|
|
220
248
|
)
|
|
221
249
|
except ValueError as ve:
|
|
222
250
|
if "must be between" in str(ve):
|
|
223
251
|
raise ve
|
|
224
252
|
else:
|
|
225
253
|
raise ValueError(
|
|
226
|
-
"
|
|
254
|
+
"Compaction threshold must be a valid decimal number"
|
|
227
255
|
)
|
|
228
256
|
|
|
257
|
+
# Save compaction strategy
|
|
258
|
+
compaction_strategy = self.query_one(
|
|
259
|
+
"#compaction-strategy-select", Select
|
|
260
|
+
).value
|
|
261
|
+
if compaction_strategy in ["summarization", "truncation"]:
|
|
262
|
+
set_config_value("compaction_strategy", compaction_strategy)
|
|
263
|
+
|
|
229
264
|
# Return success message with model change info
|
|
230
265
|
message = "Settings saved successfully!"
|
|
231
266
|
if selected_model:
|
|
@@ -2,11 +2,11 @@ code_puppy/__init__.py,sha256=-ANvE6Xe5NlWDIRCIfL1x-rgtCZ6zM2Ye9NphFoULSY,82
|
|
|
2
2
|
code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
|
|
3
3
|
code_puppy/agent.py,sha256=tWtH5FtxH6NJzvBfptJCKo4Nhw2J7JMlAKQuwkiyFyQ,7056
|
|
4
4
|
code_puppy/agent_prompts.py,sha256=pBDC5zsWozEiFHmoOS6CnxortMvN8VZqnUEHM8sx8Ug,6975
|
|
5
|
-
code_puppy/callbacks.py,sha256=
|
|
6
|
-
code_puppy/config.py,sha256=
|
|
5
|
+
code_puppy/callbacks.py,sha256=T9J7E9N80XChcr6MqjHPp_eSGx2ODN2Fak0081vYvSE,4901
|
|
6
|
+
code_puppy/config.py,sha256=yg-YNR6N89kygPLZiXT3TecITbhA1o7xUrRvmhQNJwA,15120
|
|
7
7
|
code_puppy/http_utils.py,sha256=zN0_F8cq4agamL3GRRLLhgEjBgjKtWrgGe8thORC7YE,3434
|
|
8
|
-
code_puppy/main.py,sha256=
|
|
9
|
-
code_puppy/message_history_processor.py,sha256=
|
|
8
|
+
code_puppy/main.py,sha256=ETXVFoeD75lIBxevvIZtTOqc-2wQGG_CPEWqPHtFBd8,27275
|
|
9
|
+
code_puppy/message_history_processor.py,sha256=O2rKp7W6YeIg93W8b0XySTUEQgIZm0f_06--_kzHugM,16145
|
|
10
10
|
code_puppy/model_factory.py,sha256=cqYpDAtUcFU4iB0PFSA_nLbuXv4cWFB59BE5-1nZqXI,10202
|
|
11
11
|
code_puppy/models.json,sha256=jr0-LW87aJS79GosVwoZdHeeq5eflPzgdPoMbcqpVA8,2728
|
|
12
12
|
code_puppy/reopenable_async_client.py,sha256=4UJRaMp5np8cbef9F0zKQ7TPKOfyf5U-Kv-0zYUWDho,8274
|
|
@@ -16,7 +16,7 @@ code_puppy/summarization_agent.py,sha256=-e6yUGZ22ahSaF0y7QhgVcQBfx5ktNUkPxBIWQf
|
|
|
16
16
|
code_puppy/token_utils.py,sha256=inLo-S2YERGA-JmV-nrSFN7KMswSfHxpawAuK6YiDHc,1982
|
|
17
17
|
code_puppy/version_checker.py,sha256=bjLDmgGPrl7XnYwX1u13O8uFlsfikV90PK6nbA9Z9QU,1150
|
|
18
18
|
code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
|
|
19
|
-
code_puppy/command_line/command_handler.py,sha256=
|
|
19
|
+
code_puppy/command_line/command_handler.py,sha256=VoSQ0tnjhyRnLk553tX4Bx-ggGxJluGkCZFxJzzFCUI,15280
|
|
20
20
|
code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
|
|
21
21
|
code_puppy/command_line/load_context_completion.py,sha256=6eZxV6Bs-EFwZjN93V8ZDZUC-6RaWxvtZk-04Wtikyw,2240
|
|
22
22
|
code_puppy/command_line/meta_command_handler.py,sha256=d5eVWzRoThYD3cR-GS0CMwHxDvBK4ezLdSIqwWDrq0g,5620
|
|
@@ -34,14 +34,14 @@ code_puppy/messaging/spinner/spinner_base.py,sha256=474qMrTYpNfWcprFzmhaOJEOC-2r
|
|
|
34
34
|
code_puppy/messaging/spinner/textual_spinner.py,sha256=fdyNkbn1SJgfATclBjv2KgLSiHOHUkky1AyHfhxtoH8,3273
|
|
35
35
|
code_puppy/plugins/__init__.py,sha256=fksDqMUiXPJ5WNuMsYsVR8ulueQRCXPlvECEyicHPtQ,1312
|
|
36
36
|
code_puppy/tools/__init__.py,sha256=F3BbF3OwgUsQFFGWdj-qXgQLqCSedANVqEZtcZD-SZY,455
|
|
37
|
-
code_puppy/tools/command_runner.py,sha256=
|
|
38
|
-
code_puppy/tools/common.py,sha256=
|
|
39
|
-
code_puppy/tools/file_modifications.py,sha256=
|
|
37
|
+
code_puppy/tools/command_runner.py,sha256=HsLF2OJL13Dw2-g67ZgsolIPH30PF6iZ4gjjL6xAXrI,21425
|
|
38
|
+
code_puppy/tools/common.py,sha256=pL-9xcRs3rxU7Fl9X9EUgbDp2-csh2LLJ5DHH_KAHKY,10596
|
|
39
|
+
code_puppy/tools/file_modifications.py,sha256=_b6bWKB7P5G9-kaMqLlCduFINf-3FEQNkTRBr_VgUSI,23063
|
|
40
40
|
code_puppy/tools/file_operations.py,sha256=7nSueKqlJcnlnBg7zXkX7EWiOJX8i3NNWGE7QRCkF7o,24867
|
|
41
41
|
code_puppy/tools/token_check.py,sha256=cNrGOOKahXsnWsvh5xnMkL1NS9FjYur9QIRZGQFW-pE,1189
|
|
42
42
|
code_puppy/tools/tools_content.py,sha256=pi9ig2qahZFkUj7gBBN2TX2QldvwnqmTHrRKP8my_2k,2209
|
|
43
43
|
code_puppy/tui/__init__.py,sha256=XesAxIn32zLPOmvpR2wIDxDAnnJr81a5pBJB4cZp1Xs,321
|
|
44
|
-
code_puppy/tui/app.py,sha256=
|
|
44
|
+
code_puppy/tui/app.py,sha256=vsdfiBXo3EEPPBNCF_cEBk9urVhdcB1JNAY8AIwZwV8,43275
|
|
45
45
|
code_puppy/tui/messages.py,sha256=zQoToWI0eWdT36NEsY6RdCFzcDfAmfvoPlHv8jiCbgo,720
|
|
46
46
|
code_puppy/tui/components/__init__.py,sha256=uj5pnk3s6SEN3SbFI0ZnzaA2KK1NNg8TfUj6U-Z732U,455
|
|
47
47
|
code_puppy/tui/components/chat_view.py,sha256=u6yFqz7sz5RYxVpobnAHXDfDeIDRwDnXXTBwBl5Wn7M,17995
|
|
@@ -50,14 +50,14 @@ code_puppy/tui/components/copy_button.py,sha256=E4-OJYk5YNzDf-E81NyiVGKsTRPrUX-R
|
|
|
50
50
|
code_puppy/tui/components/custom_widgets.py,sha256=pnjkB3ZNa5lwSrAXUFlhN9AHNh4uMTpSap8AdbpecKw,1986
|
|
51
51
|
code_puppy/tui/components/input_area.py,sha256=R4R32eXPZ2R8KFisIbldNGq60KMk7kCxWrdbeTgJUr8,4395
|
|
52
52
|
code_puppy/tui/components/sidebar.py,sha256=nGtCiYzZalPmiFaJ4dwj2S4EJBu5wQZVzhoigYYY7U4,10369
|
|
53
|
-
code_puppy/tui/components/status_bar.py,sha256=
|
|
53
|
+
code_puppy/tui/components/status_bar.py,sha256=GgznJqF8Wk6XkurBuKohLyu75eT_ucBTvl9oPcySmnM,6338
|
|
54
54
|
code_puppy/tui/models/__init__.py,sha256=5Eq7BMibz-z_t_v7B4H4tCdKRG41i2CaCuNQf_lteAE,140
|
|
55
55
|
code_puppy/tui/models/chat_message.py,sha256=2fSqsl4EHKgGsi_cVKWBbFq1NQwZyledGuJ9djovtLY,477
|
|
56
56
|
code_puppy/tui/models/command_history.py,sha256=bPWr_xnyQvjG5tPg_5pwqlEzn2fR170HlvBJwAXRpAE,2895
|
|
57
57
|
code_puppy/tui/models/enums.py,sha256=1ulsei95Gxy4r1sk-m-Sm5rdmejYCGRI-YtUwJmKFfM,501
|
|
58
58
|
code_puppy/tui/screens/__init__.py,sha256=iQ_pzTdF9095l-kCWHcaxvWyP8ndFbZaGySWDpCxQwU,201
|
|
59
59
|
code_puppy/tui/screens/help.py,sha256=eJuPaOOCp7ZSUlecearqsuX6caxWv7NQszUh0tZJjBM,3232
|
|
60
|
-
code_puppy/tui/screens/settings.py,sha256=
|
|
60
|
+
code_puppy/tui/screens/settings.py,sha256=GMpv-qa08rorAE9mj3AjmqjZFPhmeJ_GWd-DBHG6iAA,10671
|
|
61
61
|
code_puppy/tui/screens/tools.py,sha256=3pr2Xkpa9Js6Yhf1A3_wQVRzFOui-KDB82LwrsdBtyk,1715
|
|
62
62
|
code_puppy/tui/tests/__init__.py,sha256=Fzb4un4eeKfaKsIa5tqI5pTuwfpS8qD7Z6W7KeqWe84,23
|
|
63
63
|
code_puppy/tui/tests/test_chat_message.py,sha256=uA3eZBzpTkSLEFVMp6k97JALVlzBP4_1YHHTXQCxV0I,765
|
|
@@ -78,9 +78,9 @@ code_puppy/tui/tests/test_sidebar_history_navigation.py,sha256=JGiyua8A2B8dLfwiE
|
|
|
78
78
|
code_puppy/tui/tests/test_status_bar.py,sha256=nYT_FZGdmqnnbn6o0ZuOkLtNUtJzLSmtX8P72liQ5Vo,1797
|
|
79
79
|
code_puppy/tui/tests/test_timestamped_history.py,sha256=nVXt9hExZZ_8MFP-AZj4L4bB_1Eo_mc-ZhVICzTuw3I,1799
|
|
80
80
|
code_puppy/tui/tests/test_tools.py,sha256=kgzzAkK4r0DPzQwHHD4cePpVNgrHor6cFr05Pg6DBWg,2687
|
|
81
|
-
code_puppy-0.0.
|
|
82
|
-
code_puppy-0.0.
|
|
83
|
-
code_puppy-0.0.
|
|
84
|
-
code_puppy-0.0.
|
|
85
|
-
code_puppy-0.0.
|
|
86
|
-
code_puppy-0.0.
|
|
81
|
+
code_puppy-0.0.124.data/data/code_puppy/models.json,sha256=jr0-LW87aJS79GosVwoZdHeeq5eflPzgdPoMbcqpVA8,2728
|
|
82
|
+
code_puppy-0.0.124.dist-info/METADATA,sha256=oRUXDjmFz9mwX_gAgaIEpbBffmzvi0051oiRt7vW22o,6537
|
|
83
|
+
code_puppy-0.0.124.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
84
|
+
code_puppy-0.0.124.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
|
|
85
|
+
code_puppy-0.0.124.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
86
|
+
code_puppy-0.0.124.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|