code-puppy 0.0.336__py3-none-any.whl → 0.0.338__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/agents/base_agent.py +55 -30
- code_puppy/claude_cache_client.py +46 -2
- code_puppy/cli_runner.py +5 -29
- code_puppy/http_utils.py +93 -130
- code_puppy/messaging/messages.py +3 -0
- code_puppy/messaging/rich_renderer.py +13 -3
- code_puppy/model_factory.py +16 -0
- code_puppy/models.json +2 -2
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +17 -2
- code_puppy/plugins/claude_code_oauth/utils.py +126 -7
- code_puppy/terminal_utils.py +128 -1
- code_puppy/tools/command_runner.py +1 -0
- {code_puppy-0.0.336.data → code_puppy-0.0.338.data}/data/code_puppy/models.json +2 -2
- {code_puppy-0.0.336.dist-info → code_puppy-0.0.338.dist-info}/METADATA +18 -71
- {code_puppy-0.0.336.dist-info → code_puppy-0.0.338.dist-info}/RECORD +19 -19
- {code_puppy-0.0.336.data → code_puppy-0.0.338.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.336.dist-info → code_puppy-0.0.338.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.336.dist-info → code_puppy-0.0.338.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.336.dist-info → code_puppy-0.0.338.dist-info}/licenses/LICENSE +0 -0
code_puppy/agents/base_agent.py
CHANGED
|
@@ -1353,7 +1353,6 @@ class BaseAgent(ABC):
|
|
|
1353
1353
|
ToolCallPartDelta,
|
|
1354
1354
|
)
|
|
1355
1355
|
from rich.console import Console
|
|
1356
|
-
from rich.markdown import Markdown
|
|
1357
1356
|
from rich.markup import escape
|
|
1358
1357
|
|
|
1359
1358
|
from code_puppy.messaging.spinner import pause_all_spinners
|
|
@@ -1375,10 +1374,17 @@ class BaseAgent(ABC):
|
|
|
1375
1374
|
text_parts: set[int] = set() # Track which parts are text
|
|
1376
1375
|
tool_parts: set[int] = set() # Track which parts are tool calls
|
|
1377
1376
|
banner_printed: set[int] = set() # Track if banner was already printed
|
|
1378
|
-
text_buffer: dict[int, list[str]] = {} # Buffer text for final markdown render
|
|
1379
1377
|
token_count: dict[int, int] = {} # Track token count per text/tool part
|
|
1380
1378
|
did_stream_anything = False # Track if we streamed any content
|
|
1381
1379
|
|
|
1380
|
+
# Termflow streaming state for text parts
|
|
1381
|
+
from termflow import Parser as TermflowParser
|
|
1382
|
+
from termflow import Renderer as TermflowRenderer
|
|
1383
|
+
|
|
1384
|
+
termflow_parsers: dict[int, TermflowParser] = {}
|
|
1385
|
+
termflow_renderers: dict[int, TermflowRenderer] = {}
|
|
1386
|
+
termflow_line_buffers: dict[int, str] = {} # Buffer incomplete lines
|
|
1387
|
+
|
|
1382
1388
|
def _print_thinking_banner() -> None:
|
|
1383
1389
|
"""Print the THINKING banner with spinner pause and line clear."""
|
|
1384
1390
|
nonlocal did_stream_anything
|
|
@@ -1437,13 +1443,17 @@ class BaseAgent(ABC):
|
|
|
1437
1443
|
elif isinstance(part, TextPart):
|
|
1438
1444
|
streaming_parts.add(event.index)
|
|
1439
1445
|
text_parts.add(event.index)
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1446
|
+
# Initialize termflow streaming for this text part
|
|
1447
|
+
termflow_parsers[event.index] = TermflowParser()
|
|
1448
|
+
termflow_renderers[event.index] = TermflowRenderer(
|
|
1449
|
+
output=console.file, width=console.width
|
|
1450
|
+
)
|
|
1451
|
+
termflow_line_buffers[event.index] = ""
|
|
1452
|
+
# Handle initial content if present
|
|
1443
1453
|
if part.content and part.content.strip():
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1454
|
+
_print_response_banner()
|
|
1455
|
+
banner_printed.add(event.index)
|
|
1456
|
+
termflow_line_buffers[event.index] = part.content
|
|
1447
1457
|
elif isinstance(part, ToolCallPart):
|
|
1448
1458
|
streaming_parts.add(event.index)
|
|
1449
1459
|
tool_parts.add(event.index)
|
|
@@ -1459,22 +1469,29 @@ class BaseAgent(ABC):
|
|
|
1459
1469
|
delta = event.delta
|
|
1460
1470
|
if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
|
|
1461
1471
|
if delta.content_delta:
|
|
1462
|
-
# For text parts,
|
|
1472
|
+
# For text parts, stream markdown with termflow
|
|
1463
1473
|
if event.index in text_parts:
|
|
1464
1474
|
# Print banner on first content
|
|
1465
1475
|
if event.index not in banner_printed:
|
|
1466
1476
|
_print_response_banner()
|
|
1467
1477
|
banner_printed.add(event.index)
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
# Update chunk counter in place (single line)
|
|
1473
|
-
count = token_count[event.index]
|
|
1474
|
-
console.print(
|
|
1475
|
-
f" ⏳ Receiving... {count} chunks ",
|
|
1476
|
-
end="\r",
|
|
1478
|
+
|
|
1479
|
+
# Add content to line buffer
|
|
1480
|
+
termflow_line_buffers[event.index] += (
|
|
1481
|
+
delta.content_delta
|
|
1477
1482
|
)
|
|
1483
|
+
|
|
1484
|
+
# Process complete lines
|
|
1485
|
+
parser = termflow_parsers[event.index]
|
|
1486
|
+
renderer = termflow_renderers[event.index]
|
|
1487
|
+
buffer = termflow_line_buffers[event.index]
|
|
1488
|
+
|
|
1489
|
+
while "\n" in buffer:
|
|
1490
|
+
line, buffer = buffer.split("\n", 1)
|
|
1491
|
+
events_to_render = parser.parse_line(line)
|
|
1492
|
+
renderer.render_all(events_to_render)
|
|
1493
|
+
|
|
1494
|
+
termflow_line_buffers[event.index] = buffer
|
|
1478
1495
|
else:
|
|
1479
1496
|
# For thinking parts, stream immediately (dim)
|
|
1480
1497
|
if event.index not in banner_printed:
|
|
@@ -1503,19 +1520,27 @@ class BaseAgent(ABC):
|
|
|
1503
1520
|
# PartEndEvent - finish the streaming with a newline
|
|
1504
1521
|
elif isinstance(event, PartEndEvent):
|
|
1505
1522
|
if event.index in streaming_parts:
|
|
1506
|
-
# For text parts,
|
|
1523
|
+
# For text parts, finalize termflow rendering
|
|
1507
1524
|
if event.index in text_parts:
|
|
1508
|
-
#
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1525
|
+
# Render any remaining buffered content
|
|
1526
|
+
if event.index in termflow_parsers:
|
|
1527
|
+
parser = termflow_parsers[event.index]
|
|
1528
|
+
renderer = termflow_renderers[event.index]
|
|
1529
|
+
remaining = termflow_line_buffers.get(event.index, "")
|
|
1530
|
+
|
|
1531
|
+
# Parse and render any remaining partial line
|
|
1532
|
+
if remaining.strip():
|
|
1533
|
+
events_to_render = parser.parse_line(remaining)
|
|
1534
|
+
renderer.render_all(events_to_render)
|
|
1535
|
+
|
|
1536
|
+
# Finalize the parser to close any open blocks
|
|
1537
|
+
final_events = parser.finalize()
|
|
1538
|
+
renderer.render_all(final_events)
|
|
1539
|
+
|
|
1540
|
+
# Clean up termflow state
|
|
1541
|
+
del termflow_parsers[event.index]
|
|
1542
|
+
del termflow_renderers[event.index]
|
|
1543
|
+
del termflow_line_buffers[event.index]
|
|
1519
1544
|
# For tool parts, clear the chunk counter line
|
|
1520
1545
|
elif event.index in tool_parts:
|
|
1521
1546
|
# Clear the chunk counter line by printing spaces and returning
|
|
@@ -10,7 +10,7 @@ serialization, avoiding httpx/Pydantic internals.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
-
from typing import Any, Callable
|
|
13
|
+
from typing import Any, Callable, MutableMapping
|
|
14
14
|
|
|
15
15
|
import httpx
|
|
16
16
|
|
|
@@ -56,7 +56,28 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
|
|
|
56
56
|
except Exception:
|
|
57
57
|
# Swallow wrapper errors; do not break real calls.
|
|
58
58
|
pass
|
|
59
|
-
|
|
59
|
+
response = await super().send(request, *args, **kwargs)
|
|
60
|
+
try:
|
|
61
|
+
if response.status_code == 401 and not request.extensions.get(
|
|
62
|
+
"claude_oauth_refresh_attempted"
|
|
63
|
+
):
|
|
64
|
+
refreshed_token = self._refresh_claude_oauth_token()
|
|
65
|
+
if refreshed_token:
|
|
66
|
+
await response.aclose()
|
|
67
|
+
body_bytes = self._extract_body_bytes(request)
|
|
68
|
+
headers = dict(request.headers)
|
|
69
|
+
self._update_auth_headers(headers, refreshed_token)
|
|
70
|
+
retry_request = self.build_request(
|
|
71
|
+
method=request.method,
|
|
72
|
+
url=request.url,
|
|
73
|
+
headers=headers,
|
|
74
|
+
content=body_bytes,
|
|
75
|
+
)
|
|
76
|
+
retry_request.extensions["claude_oauth_refresh_attempted"] = True
|
|
77
|
+
return await super().send(retry_request, *args, **kwargs)
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
return response
|
|
60
81
|
|
|
61
82
|
@staticmethod
|
|
62
83
|
def _extract_body_bytes(request: httpx.Request) -> bytes | None:
|
|
@@ -78,6 +99,29 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
|
|
|
78
99
|
|
|
79
100
|
return None
|
|
80
101
|
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _update_auth_headers(
|
|
104
|
+
headers: MutableMapping[str, str], access_token: str
|
|
105
|
+
) -> None:
|
|
106
|
+
bearer_value = f"Bearer {access_token}"
|
|
107
|
+
if "Authorization" in headers or "authorization" in headers:
|
|
108
|
+
headers["Authorization"] = bearer_value
|
|
109
|
+
elif "x-api-key" in headers or "X-API-Key" in headers:
|
|
110
|
+
headers["x-api-key"] = access_token
|
|
111
|
+
else:
|
|
112
|
+
headers["Authorization"] = bearer_value
|
|
113
|
+
|
|
114
|
+
def _refresh_claude_oauth_token(self) -> str | None:
|
|
115
|
+
try:
|
|
116
|
+
from code_puppy.plugins.claude_code_oauth.utils import refresh_access_token
|
|
117
|
+
|
|
118
|
+
refreshed_token = refresh_access_token(force=True)
|
|
119
|
+
if refreshed_token:
|
|
120
|
+
self._update_auth_headers(self.headers, refreshed_token)
|
|
121
|
+
return refreshed_token
|
|
122
|
+
except Exception:
|
|
123
|
+
return None
|
|
124
|
+
|
|
81
125
|
@staticmethod
|
|
82
126
|
def _inject_cache_control(body: bytes) -> bytes | None:
|
|
83
127
|
try:
|
code_puppy/cli_runner.py
CHANGED
|
@@ -17,10 +17,7 @@ import traceback
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
19
|
from dbos import DBOS, DBOSConfig
|
|
20
|
-
from rich.console import Console
|
|
21
|
-
from rich.markdown import CodeBlock, Markdown
|
|
22
|
-
from rich.syntax import Syntax
|
|
23
|
-
from rich.text import Text
|
|
20
|
+
from rich.console import Console
|
|
24
21
|
|
|
25
22
|
from code_puppy import __version__, callbacks, plugins
|
|
26
23
|
from code_puppy.agents import get_current_agent
|
|
@@ -43,6 +40,7 @@ from code_puppy.keymap import (
|
|
|
43
40
|
)
|
|
44
41
|
from code_puppy.messaging import emit_info
|
|
45
42
|
from code_puppy.terminal_utils import (
|
|
43
|
+
print_truecolor_warning,
|
|
46
44
|
reset_unix_terminal,
|
|
47
45
|
reset_windows_terminal_ansi,
|
|
48
46
|
reset_windows_terminal_full,
|
|
@@ -91,7 +89,6 @@ async def main():
|
|
|
91
89
|
"command", nargs="*", help="Run a single command (deprecated, use -p instead)"
|
|
92
90
|
)
|
|
93
91
|
args = parser.parse_args()
|
|
94
|
-
from rich.console import Console
|
|
95
92
|
|
|
96
93
|
from code_puppy.messaging import (
|
|
97
94
|
RichConsoleRenderer,
|
|
@@ -146,6 +143,9 @@ async def main():
|
|
|
146
143
|
except ImportError:
|
|
147
144
|
emit_system_message("🐶 Code Puppy is Loading...")
|
|
148
145
|
|
|
146
|
+
# Check for truecolor support and warn if not available
|
|
147
|
+
print_truecolor_warning(display_console)
|
|
148
|
+
|
|
149
149
|
available_port = find_available_port()
|
|
150
150
|
if available_port is None:
|
|
151
151
|
emit_error("No available ports in range 8090-9010!")
|
|
@@ -679,8 +679,6 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
679
679
|
save_command_to_history(task)
|
|
680
680
|
|
|
681
681
|
try:
|
|
682
|
-
prettier_code_blocks()
|
|
683
|
-
|
|
684
682
|
# No need to get agent directly - use manager's run methods
|
|
685
683
|
|
|
686
684
|
# Use our custom helper to enable attachment handling with spinner support
|
|
@@ -750,28 +748,6 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
750
748
|
pass
|
|
751
749
|
|
|
752
750
|
|
|
753
|
-
def prettier_code_blocks():
|
|
754
|
-
"""Configure Rich to use prettier code block rendering."""
|
|
755
|
-
|
|
756
|
-
class SimpleCodeBlock(CodeBlock):
|
|
757
|
-
def __rich_console__(
|
|
758
|
-
self, console: Console, options: ConsoleOptions
|
|
759
|
-
) -> RenderResult:
|
|
760
|
-
code = str(self.text).rstrip()
|
|
761
|
-
yield Text(self.lexer_name, style="dim")
|
|
762
|
-
syntax = Syntax(
|
|
763
|
-
code,
|
|
764
|
-
self.lexer_name,
|
|
765
|
-
theme=self.theme,
|
|
766
|
-
background_color="default",
|
|
767
|
-
line_numbers=True,
|
|
768
|
-
)
|
|
769
|
-
yield syntax
|
|
770
|
-
yield Text(f"/{self.lexer_name}", style="dim")
|
|
771
|
-
|
|
772
|
-
Markdown.elements["fence"] = SimpleCodeBlock
|
|
773
|
-
|
|
774
|
-
|
|
775
751
|
async def run_prompt_with_attachments(
|
|
776
752
|
agent,
|
|
777
753
|
raw_prompt: str,
|
code_puppy/http_utils.py
CHANGED
|
@@ -5,10 +5,10 @@ This module provides functions for creating properly configured HTTP clients.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
-
import logging
|
|
9
8
|
import os
|
|
10
9
|
import socket
|
|
11
10
|
import time
|
|
11
|
+
from dataclasses import dataclass
|
|
12
12
|
from typing import Any, Dict, Optional, Union
|
|
13
13
|
|
|
14
14
|
import httpx
|
|
@@ -16,7 +16,69 @@ import requests
|
|
|
16
16
|
|
|
17
17
|
from code_puppy.config import get_http2
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ProxyConfig:
|
|
22
|
+
"""Configuration for proxy and SSL settings."""
|
|
23
|
+
|
|
24
|
+
verify: Union[bool, str, None]
|
|
25
|
+
trust_env: bool
|
|
26
|
+
proxy_url: str | None
|
|
27
|
+
disable_retry: bool
|
|
28
|
+
http2_enabled: bool
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _resolve_proxy_config(verify: Union[bool, str, None] = None) -> ProxyConfig:
|
|
32
|
+
"""Resolve proxy, SSL, and retry settings from environment.
|
|
33
|
+
|
|
34
|
+
This centralizes the logic for detecting proxies, determining SSL verification,
|
|
35
|
+
and checking if retry transport should be disabled.
|
|
36
|
+
"""
|
|
37
|
+
if verify is None:
|
|
38
|
+
verify = get_cert_bundle_path()
|
|
39
|
+
|
|
40
|
+
http2_enabled = get_http2()
|
|
41
|
+
|
|
42
|
+
disable_retry = os.environ.get(
|
|
43
|
+
"CODE_PUPPY_DISABLE_RETRY_TRANSPORT", ""
|
|
44
|
+
).lower() in ("1", "true", "yes")
|
|
45
|
+
|
|
46
|
+
has_proxy = bool(
|
|
47
|
+
os.environ.get("HTTP_PROXY")
|
|
48
|
+
or os.environ.get("HTTPS_PROXY")
|
|
49
|
+
or os.environ.get("http_proxy")
|
|
50
|
+
or os.environ.get("https_proxy")
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Determine trust_env and verify based on proxy/retry settings
|
|
54
|
+
if disable_retry:
|
|
55
|
+
# Test mode: disable SSL verification for proxy testing
|
|
56
|
+
verify = False
|
|
57
|
+
trust_env = True
|
|
58
|
+
elif has_proxy:
|
|
59
|
+
# Production proxy: keep SSL verification enabled
|
|
60
|
+
trust_env = True
|
|
61
|
+
else:
|
|
62
|
+
trust_env = False
|
|
63
|
+
|
|
64
|
+
# Extract proxy URL
|
|
65
|
+
proxy_url = None
|
|
66
|
+
if has_proxy:
|
|
67
|
+
proxy_url = (
|
|
68
|
+
os.environ.get("HTTPS_PROXY")
|
|
69
|
+
or os.environ.get("https_proxy")
|
|
70
|
+
or os.environ.get("HTTP_PROXY")
|
|
71
|
+
or os.environ.get("http_proxy")
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return ProxyConfig(
|
|
75
|
+
verify=verify,
|
|
76
|
+
trust_env=trust_env,
|
|
77
|
+
proxy_url=proxy_url,
|
|
78
|
+
disable_retry=disable_retry,
|
|
79
|
+
http2_enabled=http2_enabled,
|
|
80
|
+
)
|
|
81
|
+
|
|
20
82
|
|
|
21
83
|
try:
|
|
22
84
|
from .reopenable_async_client import ReopenableAsyncClient
|
|
@@ -58,14 +120,7 @@ class RetryingAsyncClient(httpx.AsyncClient):
|
|
|
58
120
|
|
|
59
121
|
for attempt in range(self.max_retries + 1):
|
|
60
122
|
try:
|
|
61
|
-
|
|
62
|
-
# But only if it's not the first attempt
|
|
63
|
-
req_to_send = request
|
|
64
|
-
if attempt > 0:
|
|
65
|
-
# httpx requests are reusable, but we need to be careful with streams
|
|
66
|
-
pass
|
|
67
|
-
|
|
68
|
-
response = await super().send(req_to_send, **kwargs)
|
|
123
|
+
response = await super().send(request, **kwargs)
|
|
69
124
|
last_response = response
|
|
70
125
|
|
|
71
126
|
# Check for retryable status
|
|
@@ -128,7 +183,7 @@ class RetryingAsyncClient(httpx.AsyncClient):
|
|
|
128
183
|
return last_response
|
|
129
184
|
|
|
130
185
|
|
|
131
|
-
def get_cert_bundle_path() -> str:
|
|
186
|
+
def get_cert_bundle_path() -> str | None:
|
|
132
187
|
# First check if SSL_CERT_FILE environment variable is set
|
|
133
188
|
ssl_cert_file = os.environ.get("SSL_CERT_FILE")
|
|
134
189
|
if ssl_cert_file and os.path.exists(ssl_cert_file):
|
|
@@ -164,66 +219,26 @@ def create_async_client(
|
|
|
164
219
|
headers: Optional[Dict[str, str]] = None,
|
|
165
220
|
retry_status_codes: tuple = (429, 502, 503, 504),
|
|
166
221
|
) -> httpx.AsyncClient:
|
|
167
|
-
|
|
168
|
-
verify = get_cert_bundle_path()
|
|
169
|
-
|
|
170
|
-
# Check if HTTP/2 is enabled in config
|
|
171
|
-
http2_enabled = get_http2()
|
|
172
|
-
|
|
173
|
-
# Check if custom retry transport should be disabled (e.g., for integration tests with proxies)
|
|
174
|
-
disable_retry_transport = os.environ.get(
|
|
175
|
-
"CODE_PUPPY_DISABLE_RETRY_TRANSPORT", ""
|
|
176
|
-
).lower() in ("1", "true", "yes")
|
|
177
|
-
|
|
178
|
-
# Check if proxy environment variables are set
|
|
179
|
-
has_proxy = bool(
|
|
180
|
-
os.environ.get("HTTP_PROXY")
|
|
181
|
-
or os.environ.get("HTTPS_PROXY")
|
|
182
|
-
or os.environ.get("http_proxy")
|
|
183
|
-
or os.environ.get("https_proxy")
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
# When retry transport is disabled (test mode), disable SSL verification
|
|
187
|
-
# for proxy testing. For production proxies, SSL should still be verified!
|
|
188
|
-
if disable_retry_transport:
|
|
189
|
-
verify = False
|
|
190
|
-
trust_env = True
|
|
191
|
-
elif has_proxy:
|
|
192
|
-
# Production proxy detected - keep SSL verification enabled for security
|
|
193
|
-
trust_env = True
|
|
194
|
-
else:
|
|
195
|
-
trust_env = False
|
|
196
|
-
|
|
197
|
-
# Extract proxy URL if needed
|
|
198
|
-
proxy_url = None
|
|
199
|
-
if has_proxy:
|
|
200
|
-
proxy_url = (
|
|
201
|
-
os.environ.get("HTTPS_PROXY")
|
|
202
|
-
or os.environ.get("https_proxy")
|
|
203
|
-
or os.environ.get("HTTP_PROXY")
|
|
204
|
-
or os.environ.get("http_proxy")
|
|
205
|
-
)
|
|
222
|
+
config = _resolve_proxy_config(verify)
|
|
206
223
|
|
|
207
|
-
|
|
208
|
-
if not disable_retry_transport:
|
|
224
|
+
if not config.disable_retry:
|
|
209
225
|
return RetryingAsyncClient(
|
|
210
226
|
retry_status_codes=retry_status_codes,
|
|
211
|
-
proxy=proxy_url,
|
|
212
|
-
verify=verify,
|
|
227
|
+
proxy=config.proxy_url,
|
|
228
|
+
verify=config.verify,
|
|
213
229
|
headers=headers or {},
|
|
214
230
|
timeout=timeout,
|
|
215
|
-
http2=http2_enabled,
|
|
216
|
-
trust_env=trust_env,
|
|
231
|
+
http2=config.http2_enabled,
|
|
232
|
+
trust_env=config.trust_env,
|
|
217
233
|
)
|
|
218
234
|
else:
|
|
219
|
-
# Regular client for testing
|
|
220
235
|
return httpx.AsyncClient(
|
|
221
|
-
proxy=proxy_url,
|
|
222
|
-
verify=verify,
|
|
236
|
+
proxy=config.proxy_url,
|
|
237
|
+
verify=config.verify,
|
|
223
238
|
headers=headers or {},
|
|
224
239
|
timeout=timeout,
|
|
225
|
-
http2=http2_enabled,
|
|
226
|
-
trust_env=trust_env,
|
|
240
|
+
http2=config.http2_enabled,
|
|
241
|
+
trust_env=config.trust_env,
|
|
227
242
|
)
|
|
228
243
|
|
|
229
244
|
|
|
@@ -273,85 +288,33 @@ def create_reopenable_async_client(
|
|
|
273
288
|
headers: Optional[Dict[str, str]] = None,
|
|
274
289
|
retry_status_codes: tuple = (429, 502, 503, 504),
|
|
275
290
|
) -> Union[ReopenableAsyncClient, httpx.AsyncClient]:
|
|
276
|
-
|
|
277
|
-
verify = get_cert_bundle_path()
|
|
278
|
-
|
|
279
|
-
# Check if HTTP/2 is enabled in config
|
|
280
|
-
http2_enabled = get_http2()
|
|
281
|
-
|
|
282
|
-
# Check if custom retry transport should be disabled (e.g., for integration tests with proxies)
|
|
283
|
-
disable_retry_transport = os.environ.get(
|
|
284
|
-
"CODE_PUPPY_DISABLE_RETRY_TRANSPORT", ""
|
|
285
|
-
).lower() in ("1", "true", "yes")
|
|
286
|
-
|
|
287
|
-
# Check if proxy environment variables are set
|
|
288
|
-
has_proxy = bool(
|
|
289
|
-
os.environ.get("HTTP_PROXY")
|
|
290
|
-
or os.environ.get("HTTPS_PROXY")
|
|
291
|
-
or os.environ.get("http_proxy")
|
|
292
|
-
or os.environ.get("https_proxy")
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
# When retry transport is disabled (test mode), disable SSL verification
|
|
296
|
-
if disable_retry_transport:
|
|
297
|
-
verify = False
|
|
298
|
-
trust_env = True
|
|
299
|
-
elif has_proxy:
|
|
300
|
-
trust_env = True
|
|
301
|
-
else:
|
|
302
|
-
trust_env = False
|
|
291
|
+
config = _resolve_proxy_config(verify)
|
|
303
292
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
)
|
|
293
|
+
base_kwargs = {
|
|
294
|
+
"proxy": config.proxy_url,
|
|
295
|
+
"verify": config.verify,
|
|
296
|
+
"headers": headers or {},
|
|
297
|
+
"timeout": timeout,
|
|
298
|
+
"http2": config.http2_enabled,
|
|
299
|
+
"trust_env": config.trust_env,
|
|
300
|
+
}
|
|
313
301
|
|
|
314
302
|
if ReopenableAsyncClient is not None:
|
|
315
|
-
# Use RetryingAsyncClient if retries are enabled
|
|
316
303
|
client_class = (
|
|
317
|
-
RetryingAsyncClient if not
|
|
304
|
+
RetryingAsyncClient if not config.disable_retry else httpx.AsyncClient
|
|
318
305
|
)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
kwargs = {
|
|
322
|
-
"proxy": proxy_url,
|
|
323
|
-
"verify": verify,
|
|
324
|
-
"headers": headers or {},
|
|
325
|
-
"timeout": timeout,
|
|
326
|
-
"http2": http2_enabled,
|
|
327
|
-
"trust_env": trust_env,
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if not disable_retry_transport:
|
|
306
|
+
kwargs = {**base_kwargs, "client_class": client_class}
|
|
307
|
+
if not config.disable_retry:
|
|
331
308
|
kwargs["retry_status_codes"] = retry_status_codes
|
|
332
|
-
|
|
333
|
-
return ReopenableAsyncClient(client_class=client_class, **kwargs)
|
|
309
|
+
return ReopenableAsyncClient(**kwargs)
|
|
334
310
|
else:
|
|
335
|
-
# Fallback to RetryingAsyncClient
|
|
336
|
-
if not
|
|
311
|
+
# Fallback to RetryingAsyncClient or plain AsyncClient
|
|
312
|
+
if not config.disable_retry:
|
|
337
313
|
return RetryingAsyncClient(
|
|
338
|
-
retry_status_codes=retry_status_codes,
|
|
339
|
-
proxy=proxy_url,
|
|
340
|
-
verify=verify,
|
|
341
|
-
headers=headers or {},
|
|
342
|
-
timeout=timeout,
|
|
343
|
-
http2=http2_enabled,
|
|
344
|
-
trust_env=trust_env,
|
|
314
|
+
retry_status_codes=retry_status_codes, **base_kwargs
|
|
345
315
|
)
|
|
346
316
|
else:
|
|
347
|
-
return httpx.AsyncClient(
|
|
348
|
-
proxy=proxy_url,
|
|
349
|
-
verify=verify,
|
|
350
|
-
headers=headers or {},
|
|
351
|
-
timeout=timeout,
|
|
352
|
-
http2=http2_enabled,
|
|
353
|
-
trust_env=trust_env,
|
|
354
|
-
)
|
|
317
|
+
return httpx.AsyncClient(**base_kwargs)
|
|
355
318
|
|
|
356
319
|
|
|
357
320
|
def is_cert_bundle_available() -> bool:
|
code_puppy/messaging/messages.py
CHANGED
|
@@ -209,6 +209,9 @@ class ShellStartMessage(BaseMessage):
|
|
|
209
209
|
default=None, description="Working directory for the command"
|
|
210
210
|
)
|
|
211
211
|
timeout: int = Field(default=60, description="Timeout in seconds")
|
|
212
|
+
background: bool = Field(
|
|
213
|
+
default=False, description="Whether command runs in background mode"
|
|
214
|
+
)
|
|
212
215
|
|
|
213
216
|
|
|
214
217
|
class ShellLineMessage(BaseMessage):
|
|
@@ -620,15 +620,25 @@ class RichConsoleRenderer:
|
|
|
620
620
|
safe_command = escape_rich_markup(msg.command)
|
|
621
621
|
# Header showing command is starting
|
|
622
622
|
banner = self._format_banner("shell_command", "SHELL COMMAND")
|
|
623
|
-
|
|
623
|
+
|
|
624
|
+
# Add background indicator if running in background mode
|
|
625
|
+
if msg.background:
|
|
626
|
+
self._console.print(
|
|
627
|
+
f"\n{banner} 🚀 [dim]$ {safe_command}[/dim] [bold magenta][BACKGROUND 🌙][/bold magenta]"
|
|
628
|
+
)
|
|
629
|
+
else:
|
|
630
|
+
self._console.print(f"\n{banner} 🚀 [dim]$ {safe_command}[/dim]")
|
|
624
631
|
|
|
625
632
|
# Show working directory if specified
|
|
626
633
|
if msg.cwd:
|
|
627
634
|
safe_cwd = escape_rich_markup(msg.cwd)
|
|
628
635
|
self._console.print(f"[dim]📂 Working directory: {safe_cwd}[/dim]")
|
|
629
636
|
|
|
630
|
-
# Show timeout
|
|
631
|
-
|
|
637
|
+
# Show timeout or background status
|
|
638
|
+
if msg.background:
|
|
639
|
+
self._console.print("[dim]⏱ Runs detached (no timeout)[/dim]")
|
|
640
|
+
else:
|
|
641
|
+
self._console.print(f"[dim]⏱ Timeout: {msg.timeout}s[/dim]")
|
|
632
642
|
|
|
633
643
|
def _render_shell_line(self, msg: ShellLineMessage) -> None:
|
|
634
644
|
"""Render shell output line preserving ANSI codes."""
|
code_puppy/model_factory.py
CHANGED
|
@@ -388,6 +388,20 @@ class ModelFactory:
|
|
|
388
388
|
return AnthropicModel(model_name=model_config["name"], provider=provider)
|
|
389
389
|
elif model_type == "claude_code":
|
|
390
390
|
url, headers, verify, api_key = get_custom_config(model_config)
|
|
391
|
+
if model_config.get("oauth_source") == "claude-code-plugin":
|
|
392
|
+
try:
|
|
393
|
+
from code_puppy.plugins.claude_code_oauth.utils import (
|
|
394
|
+
get_valid_access_token,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
refreshed_token = get_valid_access_token()
|
|
398
|
+
if refreshed_token:
|
|
399
|
+
api_key = refreshed_token
|
|
400
|
+
custom_endpoint = model_config.get("custom_endpoint")
|
|
401
|
+
if isinstance(custom_endpoint, dict):
|
|
402
|
+
custom_endpoint["api_key"] = refreshed_token
|
|
403
|
+
except ImportError:
|
|
404
|
+
pass
|
|
391
405
|
if not api_key:
|
|
392
406
|
emit_warning(
|
|
393
407
|
f"API key is not set for Claude Code endpoint; skipping model '{model_config.get('name')}'."
|
|
@@ -663,6 +677,8 @@ class ModelFactory:
|
|
|
663
677
|
f"API key is not set for Cerebras endpoint; skipping model '{model_config.get('name')}'."
|
|
664
678
|
)
|
|
665
679
|
return None
|
|
680
|
+
# Add Cerebras 3rd party integration header
|
|
681
|
+
headers["X-Cerebras-3rd-Party-Integration"] = "code-puppy"
|
|
666
682
|
client = create_async_client(headers=headers, verify=verify)
|
|
667
683
|
provider_args = dict(
|
|
668
684
|
api_key=api_key,
|
code_puppy/models.json
CHANGED
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"supported_settings": ["reasoning_effort", "verbosity"],
|
|
56
56
|
"supports_xhigh_reasoning": true
|
|
57
57
|
},
|
|
58
|
-
"Cerebras-GLM-4.
|
|
58
|
+
"Cerebras-GLM-4.7": {
|
|
59
59
|
"type": "cerebras",
|
|
60
|
-
"name": "zai-glm-4.
|
|
60
|
+
"name": "zai-glm-4.7",
|
|
61
61
|
"custom_endpoint": {
|
|
62
62
|
"url": "https://api.cerebras.ai/v1",
|
|
63
63
|
"api_key": "$CEREBRAS_API_KEY"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import base64
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
5
6
|
from collections.abc import AsyncIterator
|
|
@@ -75,7 +76,16 @@ class AntigravityModel(GoogleModel):
|
|
|
75
76
|
system_parts.append({"text": part.content})
|
|
76
77
|
elif isinstance(part, UserPromptPart):
|
|
77
78
|
# Use parent's _map_user_prompt
|
|
78
|
-
|
|
79
|
+
mapped_parts = await self._map_user_prompt(part)
|
|
80
|
+
# Sanitize bytes to base64 for JSON serialization
|
|
81
|
+
for mp in mapped_parts:
|
|
82
|
+
if "inline_data" in mp and "data" in mp["inline_data"]:
|
|
83
|
+
data = mp["inline_data"]["data"]
|
|
84
|
+
if isinstance(data, bytes):
|
|
85
|
+
mp["inline_data"]["data"] = base64.b64encode(
|
|
86
|
+
data
|
|
87
|
+
).decode("utf-8")
|
|
88
|
+
message_parts.extend(mapped_parts)
|
|
79
89
|
elif isinstance(part, ToolReturnPart):
|
|
80
90
|
message_parts.append(
|
|
81
91
|
{
|
|
@@ -542,8 +552,13 @@ def _antigravity_content_model_response(
|
|
|
542
552
|
|
|
543
553
|
elif isinstance(item, FilePart):
|
|
544
554
|
content = item.content
|
|
555
|
+
# Ensure data is base64 string, not bytes
|
|
556
|
+
data_val = content.data
|
|
557
|
+
if isinstance(data_val, bytes):
|
|
558
|
+
data_val = base64.b64encode(data_val).decode("utf-8")
|
|
559
|
+
|
|
545
560
|
inline_data_dict: BlobDict = {
|
|
546
|
-
"data":
|
|
561
|
+
"data": data_val,
|
|
547
562
|
"mime_type": content.media_type,
|
|
548
563
|
}
|
|
549
564
|
part["inline_data"] = inline_data_dict
|
|
@@ -21,6 +21,8 @@ from .config import (
|
|
|
21
21
|
get_token_storage_path,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
+
TOKEN_REFRESH_BUFFER_SECONDS = 60
|
|
25
|
+
|
|
24
26
|
logger = logging.getLogger(__name__)
|
|
25
27
|
|
|
26
28
|
|
|
@@ -132,6 +134,124 @@ def load_stored_tokens() -> Optional[Dict[str, Any]]:
|
|
|
132
134
|
return None
|
|
133
135
|
|
|
134
136
|
|
|
137
|
+
def _calculate_expires_at(expires_in: Optional[float]) -> Optional[float]:
|
|
138
|
+
if expires_in is None:
|
|
139
|
+
return None
|
|
140
|
+
try:
|
|
141
|
+
return time.time() + float(expires_in)
|
|
142
|
+
except (TypeError, ValueError):
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def is_token_expired(tokens: Dict[str, Any]) -> bool:
|
|
147
|
+
expires_at = tokens.get("expires_at")
|
|
148
|
+
if expires_at is None:
|
|
149
|
+
return False
|
|
150
|
+
try:
|
|
151
|
+
expires_at_value = float(expires_at)
|
|
152
|
+
except (TypeError, ValueError):
|
|
153
|
+
return False
|
|
154
|
+
return time.time() >= expires_at_value - TOKEN_REFRESH_BUFFER_SECONDS
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def update_claude_code_model_tokens(access_token: str) -> bool:
|
|
158
|
+
try:
|
|
159
|
+
claude_models = load_claude_models()
|
|
160
|
+
if not claude_models:
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
updated = False
|
|
164
|
+
for config in claude_models.values():
|
|
165
|
+
if config.get("oauth_source") != "claude-code-plugin":
|
|
166
|
+
continue
|
|
167
|
+
custom_endpoint = config.get("custom_endpoint")
|
|
168
|
+
if not isinstance(custom_endpoint, dict):
|
|
169
|
+
continue
|
|
170
|
+
custom_endpoint["api_key"] = access_token
|
|
171
|
+
updated = True
|
|
172
|
+
|
|
173
|
+
if updated:
|
|
174
|
+
return save_claude_models(claude_models)
|
|
175
|
+
except Exception as exc: # pragma: no cover - defensive logging
|
|
176
|
+
logger.error("Failed to update Claude model tokens: %s", exc)
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def refresh_access_token(force: bool = False) -> Optional[str]:
|
|
181
|
+
tokens = load_stored_tokens()
|
|
182
|
+
if not tokens:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
if not force and not is_token_expired(tokens):
|
|
186
|
+
return tokens.get("access_token")
|
|
187
|
+
|
|
188
|
+
refresh_token = tokens.get("refresh_token")
|
|
189
|
+
if not refresh_token:
|
|
190
|
+
logger.debug("No refresh_token available")
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
payload = {
|
|
194
|
+
"grant_type": "refresh_token",
|
|
195
|
+
"client_id": CLAUDE_CODE_OAUTH_CONFIG["client_id"],
|
|
196
|
+
"refresh_token": refresh_token,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
headers = {
|
|
200
|
+
"Content-Type": "application/json",
|
|
201
|
+
"Accept": "application/json",
|
|
202
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
response = requests.post(
|
|
207
|
+
CLAUDE_CODE_OAUTH_CONFIG["token_url"],
|
|
208
|
+
json=payload,
|
|
209
|
+
headers=headers,
|
|
210
|
+
timeout=30,
|
|
211
|
+
)
|
|
212
|
+
if response.status_code == 200:
|
|
213
|
+
new_tokens = response.json()
|
|
214
|
+
tokens["access_token"] = new_tokens.get("access_token")
|
|
215
|
+
tokens["refresh_token"] = new_tokens.get("refresh_token", refresh_token)
|
|
216
|
+
if "expires_in" in new_tokens:
|
|
217
|
+
tokens["expires_in"] = new_tokens["expires_in"]
|
|
218
|
+
tokens["expires_at"] = _calculate_expires_at(
|
|
219
|
+
new_tokens.get("expires_in")
|
|
220
|
+
)
|
|
221
|
+
if save_tokens(tokens):
|
|
222
|
+
update_claude_code_model_tokens(tokens["access_token"])
|
|
223
|
+
return tokens["access_token"]
|
|
224
|
+
else:
|
|
225
|
+
logger.error(
|
|
226
|
+
"Token refresh failed: %s - %s", response.status_code, response.text
|
|
227
|
+
)
|
|
228
|
+
except Exception as exc: # pragma: no cover - defensive logging
|
|
229
|
+
logger.error("Token refresh error: %s", exc)
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def get_valid_access_token() -> Optional[str]:
|
|
234
|
+
tokens = load_stored_tokens()
|
|
235
|
+
if not tokens:
|
|
236
|
+
logger.debug("No stored Claude Code OAuth tokens found")
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
access_token = tokens.get("access_token")
|
|
240
|
+
if not access_token:
|
|
241
|
+
logger.debug("No access_token in stored tokens")
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
if is_token_expired(tokens):
|
|
245
|
+
logger.info("Claude Code OAuth token expired, attempting refresh")
|
|
246
|
+
refreshed = refresh_access_token()
|
|
247
|
+
if refreshed:
|
|
248
|
+
return refreshed
|
|
249
|
+
logger.warning("Claude Code token refresh failed")
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
return access_token
|
|
253
|
+
|
|
254
|
+
|
|
135
255
|
def save_tokens(tokens: Dict[str, Any]) -> bool:
|
|
136
256
|
try:
|
|
137
257
|
token_path = get_token_storage_path()
|
|
@@ -243,7 +363,11 @@ def exchange_code_for_tokens(
|
|
|
243
363
|
logger.info("Token exchange response: %s", response.status_code)
|
|
244
364
|
logger.debug("Response body: %s", response.text)
|
|
245
365
|
if response.status_code == 200:
|
|
246
|
-
|
|
366
|
+
token_data = response.json()
|
|
367
|
+
token_data["expires_at"] = _calculate_expires_at(
|
|
368
|
+
token_data.get("expires_in")
|
|
369
|
+
)
|
|
370
|
+
return token_data
|
|
247
371
|
logger.error(
|
|
248
372
|
"Token exchange failed: %s - %s",
|
|
249
373
|
response.status_code,
|
|
@@ -341,12 +465,7 @@ def add_models_to_extra_config(models: List[str]) -> bool:
|
|
|
341
465
|
# Start fresh - overwrite the file on every auth instead of loading existing
|
|
342
466
|
claude_models = {}
|
|
343
467
|
added = 0
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
# Handle case where tokens are None or empty
|
|
347
|
-
access_token = ""
|
|
348
|
-
if tokens and "access_token" in tokens:
|
|
349
|
-
access_token = tokens["access_token"]
|
|
468
|
+
access_token = get_valid_access_token() or ""
|
|
350
469
|
|
|
351
470
|
for model_name in filtered_models:
|
|
352
471
|
prefixed = f"{CLAUDE_CODE_OAUTH_CONFIG['prefix']}{model_name}"
|
code_puppy/terminal_utils.py
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
Handles Windows console mode resets and Unix terminal sanity restoration.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import os
|
|
6
7
|
import platform
|
|
7
8
|
import subprocess
|
|
8
9
|
import sys
|
|
9
|
-
from typing import Callable, Optional
|
|
10
|
+
from typing import TYPE_CHECKING, Callable, Optional
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from rich.console import Console
|
|
10
14
|
|
|
11
15
|
# Store the original console ctrl handler so we can restore it if needed
|
|
12
16
|
_original_ctrl_handler: Optional[Callable] = None
|
|
@@ -289,3 +293,126 @@ def ensure_ctrl_c_disabled() -> bool:
|
|
|
289
293
|
|
|
290
294
|
except Exception:
|
|
291
295
|
return False
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def detect_truecolor_support() -> bool:
|
|
299
|
+
"""Detect if the terminal supports truecolor (24-bit color).
|
|
300
|
+
|
|
301
|
+
Checks multiple indicators:
|
|
302
|
+
1. COLORTERM environment variable (most reliable)
|
|
303
|
+
2. TERM environment variable patterns
|
|
304
|
+
3. Rich's Console color_system detection as fallback
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
True if truecolor is supported, False otherwise.
|
|
308
|
+
"""
|
|
309
|
+
# Check COLORTERM - this is the most reliable indicator
|
|
310
|
+
colorterm = os.environ.get("COLORTERM", "").lower()
|
|
311
|
+
if colorterm in ("truecolor", "24bit"):
|
|
312
|
+
return True
|
|
313
|
+
|
|
314
|
+
# Check TERM for known truecolor-capable terminals
|
|
315
|
+
term = os.environ.get("TERM", "").lower()
|
|
316
|
+
truecolor_terms = (
|
|
317
|
+
"xterm-direct",
|
|
318
|
+
"xterm-truecolor",
|
|
319
|
+
"iterm2",
|
|
320
|
+
"vte-256color", # Many modern terminals set this
|
|
321
|
+
)
|
|
322
|
+
if any(t in term for t in truecolor_terms):
|
|
323
|
+
return True
|
|
324
|
+
|
|
325
|
+
# Some terminals like iTerm2, Kitty, Alacritty set specific env vars
|
|
326
|
+
if os.environ.get("ITERM_SESSION_ID"):
|
|
327
|
+
return True
|
|
328
|
+
if os.environ.get("KITTY_WINDOW_ID"):
|
|
329
|
+
return True
|
|
330
|
+
if os.environ.get("ALACRITTY_SOCKET"):
|
|
331
|
+
return True
|
|
332
|
+
if os.environ.get("WT_SESSION"): # Windows Terminal
|
|
333
|
+
return True
|
|
334
|
+
|
|
335
|
+
# Use Rich's detection as a fallback
|
|
336
|
+
try:
|
|
337
|
+
from rich.console import Console
|
|
338
|
+
|
|
339
|
+
console = Console(force_terminal=True)
|
|
340
|
+
color_system = console.color_system
|
|
341
|
+
return color_system == "truecolor"
|
|
342
|
+
except Exception:
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def print_truecolor_warning(console: Optional["Console"] = None) -> None:
|
|
349
|
+
"""Print a big fat red warning if truecolor is not supported.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
console: Optional Rich Console instance. If None, creates a new one.
|
|
353
|
+
"""
|
|
354
|
+
if detect_truecolor_support():
|
|
355
|
+
return # All good, no warning needed
|
|
356
|
+
|
|
357
|
+
if console is None:
|
|
358
|
+
try:
|
|
359
|
+
from rich.console import Console
|
|
360
|
+
|
|
361
|
+
console = Console()
|
|
362
|
+
except ImportError:
|
|
363
|
+
# Rich not available, fall back to plain print
|
|
364
|
+
print("\n" + "=" * 70)
|
|
365
|
+
print("⚠️ WARNING: TERMINAL DOES NOT SUPPORT TRUECOLOR (24-BIT COLOR)")
|
|
366
|
+
print("=" * 70)
|
|
367
|
+
print("Code Puppy looks best with truecolor support.")
|
|
368
|
+
print("Consider using a modern terminal like:")
|
|
369
|
+
print(" • iTerm2 (macOS)")
|
|
370
|
+
print(" • Windows Terminal (Windows)")
|
|
371
|
+
print(" • Kitty, Alacritty, or any modern terminal emulator")
|
|
372
|
+
print("")
|
|
373
|
+
print("You can also try setting: export COLORTERM=truecolor")
|
|
374
|
+
print("")
|
|
375
|
+
print("Note: The built-in macOS Terminal.app does not support truecolor")
|
|
376
|
+
print("(Sequoia and earlier). You'll need a different terminal app.")
|
|
377
|
+
print("=" * 70 + "\n")
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
# Get detected color system for diagnostic info
|
|
381
|
+
color_system = console.color_system or "unknown"
|
|
382
|
+
|
|
383
|
+
# Build the warning box
|
|
384
|
+
warning_lines = [
|
|
385
|
+
"",
|
|
386
|
+
"[bold bright_red on red]" + "━" * 72 + "[/]",
|
|
387
|
+
"[bold bright_red on red]┃[/][bold bright_white on red]"
|
|
388
|
+
+ " " * 70
|
|
389
|
+
+ "[/][bold bright_red on red]┃[/]",
|
|
390
|
+
"[bold bright_red on red]┃[/][bold bright_white on red] ⚠️ WARNING: TERMINAL DOES NOT SUPPORT TRUECOLOR (24-BIT COLOR) ⚠️ [/][bold bright_red on red]┃[/]",
|
|
391
|
+
"[bold bright_red on red]┃[/][bold bright_white on red]"
|
|
392
|
+
+ " " * 70
|
|
393
|
+
+ "[/][bold bright_red on red]┃[/]",
|
|
394
|
+
"[bold bright_red on red]" + "━" * 72 + "[/]",
|
|
395
|
+
"",
|
|
396
|
+
f"[yellow]Detected color system:[/] [bold]{color_system}[/]",
|
|
397
|
+
"",
|
|
398
|
+
"[bold white]Code Puppy uses rich colors and will look degraded without truecolor.[/]",
|
|
399
|
+
"",
|
|
400
|
+
"[cyan]Consider using a modern terminal emulator:[/]",
|
|
401
|
+
" [green]•[/] [bold]iTerm2[/] (macOS) - https://iterm2.com",
|
|
402
|
+
" [green]•[/] [bold]Windows Terminal[/] (Windows) - Built into Windows 11",
|
|
403
|
+
" [green]•[/] [bold]Kitty[/] - https://sw.kovidgoyal.net/kitty",
|
|
404
|
+
" [green]•[/] [bold]Alacritty[/] - https://alacritty.org",
|
|
405
|
+
" [green]•[/] [bold]Warp[/] (macOS) - https://warp.dev",
|
|
406
|
+
"",
|
|
407
|
+
"[cyan]Or try setting the COLORTERM environment variable:[/]",
|
|
408
|
+
" [dim]export COLORTERM=truecolor[/]",
|
|
409
|
+
"",
|
|
410
|
+
"[dim italic]Note: The built-in macOS Terminal.app does not support truecolor (Sequoia and earlier).[/]",
|
|
411
|
+
"[dim italic]Setting COLORTERM=truecolor won't help - you'll need a different terminal app.[/]",
|
|
412
|
+
"",
|
|
413
|
+
"[bold bright_red]" + "─" * 72 + "[/]",
|
|
414
|
+
"",
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
for line in warning_lines:
|
|
418
|
+
console.print(line)
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"supported_settings": ["reasoning_effort", "verbosity"],
|
|
56
56
|
"supports_xhigh_reasoning": true
|
|
57
57
|
},
|
|
58
|
-
"Cerebras-GLM-4.
|
|
58
|
+
"Cerebras-GLM-4.7": {
|
|
59
59
|
"type": "cerebras",
|
|
60
|
-
"name": "zai-glm-4.
|
|
60
|
+
"name": "zai-glm-4.7",
|
|
61
61
|
"custom_endpoint": {
|
|
62
62
|
"url": "https://api.cerebras.ai/v1",
|
|
63
63
|
"api_key": "$CEREBRAS_API_KEY"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-puppy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.338
|
|
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
|
|
@@ -34,6 +34,7 @@ Requires-Dist: rich>=13.4.2
|
|
|
34
34
|
Requires-Dist: ripgrep==14.1.0
|
|
35
35
|
Requires-Dist: ruff>=0.11.11
|
|
36
36
|
Requires-Dist: tenacity>=8.2.0
|
|
37
|
+
Requires-Dist: termflow-md>=0.1.6
|
|
37
38
|
Requires-Dist: uvicorn>=0.30.0
|
|
38
39
|
Description-Content-Type: text/markdown
|
|
39
40
|
|
|
@@ -106,12 +107,7 @@ uvx code-puppy -i
|
|
|
106
107
|
# Install UV if you don't have it
|
|
107
108
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
111
|
-
source ~/.zshrc # or ~/.bashrc
|
|
112
|
-
|
|
113
|
-
# Install and run code-puppy
|
|
114
|
-
uvx code-puppy -i
|
|
110
|
+
uvx code-puppy
|
|
115
111
|
```
|
|
116
112
|
|
|
117
113
|
#### Windows
|
|
@@ -122,73 +118,15 @@ On Windows, we recommend installing code-puppy as a global tool for the best exp
|
|
|
122
118
|
# Install UV if you don't have it (run in PowerShell as Admin)
|
|
123
119
|
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
uv tool install code-puppy
|
|
127
|
-
|
|
128
|
-
# Run code-puppy
|
|
129
|
-
code-puppy -i
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
**Why `uv tool install` on Windows?** Running with `uvx` creates an extra process layer that can interfere with keyboard signal handling (Ctrl+C, Ctrl+X). Installing as a tool runs code-puppy directly for reliable cancellation.
|
|
133
|
-
|
|
134
|
-
#### Upgrading
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
# Upgrade code-puppy to the latest version
|
|
138
|
-
uv tool upgrade code-puppy
|
|
139
|
-
|
|
140
|
-
# Or upgrade all installed tools
|
|
141
|
-
uv tool upgrade --all
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
UV will automatically download the latest compatible Python version (3.11+) if your system doesn't have one.
|
|
145
|
-
|
|
146
|
-
### pip (Alternative)
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
pip install code-puppy
|
|
121
|
+
uvx code-puppy
|
|
150
122
|
```
|
|
151
123
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
### Permanent Python Management
|
|
155
|
-
|
|
156
|
-
To make UV always use managed Python versions (recommended):
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# Set environment variable permanently
|
|
160
|
-
echo 'export UV_MANAGED_PYTHON=1' >> ~/.zshrc # or ~/.bashrc
|
|
161
|
-
source ~/.zshrc # or ~/.bashrc
|
|
162
|
-
|
|
163
|
-
# Now all UV commands will prefer managed Python installations
|
|
164
|
-
uvx code-puppy # No need for --managed-python flag anymore
|
|
165
|
-
```
|
|
124
|
+
## Changelog (By Kittylog!)
|
|
166
125
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
```bash
|
|
170
|
-
# Check which Python UV will use
|
|
171
|
-
uv python find
|
|
172
|
-
|
|
173
|
-
# Or check the current project's Python
|
|
174
|
-
uv run python --version
|
|
175
|
-
```
|
|
126
|
+
[📋 View the full changelog on Kittylog](https://kittylog.app/c/mpfaffenberger/code_puppy)
|
|
176
127
|
|
|
177
128
|
## Usage
|
|
178
129
|
|
|
179
|
-
### Custom Commands
|
|
180
|
-
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
|
|
181
|
-
|
|
182
|
-
```bash
|
|
183
|
-
# Create a custom command
|
|
184
|
-
echo "# Code Review
|
|
185
|
-
|
|
186
|
-
Please review this code for security issues." > .claude/commands/review.md
|
|
187
|
-
|
|
188
|
-
# Use it in Code Puppy
|
|
189
|
-
/review with focus on authentication
|
|
190
|
-
```
|
|
191
|
-
|
|
192
130
|
### Adding Models from models.dev 🆕
|
|
193
131
|
|
|
194
132
|
While there are several models configured right out of the box from providers like Synthetic, Cerebras, OpenAI, Google, and Anthropic, Code Puppy integrates with [models.dev](https://models.dev) to let you browse and add models from **65+ providers** with a single command:
|
|
@@ -256,6 +194,18 @@ The following environment variables control DBOS behavior:
|
|
|
256
194
|
- `DBOS_SYSTEM_DATABASE_URL`: Database URL used by DBOS. Can point to a local SQLite file or a Postgres instance. Example: `postgresql://postgres:dbos@localhost:5432/postgres`. Default: `dbos_store.sqlite` file in the config directory.
|
|
257
195
|
- `DBOS_APP_VERSION`: If set, Code Puppy uses it as the [DBOS application version](https://docs.dbos.dev/architecture#application-and-workflow-versions) and automatically tries to recover pending workflows for this version. Default: Code Puppy version + Unix timestamp in millisecond (disable automatic recovery).
|
|
258
196
|
|
|
197
|
+
### Custom Commands
|
|
198
|
+
Create markdown files in `.claude/commands/`, `.github/prompts/`, or `.agents/commands/` to define custom slash commands. The filename becomes the command name and the content runs as a prompt.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Create a custom command
|
|
202
|
+
echo "# Code Review
|
|
203
|
+
|
|
204
|
+
Please review this code for security issues." > .claude/commands/review.md
|
|
205
|
+
|
|
206
|
+
# Use it in Code Puppy
|
|
207
|
+
/review with focus on authentication
|
|
208
|
+
```
|
|
259
209
|
|
|
260
210
|
## Requirements
|
|
261
211
|
|
|
@@ -275,9 +225,6 @@ For examples and more information about agent rules, visit [https://agent.md](ht
|
|
|
275
225
|
|
|
276
226
|
Use the `/mcp` command to manage MCP (list, start, stop, status, etc.)
|
|
277
227
|
|
|
278
|
-
Watch this video for examples! https://www.youtube.com/watch?v=1t1zEetOqlo
|
|
279
|
-
|
|
280
|
-
|
|
281
228
|
## Round Robin Model Distribution
|
|
282
229
|
|
|
283
230
|
Code Puppy supports **Round Robin model distribution** to help you overcome rate limits and distribute load across multiple AI models. This feature automatically cycles through configured models with each request, maximizing your API usage while staying within rate limits.
|
|
@@ -2,17 +2,17 @@ code_puppy/__init__.py,sha256=xMPewo9RNHb3yfFNIk5WCbv2cvSPtJOCgK2-GqLbNnU,373
|
|
|
2
2
|
code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
|
|
3
3
|
code_puppy/callbacks.py,sha256=hqTV--dNxG5vwWWm3MrEjmb8MZuHFFdmHePl23NXPHk,8621
|
|
4
4
|
code_puppy/chatgpt_codex_client.py,sha256=Om0ANB_kpHubhCwNzF9ENf8RvKBqs0IYzBLl_SNw0Vk,9833
|
|
5
|
-
code_puppy/claude_cache_client.py,sha256=
|
|
6
|
-
code_puppy/cli_runner.py,sha256=
|
|
5
|
+
code_puppy/claude_cache_client.py,sha256=QaucFONE0InS1GANCZwMFx-7sEptbZfjVzb_CgjvuUo,7949
|
|
6
|
+
code_puppy/cli_runner.py,sha256=E7C2pCWof4JgNcCRgMlodeTs2DyWh0NaB7_a_nwafM0,33117
|
|
7
7
|
code_puppy/config.py,sha256=RlnrLkyFXm7h2Htf8rQA7vqoAyzLPMrESle417uLmFw,52373
|
|
8
8
|
code_puppy/error_logging.py,sha256=a80OILCUtJhexI6a9GM-r5LqIdjvSRzggfgPp2jv1X0,3297
|
|
9
9
|
code_puppy/gemini_code_assist.py,sha256=KGS7sO5OLc83nDF3xxS-QiU6vxW9vcm6hmzilu79Ef8,13867
|
|
10
|
-
code_puppy/http_utils.py,sha256=
|
|
10
|
+
code_puppy/http_utils.py,sha256=H3N5Qz2B1CcsGUYOycGWAqoNMr2P1NCVluKX3aRwRqI,10358
|
|
11
11
|
code_puppy/keymap.py,sha256=IvMkTlB_bIqOWpbTpmftkdyjhtD5todXuEIw1zCZ4u0,3584
|
|
12
12
|
code_puppy/main.py,sha256=82r3vZy_XcyEsenLn82BnUusaoyL3Bpm_Th_jKgqecE,273
|
|
13
|
-
code_puppy/model_factory.py,sha256=
|
|
13
|
+
code_puppy/model_factory.py,sha256=BSGHZlwtF7jkYz2qFG9oJglG-NnfmbsQXbx4I6stXW0,38313
|
|
14
14
|
code_puppy/model_utils.py,sha256=NU8W8NW5F7QS_PXHaLeh55Air1koUV7IVYFP7Rz3XpY,3615
|
|
15
|
-
code_puppy/models.json,sha256=
|
|
15
|
+
code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
|
|
16
16
|
code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
|
|
17
17
|
code_puppy/models_dev_parser.py,sha256=8ndmWrsSyKbXXpRZPXc0w6TfWMuCcgaHiMifmlaBaPc,20611
|
|
18
18
|
code_puppy/pydantic_patches.py,sha256=YecAEeCOjSIwIBu2O5vEw72atMSL37cXGrbEuukI07o,4582
|
|
@@ -21,7 +21,7 @@ code_puppy/round_robin_model.py,sha256=kSawwPUiPgg0yg8r4AAVgvjzsWkptxpSORd75-HP7
|
|
|
21
21
|
code_puppy/session_storage.py,sha256=T4hOsAl9z0yz2JZCptjJBOnN8fCmkLZx5eLy1hTdv6Q,9631
|
|
22
22
|
code_puppy/status_display.py,sha256=qHzIQGAPEa2_-4gQSg7_rE1ihOosBq8WO73MWFNmmlo,8938
|
|
23
23
|
code_puppy/summarization_agent.py,sha256=6Pu_Wp_rF-HAhoX9u2uXTabRVkOZUYwRoMP1lzNS4ew,4485
|
|
24
|
-
code_puppy/terminal_utils.py,sha256=
|
|
24
|
+
code_puppy/terminal_utils.py,sha256=TaS19x7EZqudlBUAQwLMzBMNxBHBNInvQQREXqRGtkM,12984
|
|
25
25
|
code_puppy/uvx_detection.py,sha256=tP9X9Nvzow--KIqtqjgrHQkSxMJ3EevfoaeoB9VLY2o,7224
|
|
26
26
|
code_puppy/version_checker.py,sha256=aq2Mwxl1CR9sEFBgrPt3OQOowLOBUp9VaQYWJhuUv8Q,1780
|
|
27
27
|
code_puppy/agents/__init__.py,sha256=PtPB7Z5MSwmUKipgt_qxvIuGggcuVaYwNbnp1UP4tPc,518
|
|
@@ -40,7 +40,7 @@ code_puppy/agents/agent_qa_expert.py,sha256=5Ikb4U3SZQknUEfwlHZiyZXKqnffnOTQagr_
|
|
|
40
40
|
code_puppy/agents/agent_qa_kitten.py,sha256=5PeFFSwCFlTUvP6h5bGntx0xv5NmRwBiw0HnMqY8nLI,9107
|
|
41
41
|
code_puppy/agents/agent_security_auditor.py,sha256=SpiYNA0XAsIwBj7S2_EQPRslRUmF_-b89pIJyW7DYtY,12022
|
|
42
42
|
code_puppy/agents/agent_typescript_reviewer.py,sha256=vsnpp98xg6cIoFAEJrRTUM_i4wLEWGm5nJxs6fhHobM,10275
|
|
43
|
-
code_puppy/agents/base_agent.py,sha256=
|
|
43
|
+
code_puppy/agents/base_agent.py,sha256=FsjSw4i4YYVi9iyxviy6Y0aRGsm9ALvkOLbAfjcGAmc,83923
|
|
44
44
|
code_puppy/agents/json_agent.py,sha256=lhopDJDoiSGHvD8A6t50hi9ZBoNRKgUywfxd0Po_Dzc,4886
|
|
45
45
|
code_puppy/agents/prompt_reviewer.py,sha256=JJrJ0m5q0Puxl8vFsyhAbY9ftU9n6c6UxEVdNct1E-Q,5558
|
|
46
46
|
code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
|
|
@@ -112,10 +112,10 @@ code_puppy/messaging/bus.py,sha256=TbdltJ0D5tqnaE4irq1fcXllDYm-mQ_SiX1IFm-S4sw,2
|
|
|
112
112
|
code_puppy/messaging/commands.py,sha256=77CtKVNaF5KS3Xyzd0ccDAisZWQxL3weVEt3J-SfYxo,5464
|
|
113
113
|
code_puppy/messaging/markdown_patches.py,sha256=dMIJozzJChuHa8QNMSEz_kC-dyt7kZiDLZ7rjthbcmg,1626
|
|
114
114
|
code_puppy/messaging/message_queue.py,sha256=e-viZxacBoNSxRJnCJ4hU4vzsSI3oX_rN58RwhJKFfU,11825
|
|
115
|
-
code_puppy/messaging/messages.py,sha256=
|
|
115
|
+
code_puppy/messaging/messages.py,sha256=F7RwMHeQrIk-8kuSSBU76wBq1NGuLb2H5cJrSMTC3XM,16464
|
|
116
116
|
code_puppy/messaging/queue_console.py,sha256=T0U_V1tdN6hd9DLokp-HCk0mhu8Ivpfajha368CBZrU,9983
|
|
117
117
|
code_puppy/messaging/renderers.py,sha256=GHVtMnxE1pJ-yrcRjacY81JcjlHRz3UVHzp-ohN-CGE,12058
|
|
118
|
-
code_puppy/messaging/rich_renderer.py,sha256=
|
|
118
|
+
code_puppy/messaging/rich_renderer.py,sha256=FiT1e5S8nNQte0E6CMFQ3KyTixadkgKSjp1hcZXtyOE,37892
|
|
119
119
|
code_puppy/messaging/spinner/__init__.py,sha256=KpK5tJqq9YnN3wklqvdH0BQmuwYnT83Mp4tPfQa9RqI,1664
|
|
120
120
|
code_puppy/messaging/spinner/console_spinner.py,sha256=YIReuWPD01YPy58FqWdMDWj2QhauTUxKo675Ub4-eDA,8451
|
|
121
121
|
code_puppy/messaging/spinner/spinner_base.py,sha256=JiQDAhCfwrWUFunb8Xcj1caEl34JJY7Bcio7mDeckSc,2694
|
|
@@ -123,7 +123,7 @@ code_puppy/plugins/__init__.py,sha256=gWgrXWoFpl-3Mxz2DAvxKW6SkCWrOnw-hKsY9O7nHc
|
|
|
123
123
|
code_puppy/plugins/oauth_puppy_html.py,sha256=Wpa-V_NlRiBAvo_OXHuR7wvOH_jSt8L9HSFGiab6xI0,13058
|
|
124
124
|
code_puppy/plugins/antigravity_oauth/__init__.py,sha256=1miHihSqRNXO20Vh_Gn9M3Aa2szh0gtdSCaKKj9nq0Q,362
|
|
125
125
|
code_puppy/plugins/antigravity_oauth/accounts.py,sha256=GQit2-K24bsopmTZyscFUq3M0cAEO5WutHWnipVdgz8,14304
|
|
126
|
-
code_puppy/plugins/antigravity_oauth/antigravity_model.py,sha256=
|
|
126
|
+
code_puppy/plugins/antigravity_oauth/antigravity_model.py,sha256=g0_nXnMg288CvBE48CFgZ-iAqlYbtbi1dcB4Up6cYYc,26115
|
|
127
127
|
code_puppy/plugins/antigravity_oauth/config.py,sha256=BoQgqf5I2XoHWnBBo9vhCIc_XwPj9Mbp0Z95ygWwt78,1362
|
|
128
128
|
code_puppy/plugins/antigravity_oauth/constants.py,sha256=qsrA10JJvzNuY0OobvvwCQcoGpILBninllcUUMKkUrQ,4644
|
|
129
129
|
code_puppy/plugins/antigravity_oauth/oauth.py,sha256=ZHXJtZP63l6brOpX1WdLfuUClIleA79-4y36YUJc6Wo,15137
|
|
@@ -145,7 +145,7 @@ code_puppy/plugins/claude_code_oauth/__init__.py,sha256=mCcOU-wM7LNCDjr-w-WLPzom
|
|
|
145
145
|
code_puppy/plugins/claude_code_oauth/config.py,sha256=DjGySCkvjSGZds6DYErLMAi3TItt8iSLGvyJN98nSEM,2013
|
|
146
146
|
code_puppy/plugins/claude_code_oauth/register_callbacks.py,sha256=g8sl-i7jIOF6OFALeaLqTF3mS4tD8GR_FCzvPjVw2js,10165
|
|
147
147
|
code_puppy/plugins/claude_code_oauth/test_plugin.py,sha256=yQy4EeZl4bjrcog1d8BjknoDTRK75mRXXvkSQJYSSEM,9286
|
|
148
|
-
code_puppy/plugins/claude_code_oauth/utils.py,sha256=
|
|
148
|
+
code_puppy/plugins/claude_code_oauth/utils.py,sha256=TVgz5aFd2GFPHSiG9NnOYiw-y6KRkWwt_SZxmMpwMIY,17243
|
|
149
149
|
code_puppy/plugins/customizable_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
150
150
|
code_puppy/plugins/customizable_commands/register_callbacks.py,sha256=zVMfIzr--hVn0IOXxIicbmgj2s-HZUgtrOc0NCDOnDw,5183
|
|
151
151
|
code_puppy/plugins/example_custom_command/README.md,sha256=5c5Zkm7CW6BDSfe3WoLU7GW6t5mjjYAbu9-_pu-b3p4,8244
|
|
@@ -159,7 +159,7 @@ code_puppy/plugins/shell_safety/register_callbacks.py,sha256=W3v664RR48Fdbbbltf_
|
|
|
159
159
|
code_puppy/prompts/codex_system_prompt.md,sha256=hEFTCziroLqZmqNle5kG34A8kvTteOWezCiVrAEKhE0,24400
|
|
160
160
|
code_puppy/tools/__init__.py,sha256=BVTZ85jLHgDANwOnUSOz3UDlp8VQDq4DoGF23BRlyWw,6032
|
|
161
161
|
code_puppy/tools/agent_tools.py,sha256=snBI6FlFtR03CbYKXwu53R48c_fRSuDIwcNdVUruLcA,21020
|
|
162
|
-
code_puppy/tools/command_runner.py,sha256=
|
|
162
|
+
code_puppy/tools/command_runner.py,sha256=3qXVnVTaBPia6y2D29As47_TRKgpyCj82yMFK-8UUYc,44954
|
|
163
163
|
code_puppy/tools/common.py,sha256=IboS6sbwN4a3FzHdfsZJtEFiyDUCszevI6LpH14ydEk,40561
|
|
164
164
|
code_puppy/tools/file_modifications.py,sha256=vz9n7R0AGDSdLUArZr_55yJLkyI30M8zreAppxIx02M,29380
|
|
165
165
|
code_puppy/tools/file_operations.py,sha256=CqhpuBnOFOcQCIYXOujskxq2VMLWYJhibYrH0YcPSfA,35692
|
|
@@ -174,10 +174,10 @@ code_puppy/tools/browser/browser_scripts.py,sha256=sNb8eLEyzhasy5hV4B9OjM8yIVMLV
|
|
|
174
174
|
code_puppy/tools/browser/browser_workflows.py,sha256=nitW42vCf0ieTX1gLabozTugNQ8phtoFzZbiAhw1V90,6491
|
|
175
175
|
code_puppy/tools/browser/camoufox_manager.py,sha256=RZjGOEftE5sI_tsercUyXFSZI2wpStXf-q0PdYh2G3I,8680
|
|
176
176
|
code_puppy/tools/browser/vqa_agent.py,sha256=DBn9HKloILqJSTSdNZzH_PYWT0B2h9VwmY6akFQI_uU,2913
|
|
177
|
-
code_puppy-0.0.
|
|
178
|
-
code_puppy-0.0.
|
|
179
|
-
code_puppy-0.0.
|
|
180
|
-
code_puppy-0.0.
|
|
181
|
-
code_puppy-0.0.
|
|
182
|
-
code_puppy-0.0.
|
|
183
|
-
code_puppy-0.0.
|
|
177
|
+
code_puppy-0.0.338.data/data/code_puppy/models.json,sha256=FMQdE_yvP_8y0xxt3K918UkFL9cZMYAqW1SfXcQkU_k,3105
|
|
178
|
+
code_puppy-0.0.338.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
|
|
179
|
+
code_puppy-0.0.338.dist-info/METADATA,sha256=kNsGvoJiQpXWHOgGVtrwjkyYvFo8sVRj6xtxXRr83Lw,27520
|
|
180
|
+
code_puppy-0.0.338.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
181
|
+
code_puppy-0.0.338.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
|
|
182
|
+
code_puppy-0.0.338.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
183
|
+
code_puppy-0.0.338.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|