code-puppy 0.0.54__py3-none-any.whl → 0.0.56__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/agent.py +4 -3
- code_puppy/agent_prompts.py +1 -1
- code_puppy/command_line/file_path_completion.py +2 -1
- code_puppy/command_line/meta_command_handler.py +2 -0
- code_puppy/command_line/model_picker_completion.py +6 -4
- code_puppy/command_line/prompt_toolkit_completion.py +7 -9
- code_puppy/command_line/utils.py +2 -1
- code_puppy/config.py +1 -1
- code_puppy/main.py +10 -10
- code_puppy/model_factory.py +12 -11
- code_puppy/session_memory.py +2 -2
- code_puppy/tools/__init__.py +2 -2
- code_puppy/tools/code_map.py +4 -3
- code_puppy/tools/command_runner.py +6 -4
- code_puppy/tools/common.py +36 -0
- code_puppy/tools/file_modifications.py +149 -213
- code_puppy/tools/file_operations.py +139 -18
- code_puppy/tools/web_search.py +1 -0
- {code_puppy-0.0.54.dist-info → code_puppy-0.0.56.dist-info}/METADATA +3 -1
- code_puppy-0.0.56.dist-info/RECORD +28 -0
- code_puppy-0.0.54.dist-info/RECORD +0 -28
- {code_puppy-0.0.54.data → code_puppy-0.0.56.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.54.dist-info → code_puppy-0.0.56.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.54.dist-info → code_puppy-0.0.56.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.54.dist-info → code_puppy-0.0.56.dist-info}/licenses/LICENSE +0 -0
code_puppy/agent.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import pydantic
|
|
3
2
|
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pydantic
|
|
4
5
|
from pydantic_ai import Agent
|
|
5
6
|
|
|
6
7
|
from code_puppy.agent_prompts import get_system_prompt
|
|
7
8
|
from code_puppy.model_factory import ModelFactory
|
|
8
|
-
from code_puppy.tools.common import console
|
|
9
|
-
from code_puppy.tools import register_all_tools
|
|
10
9
|
from code_puppy.session_memory import SessionMemory
|
|
10
|
+
from code_puppy.tools import register_all_tools
|
|
11
|
+
from code_puppy.tools.common import console
|
|
11
12
|
|
|
12
13
|
# Environment variables used in this module:
|
|
13
14
|
# - MODELS_JSON_PATH: Optional path to a custom models.json configuration file.
|
code_puppy/agent_prompts.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from code_puppy.config import
|
|
1
|
+
from code_puppy.config import get_owner_name, get_puppy_name
|
|
2
2
|
|
|
3
3
|
SYSTEM_PROMPT_TEMPLATE = """
|
|
4
4
|
You are {puppy_name}, the most loyal digital puppy, helping your owner {owner_name} get coding stuff done! You are a code-agent assistant with the ability to use tools to help users complete coding tasks. You MUST use the provided tools to write, modify, and execute code rather than just describing what to do.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import json
|
|
3
|
-
|
|
2
|
+
import os
|
|
3
|
+
from typing import Iterable, Optional
|
|
4
|
+
|
|
5
|
+
from prompt_toolkit import PromptSession
|
|
4
6
|
from prompt_toolkit.completion import Completer, Completion
|
|
5
|
-
from prompt_toolkit.history import FileHistory
|
|
6
7
|
from prompt_toolkit.document import Document
|
|
7
|
-
from prompt_toolkit import
|
|
8
|
+
from prompt_toolkit.history import FileHistory
|
|
9
|
+
|
|
8
10
|
from code_puppy.config import get_model_name, set_model_name
|
|
9
11
|
|
|
10
12
|
MODELS_JSON_PATH = os.environ.get("MODELS_JSON_PATH")
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from code_puppy.command_line.utils import list_directory
|
|
3
|
-
from code_puppy.config import get_puppy_name, get_config_keys, get_value
|
|
4
|
-
|
|
5
1
|
# ANSI color codes are no longer necessary because prompt_toolkit handles
|
|
6
2
|
# styling via the `Style` class. We keep them here commented-out in case
|
|
7
3
|
# someone needs raw ANSI later, but they are unused in the current code.
|
|
@@ -11,23 +7,25 @@ from code_puppy.config import get_puppy_name, get_config_keys, get_value
|
|
|
11
7
|
# YELLOW = '\033[1;33m'
|
|
12
8
|
# BOLD = '\033[1m'
|
|
13
9
|
import asyncio
|
|
10
|
+
import os
|
|
14
11
|
from typing import Optional
|
|
12
|
+
|
|
15
13
|
from prompt_toolkit import PromptSession
|
|
14
|
+
from prompt_toolkit.completion import Completer, Completion, merge_completers
|
|
16
15
|
from prompt_toolkit.formatted_text import FormattedText
|
|
17
|
-
from prompt_toolkit.completion import merge_completers
|
|
18
16
|
from prompt_toolkit.history import FileHistory
|
|
19
|
-
from prompt_toolkit.styles import Style
|
|
20
17
|
from prompt_toolkit.key_binding import KeyBindings
|
|
21
18
|
from prompt_toolkit.keys import Keys
|
|
19
|
+
from prompt_toolkit.styles import Style
|
|
22
20
|
|
|
21
|
+
from code_puppy.command_line.file_path_completion import FilePathCompleter
|
|
23
22
|
from code_puppy.command_line.model_picker_completion import (
|
|
24
23
|
ModelNameCompleter,
|
|
25
24
|
get_active_model,
|
|
26
25
|
update_model_in_input,
|
|
27
26
|
)
|
|
28
|
-
from code_puppy.command_line.
|
|
29
|
-
|
|
30
|
-
from prompt_toolkit.completion import Completer, Completion
|
|
27
|
+
from code_puppy.command_line.utils import list_directory
|
|
28
|
+
from code_puppy.config import get_config_keys, get_puppy_name, get_value
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
class SetCompleter(Completer):
|
code_puppy/command_line/utils.py
CHANGED
code_puppy/config.py
CHANGED
code_puppy/main.py
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import argparse
|
|
2
|
+
import asyncio
|
|
3
3
|
import os
|
|
4
|
-
from code_puppy.version_checker import fetch_latest_version
|
|
5
|
-
from code_puppy import __version__
|
|
6
4
|
import sys
|
|
5
|
+
|
|
7
6
|
from dotenv import load_dotenv
|
|
8
|
-
from
|
|
9
|
-
from rich.
|
|
10
|
-
from rich.markdown import Markdown
|
|
11
|
-
from rich.console import ConsoleOptions, RenderResult
|
|
12
|
-
from rich.markdown import CodeBlock
|
|
13
|
-
from rich.text import Text
|
|
7
|
+
from rich.console import Console, ConsoleOptions, RenderResult
|
|
8
|
+
from rich.markdown import CodeBlock, Markdown
|
|
14
9
|
from rich.syntax import Syntax
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
from code_puppy import __version__
|
|
13
|
+
from code_puppy.agent import get_code_generation_agent, session_memory
|
|
15
14
|
from code_puppy.command_line.prompt_toolkit_completion import (
|
|
16
15
|
get_input_with_combined_completion,
|
|
17
16
|
get_prompt_with_active_model,
|
|
18
17
|
)
|
|
18
|
+
from code_puppy.config import ensure_config_exists
|
|
19
19
|
|
|
20
20
|
# Initialize rich console for pretty output
|
|
21
21
|
from code_puppy.tools.common import console
|
|
22
|
-
from code_puppy.
|
|
22
|
+
from code_puppy.version_checker import fetch_latest_version
|
|
23
23
|
|
|
24
24
|
# from code_puppy.tools import * # noqa: F403
|
|
25
25
|
|
code_puppy/model_factory.py
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import json
|
|
3
1
|
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
4
5
|
import time
|
|
5
|
-
from
|
|
6
|
+
from collections import deque
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from anthropic import AsyncAnthropic
|
|
11
|
+
from httpx import Response
|
|
12
|
+
from openai import AsyncAzureOpenAI # For Azure OpenAI client
|
|
13
|
+
from pydantic_ai.models.anthropic import AnthropicModel
|
|
6
14
|
from pydantic_ai.models.gemini import GeminiModel
|
|
7
15
|
from pydantic_ai.models.openai import OpenAIModel
|
|
8
|
-
from pydantic_ai.
|
|
16
|
+
from pydantic_ai.providers.anthropic import AnthropicProvider
|
|
9
17
|
from pydantic_ai.providers.google_gla import GoogleGLAProvider
|
|
10
18
|
from pydantic_ai.providers.openai import OpenAIProvider
|
|
11
|
-
from pydantic_ai.providers.anthropic import AnthropicProvider
|
|
12
|
-
from anthropic import AsyncAnthropic
|
|
13
|
-
from openai import AsyncAzureOpenAI # For Azure OpenAI client
|
|
14
|
-
import httpx
|
|
15
|
-
from httpx import Response
|
|
16
|
-
import threading
|
|
17
|
-
from collections import deque
|
|
18
19
|
|
|
19
20
|
# Environment variables used in this module:
|
|
20
21
|
# - GEMINI_API_KEY: API key for Google's Gemini models. Required when using Gemini models.
|
code_puppy/session_memory.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from pathlib import Path
|
|
3
2
|
from datetime import datetime, timedelta
|
|
4
|
-
from
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
5
|
|
|
6
6
|
DEFAULT_MEMORY_PATH = Path(".puppy_session_memory.json")
|
|
7
7
|
|
code_puppy/tools/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from code_puppy.tools.file_operations import register_file_operations_tools
|
|
2
|
-
from code_puppy.tools.file_modifications import register_file_modifications_tools
|
|
3
1
|
from code_puppy.tools.command_runner import register_command_runner_tools
|
|
2
|
+
from code_puppy.tools.file_modifications import register_file_modifications_tools
|
|
3
|
+
from code_puppy.tools.file_operations import register_file_operations_tools
|
|
4
4
|
from code_puppy.tools.web_search import register_web_search_tools
|
|
5
5
|
|
|
6
6
|
|
code_puppy/tools/code_map.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
import time
|
|
3
|
-
from typing import
|
|
4
|
-
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
5
|
from pydantic_ai import RunContext
|
|
6
6
|
from rich.markdown import Markdown
|
|
7
7
|
from rich.syntax import Syntax
|
|
8
8
|
|
|
9
|
+
from code_puppy.tools.common import console
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
def run_shell_command(
|
|
11
13
|
context: RunContext, command: str, cwd: str = None, timeout: int = 60
|
|
@@ -138,8 +140,8 @@ def run_shell_command(
|
|
|
138
140
|
"success": False,
|
|
139
141
|
"command": command,
|
|
140
142
|
"error": f"Error executing command: {str(e)}",
|
|
141
|
-
"stdout":
|
|
142
|
-
"stderr":
|
|
143
|
+
"stdout": stdout[-1000:],
|
|
144
|
+
"stderr": stderr[-1000:],
|
|
143
145
|
"exit_code": -1,
|
|
144
146
|
"timeout": False,
|
|
145
147
|
}
|
code_puppy/tools/common.py
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import os
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
|
+
|
|
5
|
+
from rapidfuzz.distance import JaroWinkler
|
|
2
6
|
from rich.console import Console
|
|
3
7
|
|
|
4
8
|
NO_COLOR = bool(int(os.environ.get("CODE_PUPPY_NO_COLOR", "0")))
|
|
5
9
|
console = Console(no_color=NO_COLOR)
|
|
10
|
+
|
|
11
|
+
JW_THRESHOLD = 0.95
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _find_best_window(
|
|
15
|
+
haystack_lines: list[str],
|
|
16
|
+
needle: str,
|
|
17
|
+
) -> Tuple[Optional[Tuple[int, int]], float]:
|
|
18
|
+
"""
|
|
19
|
+
Return (start, end) indices of the window with the highest
|
|
20
|
+
Jaro-Winkler similarity to `needle`, along with that score.
|
|
21
|
+
If nothing clears JW_THRESHOLD, return (None, score).
|
|
22
|
+
"""
|
|
23
|
+
needle = needle.rstrip("\n")
|
|
24
|
+
needle_lines = needle.splitlines()
|
|
25
|
+
win_size = len(needle_lines)
|
|
26
|
+
best_score = 0.0
|
|
27
|
+
best_span: Optional[Tuple[int, int]] = None
|
|
28
|
+
best_window = ""
|
|
29
|
+
# Pre-join the needle once; join windows on the fly
|
|
30
|
+
for i in range(len(haystack_lines) - win_size + 1):
|
|
31
|
+
window = "\n".join(haystack_lines[i : i + win_size])
|
|
32
|
+
score = JaroWinkler.normalized_similarity(window, needle)
|
|
33
|
+
if score > best_score:
|
|
34
|
+
best_score = score
|
|
35
|
+
best_span = (i, i + win_size)
|
|
36
|
+
best_window = window
|
|
37
|
+
|
|
38
|
+
console.log(f"Best span: {best_span}")
|
|
39
|
+
console.log(f"Best window: {best_window}")
|
|
40
|
+
console.log(f"Best score: {best_score}")
|
|
41
|
+
return (best_span, best_score)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# file_modifications.py
|
|
2
1
|
"""Robust, always-diff-logging file-modification helpers + agent tools.
|
|
3
2
|
|
|
4
3
|
Key guarantees
|
|
@@ -11,19 +10,16 @@ Key guarantees
|
|
|
11
10
|
|
|
12
11
|
from __future__ import annotations
|
|
13
12
|
|
|
14
|
-
import ast
|
|
15
13
|
import difflib
|
|
16
14
|
import json
|
|
17
15
|
import os
|
|
18
16
|
import traceback
|
|
19
17
|
from typing import Any, Dict, List
|
|
20
18
|
|
|
21
|
-
from
|
|
19
|
+
from json_repair import repair_json
|
|
22
20
|
from pydantic_ai import RunContext
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
# Console helpers – shared across tools
|
|
26
|
-
# ---------------------------------------------------------------------------
|
|
22
|
+
from code_puppy.tools.common import _find_best_window, console
|
|
27
23
|
|
|
28
24
|
|
|
29
25
|
def _print_diff(diff_text: str) -> None:
|
|
@@ -54,11 +50,6 @@ def _log_error(msg: str, exc: Exception | None = None) -> None:
|
|
|
54
50
|
console.print(traceback.format_exc(), highlight=False)
|
|
55
51
|
|
|
56
52
|
|
|
57
|
-
# ---------------------------------------------------------------------------
|
|
58
|
-
# Pure helpers – no console output
|
|
59
|
-
# ---------------------------------------------------------------------------
|
|
60
|
-
|
|
61
|
-
|
|
62
53
|
def _delete_snippet_from_file(
|
|
63
54
|
context: RunContext | None, file_path: str, snippet: str
|
|
64
55
|
) -> Dict[str, Any]:
|
|
@@ -99,96 +90,74 @@ def _delete_snippet_from_file(
|
|
|
99
90
|
|
|
100
91
|
|
|
101
92
|
def _replace_in_file(
|
|
102
|
-
context: RunContext | None, path: str,
|
|
93
|
+
context: RunContext | None, path: str, replacements: List[Dict[str, str]]
|
|
103
94
|
) -> Dict[str, Any]:
|
|
104
95
|
"""Robust replacement engine with explicit edge‑case reporting."""
|
|
105
96
|
file_path = os.path.abspath(path)
|
|
106
|
-
preview = (diff[:400] + "…") if len(diff) > 400 else diff # for logs / errors
|
|
107
|
-
diff_text = ""
|
|
108
|
-
try:
|
|
109
|
-
if not os.path.exists(file_path):
|
|
110
|
-
return {"error": f"File '{file_path}' does not exist", "diff": preview}
|
|
111
97
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
payload = json.loads(diff)
|
|
115
|
-
except json.JSONDecodeError:
|
|
116
|
-
try:
|
|
117
|
-
payload = json.loads(diff.replace("'", '"'))
|
|
118
|
-
except Exception as exc:
|
|
119
|
-
return {
|
|
120
|
-
"error": "Could not parse diff as JSON.",
|
|
121
|
-
"reason": str(exc),
|
|
122
|
-
"received": preview,
|
|
123
|
-
"diff": preview,
|
|
124
|
-
}
|
|
125
|
-
if not isinstance(payload, dict):
|
|
126
|
-
try:
|
|
127
|
-
payload = ast.literal_eval(diff)
|
|
128
|
-
except Exception as exc:
|
|
129
|
-
return {
|
|
130
|
-
"error": "Diff is neither valid JSON nor Python literal.",
|
|
131
|
-
"reason": str(exc),
|
|
132
|
-
"received": preview,
|
|
133
|
-
"diff": preview,
|
|
134
|
-
}
|
|
98
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
99
|
+
original = f.read()
|
|
135
100
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"received": preview,
|
|
141
|
-
"diff": preview,
|
|
142
|
-
}
|
|
101
|
+
modified = original
|
|
102
|
+
for rep in replacements:
|
|
103
|
+
old_snippet = rep.get("old_str", "")
|
|
104
|
+
new_snippet = rep.get("new_str", "")
|
|
143
105
|
|
|
144
|
-
|
|
145
|
-
|
|
106
|
+
if old_snippet and old_snippet in modified:
|
|
107
|
+
modified = modified.replace(old_snippet, new_snippet)
|
|
108
|
+
continue
|
|
146
109
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
modified = modified.replace(rep.get("old_str", ""), rep.get("new_str", ""))
|
|
110
|
+
orig_lines = modified.splitlines()
|
|
111
|
+
loc, score = _find_best_window(orig_lines, old_snippet)
|
|
150
112
|
|
|
151
|
-
if
|
|
152
|
-
# ── Explicit no‑op edge case ────────────────────────────────
|
|
153
|
-
console.print(
|
|
154
|
-
"[bold yellow]No changes to apply – proposed content is identical.[/bold yellow]"
|
|
155
|
-
)
|
|
113
|
+
if loc is None:
|
|
156
114
|
return {
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"diff": "", # empty so _print_diff prints placeholder
|
|
115
|
+
"error": "No suitable match in file (JW < 0.95)",
|
|
116
|
+
"jw_score": score,
|
|
117
|
+
"received": old_snippet,
|
|
118
|
+
"diff": "",
|
|
162
119
|
}
|
|
163
120
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
121
|
+
start, end = loc
|
|
122
|
+
modified = (
|
|
123
|
+
"\n".join(orig_lines[:start])
|
|
124
|
+
+ "\n"
|
|
125
|
+
+ new_snippet.rstrip("\n")
|
|
126
|
+
+ "\n"
|
|
127
|
+
+ "\n".join(orig_lines[end:])
|
|
172
128
|
)
|
|
173
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
174
|
-
f.write(modified)
|
|
175
|
-
return {
|
|
176
|
-
"success": True,
|
|
177
|
-
"path": file_path,
|
|
178
|
-
"message": "Replacements applied.",
|
|
179
|
-
"changed": True,
|
|
180
|
-
"diff": diff_text,
|
|
181
|
-
}
|
|
182
129
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
130
|
+
if modified == original:
|
|
131
|
+
console.print(
|
|
132
|
+
"[bold yellow]No changes to apply – proposed content is identical.[/bold yellow]"
|
|
133
|
+
)
|
|
186
134
|
return {
|
|
187
|
-
"
|
|
135
|
+
"success": False,
|
|
188
136
|
"path": file_path,
|
|
189
|
-
"
|
|
137
|
+
"message": "No changes to apply.",
|
|
138
|
+
"changed": False,
|
|
139
|
+
"diff": "",
|
|
190
140
|
}
|
|
191
141
|
|
|
142
|
+
diff_text = "".join(
|
|
143
|
+
difflib.unified_diff(
|
|
144
|
+
original.splitlines(keepends=True),
|
|
145
|
+
modified.splitlines(keepends=True),
|
|
146
|
+
fromfile=f"a/{os.path.basename(file_path)}",
|
|
147
|
+
tofile=f"b/{os.path.basename(file_path)}",
|
|
148
|
+
n=3,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
152
|
+
f.write(modified)
|
|
153
|
+
return {
|
|
154
|
+
"success": True,
|
|
155
|
+
"path": file_path,
|
|
156
|
+
"message": "Replacements applied.",
|
|
157
|
+
"changed": True,
|
|
158
|
+
"diff": diff_text,
|
|
159
|
+
}
|
|
160
|
+
|
|
192
161
|
|
|
193
162
|
def _write_to_file(
|
|
194
163
|
context: RunContext | None,
|
|
@@ -209,10 +178,9 @@ def _write_to_file(
|
|
|
209
178
|
"diff": "",
|
|
210
179
|
}
|
|
211
180
|
|
|
212
|
-
# --- NEW: build diff before writing ---
|
|
213
181
|
diff_lines = difflib.unified_diff(
|
|
214
|
-
[] if not exists else [""],
|
|
215
|
-
content.splitlines(keepends=True),
|
|
182
|
+
[] if not exists else [""],
|
|
183
|
+
content.splitlines(keepends=True),
|
|
216
184
|
fromfile="/dev/null" if not exists else f"a/{os.path.basename(file_path)}",
|
|
217
185
|
tofile=f"b/{os.path.basename(file_path)}",
|
|
218
186
|
n=3,
|
|
@@ -232,149 +200,118 @@ def _write_to_file(
|
|
|
232
200
|
"diff": diff_text,
|
|
233
201
|
}
|
|
234
202
|
|
|
235
|
-
except Exception as exc:
|
|
203
|
+
except Exception as exc:
|
|
236
204
|
_log_error("Unhandled exception in write_to_file", exc)
|
|
237
205
|
return {"error": str(exc), "diff": ""}
|
|
238
206
|
|
|
239
207
|
|
|
240
|
-
def
|
|
241
|
-
context: RunContext | None, path: str, diff: str
|
|
242
|
-
) -> Dict[str, Any]:
|
|
243
|
-
"""Robust replacement engine with explicit edge‑case reporting."""
|
|
244
|
-
file_path = os.path.abspath(path)
|
|
245
|
-
preview = (diff[:400] + "…") if len(diff) > 400 else diff # for logs / errors
|
|
246
|
-
diff_text = ""
|
|
247
|
-
try:
|
|
248
|
-
if not os.path.exists(file_path):
|
|
249
|
-
return {"error": f"File '{file_path}' does not exist", "diff": preview}
|
|
250
|
-
|
|
251
|
-
# ── Parse diff payload (tolerate single quotes) ──────────────────
|
|
252
|
-
try:
|
|
253
|
-
payload = json.loads(diff)
|
|
254
|
-
except json.JSONDecodeError:
|
|
255
|
-
try:
|
|
256
|
-
payload = json.loads(diff.replace("'", '"'))
|
|
257
|
-
except Exception as exc:
|
|
258
|
-
return {
|
|
259
|
-
"error": "Could not parse diff as JSON.",
|
|
260
|
-
"reason": str(exc),
|
|
261
|
-
"received": preview,
|
|
262
|
-
"diff": preview,
|
|
263
|
-
}
|
|
264
|
-
if not isinstance(payload, dict):
|
|
265
|
-
try:
|
|
266
|
-
payload = ast.literal_eval(diff)
|
|
267
|
-
except Exception as exc:
|
|
268
|
-
return {
|
|
269
|
-
"error": "Diff is neither valid JSON nor Python literal.",
|
|
270
|
-
"reason": str(exc),
|
|
271
|
-
"received": preview,
|
|
272
|
-
"diff": preview,
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
replacements: List[Dict[str, str]] = payload.get("replacements", [])
|
|
276
|
-
if not replacements:
|
|
277
|
-
return {
|
|
278
|
-
"error": "No valid replacements found in diff.",
|
|
279
|
-
"received": preview,
|
|
280
|
-
"diff": preview,
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
284
|
-
original = f.read()
|
|
285
|
-
|
|
286
|
-
modified = original
|
|
287
|
-
for rep in replacements:
|
|
288
|
-
modified = modified.replace(rep.get("old_str", ""), rep.get("new_str", ""))
|
|
289
|
-
|
|
290
|
-
if modified == original:
|
|
291
|
-
# ── Explicit no‑op edge case ────────────────────────────────
|
|
292
|
-
console.print(
|
|
293
|
-
"[bold yellow]No changes to apply – proposed content is identical.[/bold yellow]"
|
|
294
|
-
)
|
|
295
|
-
return {
|
|
296
|
-
"success": False,
|
|
297
|
-
"path": file_path,
|
|
298
|
-
"message": "No changes to apply.",
|
|
299
|
-
"changed": False,
|
|
300
|
-
"diff": "", # empty so _print_diff prints placeholder
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
diff_text = "".join(
|
|
304
|
-
difflib.unified_diff(
|
|
305
|
-
original.splitlines(keepends=True),
|
|
306
|
-
modified.splitlines(keepends=True),
|
|
307
|
-
fromfile=f"a/{os.path.basename(file_path)}",
|
|
308
|
-
tofile=f"b/{os.path.basename(file_path)}",
|
|
309
|
-
n=3,
|
|
310
|
-
)
|
|
311
|
-
)
|
|
312
|
-
with open(file_path, "w", encoding="utf-8") as f:
|
|
313
|
-
f.write(modified)
|
|
314
|
-
return {
|
|
315
|
-
"success": True,
|
|
316
|
-
"path": file_path,
|
|
317
|
-
"message": "Replacements applied.",
|
|
318
|
-
"changed": True,
|
|
319
|
-
"diff": diff_text,
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
except Exception as exc: # noqa: BLE001
|
|
323
|
-
# ── Explicit error edge case ────────────────────────────────────
|
|
324
|
-
_log_error("Unhandled exception in replace_in_file", exc)
|
|
325
|
-
return {
|
|
326
|
-
"error": str(exc),
|
|
327
|
-
"path": file_path,
|
|
328
|
-
"diff": preview, # show the exact diff input that blew up
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
# ---------------------------------------------------------------------------
|
|
333
|
-
# Agent-tool registration
|
|
334
|
-
# ---------------------------------------------------------------------------
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def register_file_modifications_tools(agent): # noqa: C901 – a bit long but clear
|
|
208
|
+
def register_file_modifications_tools(agent):
|
|
338
209
|
"""Attach file-editing tools to *agent* with mandatory diff rendering."""
|
|
339
210
|
|
|
340
|
-
# ------------------------------------------------------------------
|
|
341
|
-
# Delete snippet
|
|
342
|
-
# ------------------------------------------------------------------
|
|
343
|
-
@agent.tool
|
|
344
211
|
def delete_snippet_from_file(
|
|
345
212
|
context: RunContext, file_path: str, snippet: str
|
|
346
213
|
) -> Dict[str, Any]:
|
|
347
214
|
console.log(f"🗑️ Deleting snippet from file [bold red]{file_path}[/bold red]")
|
|
348
215
|
res = _delete_snippet_from_file(context, file_path, snippet)
|
|
349
|
-
|
|
216
|
+
diff = res.get("diff", "")
|
|
217
|
+
if diff:
|
|
218
|
+
_print_diff(diff)
|
|
350
219
|
return res
|
|
351
220
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
@agent.tool
|
|
356
|
-
def write_to_file(context: RunContext, path: str, content: str) -> Dict[str, Any]:
|
|
221
|
+
def write_to_file(
|
|
222
|
+
context: RunContext, path: str, content: str, overwrite: bool
|
|
223
|
+
) -> Dict[str, Any]:
|
|
357
224
|
console.log(f"✏️ Writing file [bold blue]{path}[/bold blue]")
|
|
358
|
-
res = _write_to_file(context, path, content, overwrite=
|
|
359
|
-
|
|
225
|
+
res = _write_to_file(context, path, content, overwrite=overwrite)
|
|
226
|
+
diff = res.get("diff", "")
|
|
227
|
+
if diff:
|
|
228
|
+
_print_diff(diff)
|
|
360
229
|
return res
|
|
361
230
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
@agent.tool
|
|
366
|
-
def replace_in_file(context: RunContext, path: str, diff: str) -> Dict[str, Any]:
|
|
231
|
+
def replace_in_file(
|
|
232
|
+
context: RunContext, path: str, replacements: List[Dict[str, str]]
|
|
233
|
+
) -> Dict[str, Any]:
|
|
367
234
|
console.log(f"♻️ Replacing text in [bold yellow]{path}[/bold yellow]")
|
|
368
|
-
res = _replace_in_file(context, path,
|
|
369
|
-
|
|
235
|
+
res = _replace_in_file(context, path, replacements)
|
|
236
|
+
diff = res.get("diff", "")
|
|
237
|
+
if diff:
|
|
238
|
+
_print_diff(diff)
|
|
370
239
|
return res
|
|
371
240
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
241
|
+
@agent.tool(retries=5)
|
|
242
|
+
def edit_file(context: RunContext, path: str, diff: str) -> Dict[str, Any]:
|
|
243
|
+
"""
|
|
244
|
+
Unified file editing tool that can:
|
|
245
|
+
- Create/write a new file when the target does not exist (using raw content or a JSON payload with a "content" key)
|
|
246
|
+
- Replace text within an existing file via a JSON payload with "replacements" (delegates to internal replace logic)
|
|
247
|
+
- Delete a snippet from an existing file via a JSON payload with "delete_snippet"
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
path : str
|
|
251
|
+
Path to the target file (relative or absolute)
|
|
252
|
+
diff : str
|
|
253
|
+
Either:
|
|
254
|
+
* Raw file content (for file creation)
|
|
255
|
+
* A JSON string with one of the following shapes:
|
|
256
|
+
{"content": "full file contents", "overwrite": true}
|
|
257
|
+
{"replacements": [ {"old_str": "foo", "new_str": "bar"}, ... ] }
|
|
258
|
+
{"delete_snippet": "text to remove"}
|
|
259
|
+
The function auto-detects the payload type and routes to the appropriate internal helper.
|
|
260
|
+
"""
|
|
261
|
+
console.print("\n[bold white on blue] EDIT FILE [/bold white on blue]")
|
|
262
|
+
file_path = os.path.abspath(path)
|
|
263
|
+
try:
|
|
264
|
+
parsed_payload = json.loads(diff)
|
|
265
|
+
except json.JSONDecodeError:
|
|
266
|
+
try:
|
|
267
|
+
console.print(
|
|
268
|
+
"[bold yellow] JSON Parsing Failed! TRYING TO REPAIR! [/bold yellow]"
|
|
269
|
+
)
|
|
270
|
+
parsed_payload = json.loads(repair_json(diff))
|
|
271
|
+
console.print(
|
|
272
|
+
"[bold green on cyan] SUCCESS - WOOF! [/bold green on cyan]"
|
|
273
|
+
)
|
|
274
|
+
except Exception as e:
|
|
275
|
+
console.print(
|
|
276
|
+
f"[bold red] Unable to parse diff [/bold red] -- {str(e)}"
|
|
277
|
+
)
|
|
278
|
+
return {
|
|
279
|
+
"success": False,
|
|
280
|
+
"path": file_path,
|
|
281
|
+
"message": f"Unable to parse diff JSON -- {str(e)}",
|
|
282
|
+
"changed": False,
|
|
283
|
+
"diff": "",
|
|
284
|
+
}
|
|
285
|
+
if isinstance(parsed_payload, dict):
|
|
286
|
+
if "delete_snippet" in parsed_payload:
|
|
287
|
+
snippet = parsed_payload["delete_snippet"]
|
|
288
|
+
return delete_snippet_from_file(context, file_path, snippet)
|
|
289
|
+
if "replacements" in parsed_payload:
|
|
290
|
+
replacements = parsed_payload["replacements"]
|
|
291
|
+
return replace_in_file(context, file_path, replacements)
|
|
292
|
+
if "content" in parsed_payload:
|
|
293
|
+
content = parsed_payload["content"]
|
|
294
|
+
overwrite = bool(parsed_payload.get("overwrite", False))
|
|
295
|
+
file_exists = os.path.exists(file_path)
|
|
296
|
+
if file_exists and not overwrite:
|
|
297
|
+
return {
|
|
298
|
+
"success": False,
|
|
299
|
+
"path": file_path,
|
|
300
|
+
"message": f"File '{file_path}' exists. Set 'overwrite': true to replace.",
|
|
301
|
+
"changed": False,
|
|
302
|
+
}
|
|
303
|
+
return write_to_file(context, file_path, content, overwrite)
|
|
304
|
+
console.print(
|
|
305
|
+
"[bold red] Unable to route file modification tool call to sub-tool [/bold red]"
|
|
306
|
+
)
|
|
307
|
+
console.print("Inputs: ", path, diff)
|
|
308
|
+
return {
|
|
309
|
+
"success": False,
|
|
310
|
+
"path": file_path,
|
|
311
|
+
"message": "Wasn't able to route file modification to the right sub-tool!",
|
|
312
|
+
"changed": False,
|
|
313
|
+
}
|
|
314
|
+
|
|
378
315
|
@agent.tool
|
|
379
316
|
def delete_file(context: RunContext, file_path: str) -> Dict[str, Any]:
|
|
380
317
|
console.log(f"🗑️ Deleting file [bold red]{file_path}[/bold red]")
|
|
@@ -385,7 +322,6 @@ def register_file_modifications_tools(agent): # noqa: C901 – a bit long but c
|
|
|
385
322
|
else:
|
|
386
323
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
387
324
|
original = f.read()
|
|
388
|
-
# Diff: original lines → empty file
|
|
389
325
|
diff_text = "".join(
|
|
390
326
|
difflib.unified_diff(
|
|
391
327
|
original.splitlines(keepends=True),
|
|
@@ -403,7 +339,7 @@ def register_file_modifications_tools(agent): # noqa: C901 – a bit long but c
|
|
|
403
339
|
"changed": True,
|
|
404
340
|
"diff": diff_text,
|
|
405
341
|
}
|
|
406
|
-
except Exception as exc:
|
|
342
|
+
except Exception as exc:
|
|
407
343
|
_log_error("Unhandled exception in delete_file", exc)
|
|
408
344
|
res = {"error": str(exc), "diff": ""}
|
|
409
345
|
_print_diff(res.get("diff", ""))
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# file_operations.py
|
|
2
|
-
import os
|
|
3
2
|
import fnmatch
|
|
4
|
-
|
|
5
|
-
from
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, List
|
|
5
|
+
|
|
6
6
|
from pydantic_ai import RunContext
|
|
7
7
|
|
|
8
|
+
from code_puppy.tools.common import console
|
|
9
|
+
|
|
8
10
|
# ---------------------------------------------------------------------------
|
|
9
11
|
# Module-level helper functions (exposed for unit tests _and_ used as tools)
|
|
10
12
|
# ---------------------------------------------------------------------------
|
|
@@ -40,29 +42,148 @@ def should_ignore_path(path: str) -> bool:
|
|
|
40
42
|
def _list_files(
|
|
41
43
|
context: RunContext, directory: str = ".", recursive: bool = True
|
|
42
44
|
) -> List[Dict[str, Any]]:
|
|
43
|
-
|
|
45
|
+
results = []
|
|
46
|
+
directory = os.path.abspath(directory)
|
|
47
|
+
console.print("\n[bold white on blue] DIRECTORY LISTING [/bold white on blue]")
|
|
44
48
|
console.print(
|
|
45
|
-
f"\
|
|
49
|
+
f"\U0001f4c2 [bold cyan]{directory}[/bold cyan] [dim](recursive={recursive})[/dim]"
|
|
46
50
|
)
|
|
47
51
|
console.print("[dim]" + "-" * 60 + "[/dim]")
|
|
48
|
-
|
|
49
|
-
results: List[Dict[str, Any]] = []
|
|
50
|
-
if not os.path.exists(directory) or not os.path.isdir(directory):
|
|
52
|
+
if not os.path.exists(directory):
|
|
51
53
|
console.print(
|
|
52
|
-
f"[bold red]Directory '{directory}' does not exist
|
|
54
|
+
f"[bold red]Error:[/bold red] Directory '{directory}' does not exist"
|
|
53
55
|
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
console.print("[dim]" + "-" * 60 + "[/dim]\n")
|
|
57
|
+
return [{"error": f"Directory '{directory}' does not exist"}]
|
|
58
|
+
if not os.path.isdir(directory):
|
|
59
|
+
console.print(f"[bold red]Error:[/bold red] '{directory}' is not a directory")
|
|
60
|
+
console.print("[dim]" + "-" * 60 + "[/dim]\n")
|
|
61
|
+
return [{"error": f"'{directory}' is not a directory"}]
|
|
62
|
+
folder_structure = {}
|
|
63
|
+
file_list = []
|
|
57
64
|
for root, dirs, files in os.walk(directory):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
dirs[:] = [d for d in dirs if not should_ignore_path(os.path.join(root, d))]
|
|
66
|
+
rel_path = os.path.relpath(root, directory)
|
|
67
|
+
depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
|
|
68
|
+
if rel_path == ".":
|
|
69
|
+
rel_path = ""
|
|
70
|
+
if rel_path:
|
|
71
|
+
dir_path = os.path.join(directory, rel_path)
|
|
72
|
+
results.append(
|
|
73
|
+
{
|
|
74
|
+
"path": rel_path,
|
|
75
|
+
"type": "directory",
|
|
76
|
+
"size": 0,
|
|
77
|
+
"full_path": dir_path,
|
|
78
|
+
"depth": depth,
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
folder_structure[rel_path] = {
|
|
82
|
+
"path": rel_path,
|
|
83
|
+
"depth": depth,
|
|
84
|
+
"full_path": dir_path,
|
|
85
|
+
}
|
|
86
|
+
for file in files:
|
|
87
|
+
file_path = os.path.join(root, file)
|
|
88
|
+
if should_ignore_path(file_path):
|
|
89
|
+
continue
|
|
90
|
+
rel_file_path = os.path.join(rel_path, file) if rel_path else file
|
|
91
|
+
try:
|
|
92
|
+
size = os.path.getsize(file_path)
|
|
93
|
+
file_info = {
|
|
94
|
+
"path": rel_file_path,
|
|
95
|
+
"type": "file",
|
|
96
|
+
"size": size,
|
|
97
|
+
"full_path": file_path,
|
|
98
|
+
"depth": depth,
|
|
99
|
+
}
|
|
100
|
+
results.append(file_info)
|
|
101
|
+
file_list.append(file_info)
|
|
102
|
+
except (FileNotFoundError, PermissionError):
|
|
103
|
+
continue
|
|
64
104
|
if not recursive:
|
|
65
105
|
break
|
|
106
|
+
|
|
107
|
+
def format_size(size_bytes):
|
|
108
|
+
if size_bytes < 1024:
|
|
109
|
+
return f"{size_bytes} B"
|
|
110
|
+
elif size_bytes < 1024 * 1024:
|
|
111
|
+
return f"{size_bytes / 1024:.1f} KB"
|
|
112
|
+
elif size_bytes < 1024 * 1024 * 1024:
|
|
113
|
+
return f"{size_bytes / (1024 * 1024):.1f} MB"
|
|
114
|
+
else:
|
|
115
|
+
return f"{size_bytes / (1024 * 1024 * 1024):.1f} GB"
|
|
116
|
+
|
|
117
|
+
def get_file_icon(file_path):
|
|
118
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
119
|
+
if ext in [".py", ".pyw"]:
|
|
120
|
+
return "\U0001f40d"
|
|
121
|
+
elif ext in [".js", ".jsx", ".ts", ".tsx"]:
|
|
122
|
+
return "\U0001f4dc"
|
|
123
|
+
elif ext in [".html", ".htm", ".xml"]:
|
|
124
|
+
return "\U0001f310"
|
|
125
|
+
elif ext in [".css", ".scss", ".sass"]:
|
|
126
|
+
return "\U0001f3a8"
|
|
127
|
+
elif ext in [".md", ".markdown", ".rst"]:
|
|
128
|
+
return "\U0001f4dd"
|
|
129
|
+
elif ext in [".json", ".yaml", ".yml", ".toml"]:
|
|
130
|
+
return "\u2699\ufe0f"
|
|
131
|
+
elif ext in [".jpg", ".jpeg", ".png", ".gif", ".svg", ".webp"]:
|
|
132
|
+
return "\U0001f5bc\ufe0f"
|
|
133
|
+
elif ext in [".mp3", ".wav", ".ogg", ".flac"]:
|
|
134
|
+
return "\U0001f3b5"
|
|
135
|
+
elif ext in [".mp4", ".avi", ".mov", ".webm"]:
|
|
136
|
+
return "\U0001f3ac"
|
|
137
|
+
elif ext in [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"]:
|
|
138
|
+
return "\U0001f4c4"
|
|
139
|
+
elif ext in [".zip", ".tar", ".gz", ".rar", ".7z"]:
|
|
140
|
+
return "\U0001f4e6"
|
|
141
|
+
elif ext in [".exe", ".dll", ".so", ".dylib"]:
|
|
142
|
+
return "\u26a1"
|
|
143
|
+
else:
|
|
144
|
+
return "\U0001f4c4"
|
|
145
|
+
|
|
146
|
+
if results:
|
|
147
|
+
files = sorted(
|
|
148
|
+
[f for f in results if f["type"] == "file"], key=lambda x: x["path"]
|
|
149
|
+
)
|
|
150
|
+
console.print(
|
|
151
|
+
f"\U0001f4c1 [bold blue]{os.path.basename(directory) or directory}[/bold blue]"
|
|
152
|
+
)
|
|
153
|
+
all_items = sorted(results, key=lambda x: x["path"])
|
|
154
|
+
parent_dirs_with_content = set()
|
|
155
|
+
for i, item in enumerate(all_items):
|
|
156
|
+
if item["type"] == "directory" and not item["path"]:
|
|
157
|
+
continue
|
|
158
|
+
if os.sep in item["path"]:
|
|
159
|
+
parent_path = os.path.dirname(item["path"])
|
|
160
|
+
parent_dirs_with_content.add(parent_path)
|
|
161
|
+
depth = item["path"].count(os.sep) + 1 if item["path"] else 0
|
|
162
|
+
prefix = ""
|
|
163
|
+
for d in range(depth):
|
|
164
|
+
if d == depth - 1:
|
|
165
|
+
prefix += "\u2514\u2500\u2500 "
|
|
166
|
+
else:
|
|
167
|
+
prefix += " "
|
|
168
|
+
name = os.path.basename(item["path"]) or item["path"]
|
|
169
|
+
if item["type"] == "directory":
|
|
170
|
+
console.print(f"{prefix}\U0001f4c1 [bold blue]{name}/[/bold blue]")
|
|
171
|
+
else:
|
|
172
|
+
icon = get_file_icon(item["path"])
|
|
173
|
+
size_str = format_size(item["size"])
|
|
174
|
+
console.print(
|
|
175
|
+
f"{prefix}{icon} [green]{name}[/green] [dim]({size_str})[/dim]"
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
console.print("[yellow]Directory is empty[/yellow]")
|
|
179
|
+
dir_count = sum(1 for item in results if item["type"] == "directory")
|
|
180
|
+
file_count = sum(1 for item in results if item["type"] == "file")
|
|
181
|
+
total_size = sum(item["size"] for item in results if item["type"] == "file")
|
|
182
|
+
console.print("\n[bold cyan]Summary:[/bold cyan]")
|
|
183
|
+
console.print(
|
|
184
|
+
f"\U0001f4c1 [blue]{dir_count} directories[/blue], \U0001f4c4 [green]{file_count} files[/green] [dim]({format_size(total_size)} total)[/dim]"
|
|
185
|
+
)
|
|
186
|
+
console.print("[dim]" + "-" * 60 + "[/dim]\n")
|
|
66
187
|
return results
|
|
67
188
|
|
|
68
189
|
|
code_puppy/tools/web_search.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-puppy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.56
|
|
4
4
|
Summary: Code generation agent
|
|
5
5
|
Author: Michael Pfaffenberger
|
|
6
6
|
License: MIT
|
|
@@ -16,6 +16,7 @@ Requires-Python: >=3.10
|
|
|
16
16
|
Requires-Dist: bs4>=0.0.2
|
|
17
17
|
Requires-Dist: httpx-limiter>=0.3.0
|
|
18
18
|
Requires-Dist: httpx>=0.24.1
|
|
19
|
+
Requires-Dist: json-repair>=0.46.2
|
|
19
20
|
Requires-Dist: logfire>=0.7.1
|
|
20
21
|
Requires-Dist: pathspec>=0.11.0
|
|
21
22
|
Requires-Dist: prompt-toolkit>=3.0.38
|
|
@@ -23,6 +24,7 @@ Requires-Dist: pydantic-ai>=0.1.0
|
|
|
23
24
|
Requires-Dist: pydantic>=2.4.0
|
|
24
25
|
Requires-Dist: pytest-cov>=6.1.1
|
|
25
26
|
Requires-Dist: python-dotenv>=1.0.0
|
|
27
|
+
Requires-Dist: rapidfuzz>=3.13.0
|
|
26
28
|
Requires-Dist: rich>=13.4.2
|
|
27
29
|
Requires-Dist: ruff>=0.11.11
|
|
28
30
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
code_puppy/__init__.py,sha256=-ANvE6Xe5NlWDIRCIfL1x-rgtCZ6zM2Ye9NphFoULSY,82
|
|
2
|
+
code_puppy/agent.py,sha256=e3DBLW6dq30VuQdm5CVfLO81cU4ilyLJIz6rBj5U7pw,3239
|
|
3
|
+
code_puppy/agent_prompts.py,sha256=bT8m7UCFU56yYdMtnB9xeiPtMwU3WA63d265PclCzb4,6774
|
|
4
|
+
code_puppy/config.py,sha256=LTcZe5eHTT0zq-YJzSeNg8LCwhHb1HGN4tOkITtb7Eo,3941
|
|
5
|
+
code_puppy/main.py,sha256=qDbDB123MAv5r_kH91x1LxUROI9I28y2tvuYo2YxEbQ,10323
|
|
6
|
+
code_puppy/model_factory.py,sha256=SE4osZ2_BEzKdIlBT4nNroWlTWNc5byOFGMO9erN5N4,11646
|
|
7
|
+
code_puppy/models.json,sha256=7H-y97YK9BXhag5wJU19rtg24JtZWYx60RsBLBW3WiI,2162
|
|
8
|
+
code_puppy/session_memory.py,sha256=4sgAAjbXdLSi8hETpd56tgtrG6hqMUuZWDlJOu6BQjA,2735
|
|
9
|
+
code_puppy/version_checker.py,sha256=aRGulzuY4C4CdFvU1rITduyL-1xTFsn4GiD1uSfOl_Y,396
|
|
10
|
+
code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
|
|
11
|
+
code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
|
|
12
|
+
code_puppy/command_line/meta_command_handler.py,sha256=5jORzgSEmJBP8ixGIz8ET5Su9taPJZpQibe732LmOsg,5571
|
|
13
|
+
code_puppy/command_line/model_picker_completion.py,sha256=NkyZZG7IhcVWSJ3ADytwCA5f8DpNeVs759Qtqs4fQtY,3733
|
|
14
|
+
code_puppy/command_line/prompt_toolkit_completion.py,sha256=wxz8xCBge__EAxJRXYUCt9FoDpHog1QZ6RqpvoQP7O4,7904
|
|
15
|
+
code_puppy/command_line/utils.py,sha256=7eyxDHjPjPB9wGDJQQcXV_zOsGdYsFgI0SGCetVmTqE,1251
|
|
16
|
+
code_puppy/tools/__init__.py,sha256=J_HCbkivdr1rP5vfucxttXhGmTBx0S2LNoDMrbaE-Fc,558
|
|
17
|
+
code_puppy/tools/code_map.py,sha256=5vzKBUddY0z9kMfHZmLiewUMJofDOONJIaXCWVhbE5E,3201
|
|
18
|
+
code_puppy/tools/command_runner.py,sha256=6Ej2axH-b4ZGPLxOWg9kfW75qbftAecCFqWEtw3wEB8,6540
|
|
19
|
+
code_puppy/tools/common.py,sha256=v-qT0_OEPkH_m4bT70BaMszpssc4wYkPlf7uMooH3s8,1309
|
|
20
|
+
code_puppy/tools/file_modifications.py,sha256=vf5I-X_i7Q75ShKalo9zZyKD6nEWSy_PIJKHpOwT93I,13161
|
|
21
|
+
code_puppy/tools/file_operations.py,sha256=-mo07EB6Rp_eDEFWqdJblj1Unz6flAsSY2ZNIxYzBiM,11595
|
|
22
|
+
code_puppy/tools/web_search.py,sha256=sA2ierjuuYA517-uhb5s53SgeVsyOe1nExoZsrU1Fps,1284
|
|
23
|
+
code_puppy-0.0.56.data/data/code_puppy/models.json,sha256=7H-y97YK9BXhag5wJU19rtg24JtZWYx60RsBLBW3WiI,2162
|
|
24
|
+
code_puppy-0.0.56.dist-info/METADATA,sha256=NdJW4oObERVDmbjc67K_vdzmX0FyaApwNrUdleLXnSg,4784
|
|
25
|
+
code_puppy-0.0.56.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
+
code_puppy-0.0.56.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
|
|
27
|
+
code_puppy-0.0.56.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
28
|
+
code_puppy-0.0.56.dist-info/RECORD,,
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
code_puppy/__init__.py,sha256=-ANvE6Xe5NlWDIRCIfL1x-rgtCZ6zM2Ye9NphFoULSY,82
|
|
2
|
-
code_puppy/agent.py,sha256=yA2247YbmBdff37joH8lRDKFk-iYreCugjY4qrdmsgA,3238
|
|
3
|
-
code_puppy/agent_prompts.py,sha256=mUt9a430x0aYbnCw4L6t-Wst1_0FcqKYka_RHtnRog0,6774
|
|
4
|
-
code_puppy/config.py,sha256=Mn9VWj8Ux-qnl636BH0jE1tuM-HQ6bYmRaozMg-vbg8,3941
|
|
5
|
-
code_puppy/main.py,sha256=mLnECoA5b20Jb_9PUIznsMKOmChIjvX0lEpT69Dx7y4,10370
|
|
6
|
-
code_puppy/model_factory.py,sha256=AtCBAWEK6OmPHL79HG3J-MxvlMbXJ9s4SBNblcZKemM,11645
|
|
7
|
-
code_puppy/models.json,sha256=7H-y97YK9BXhag5wJU19rtg24JtZWYx60RsBLBW3WiI,2162
|
|
8
|
-
code_puppy/session_memory.py,sha256=CODAMmSsrxh8N9x_dXLryOSW3GnBXJ70auJnHm4m5Z8,2735
|
|
9
|
-
code_puppy/version_checker.py,sha256=aRGulzuY4C4CdFvU1rITduyL-1xTFsn4GiD1uSfOl_Y,396
|
|
10
|
-
code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
|
|
11
|
-
code_puppy/command_line/file_path_completion.py,sha256=WSGpUO5PwpqxxJAhdjzUEWjJCxGWPUl5GU95KTg_Y40,2930
|
|
12
|
-
code_puppy/command_line/meta_command_handler.py,sha256=XqidNJYaoYMaHcFI85RwwKYElEhPXgedkXwauw_4C60,5569
|
|
13
|
-
code_puppy/command_line/model_picker_completion.py,sha256=QHxzhgTvbGFw0EYFJAukcWB3JTuK4Z1ZWaYvygMqfgM,3731
|
|
14
|
-
code_puppy/command_line/prompt_toolkit_completion.py,sha256=PyPo3H4CInBCbtddI91Owgwi1tKzWu9ryADOWWWMfHI,7942
|
|
15
|
-
code_puppy/command_line/utils.py,sha256=_3wEvtJbey4E4qdg4pFX29sDPucRKGTyuodKI__NVrQ,1250
|
|
16
|
-
code_puppy/tools/__init__.py,sha256=B1sHgH9mONPmWiGb6vucCIKzoRBlhM7UBxtfSRU1AEY,558
|
|
17
|
-
code_puppy/tools/code_map.py,sha256=eAIT6IKEpq4OE6gE64HOjGMgEcPeJVEGb1eBHmFwaxY,3200
|
|
18
|
-
code_puppy/tools/command_runner.py,sha256=m-0emolt81BvKuiEUBW8VPzrA1wgA_sXRJFmySPtxqA,6514
|
|
19
|
-
code_puppy/tools/common.py,sha256=qX6wWsZPU9mwHX0AS2RIGIEQtQJ_AeWZ799LUvOCbfs,146
|
|
20
|
-
code_puppy/tools/file_modifications.py,sha256=onhmNwlMlOD0BrnMH_1QGiwZQu0qAAiiN2dHRilzBHA,16124
|
|
21
|
-
code_puppy/tools/file_operations.py,sha256=yEHeVS9S3S_iGpeqF3n9448oFSknaD2DbjIDY_URF54,6755
|
|
22
|
-
code_puppy/tools/web_search.py,sha256=GvUJJUDQ_5VHkd_YJkRUWJdqr0Y-XSZIyzmUHHiVcus,1283
|
|
23
|
-
code_puppy-0.0.54.data/data/code_puppy/models.json,sha256=7H-y97YK9BXhag5wJU19rtg24JtZWYx60RsBLBW3WiI,2162
|
|
24
|
-
code_puppy-0.0.54.dist-info/METADATA,sha256=6Y47f2cg5QxI3a7hs6WWbf51UQf3fEKLdXL0f7l9fkM,4716
|
|
25
|
-
code_puppy-0.0.54.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
-
code_puppy-0.0.54.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
|
|
27
|
-
code_puppy-0.0.54.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
28
|
-
code_puppy-0.0.54.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|