code-puppy 0.0.196__py3-none-any.whl → 0.0.198__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 +3 -5
- code_puppy/agents/json_agent.py +8 -0
- code_puppy/command_line/command_handler.py +108 -113
- code_puppy/command_line/prompt_toolkit_completion.py +34 -10
- code_puppy/config.py +6 -0
- code_puppy/main.py +6 -6
- code_puppy/tui/app.py +153 -7
- code_puppy/tui/components/input_area.py +1 -1
- code_puppy/tui/components/status_bar.py +4 -1
- code_puppy/tui/screens/__init__.py +2 -0
- code_puppy/tui/screens/autosave_picker.py +166 -0
- code_puppy/tui/screens/settings.py +4 -2
- {code_puppy-0.0.196.dist-info → code_puppy-0.0.198.dist-info}/METADATA +13 -2
- {code_puppy-0.0.196.dist-info → code_puppy-0.0.198.dist-info}/RECORD +18 -17
- {code_puppy-0.0.196.data → code_puppy-0.0.198.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.196.dist-info → code_puppy-0.0.198.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.196.dist-info → code_puppy-0.0.198.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.196.dist-info → code_puppy-0.0.198.dist-info}/licenses/LICENSE +0 -0
code_puppy/agents/base_agent.py
CHANGED
@@ -720,10 +720,7 @@ class BaseAgent(ABC):
|
|
720
720
|
emit_system_message(
|
721
721
|
f"[green]Successfully loaded {len(servers)} MCP server(s)[/green]"
|
722
722
|
)
|
723
|
-
|
724
|
-
emit_system_message(
|
725
|
-
"[yellow]No MCP servers available (check if servers are enabled)[/yellow]"
|
726
|
-
)
|
723
|
+
# Stay silent when there are no servers configured/available
|
727
724
|
return servers
|
728
725
|
|
729
726
|
def reload_mcp_servers(self):
|
@@ -891,7 +888,8 @@ class BaseAgent(ABC):
|
|
891
888
|
asyncio.CancelledError: When execution is cancelled by user
|
892
889
|
"""
|
893
890
|
group_id = str(uuid.uuid4())
|
894
|
-
|
891
|
+
# Avoid double-loading: reuse existing agent if already built
|
892
|
+
pydantic_agent = self._code_generation_agent or self.reload_code_generation_agent()
|
895
893
|
|
896
894
|
async def run_agent_task():
|
897
895
|
try:
|
code_puppy/agents/json_agent.py
CHANGED
@@ -101,6 +101,14 @@ class JSONAgent(BaseAgent):
|
|
101
101
|
"""Get tool configuration from JSON config."""
|
102
102
|
return self._config.get("tools_config")
|
103
103
|
|
104
|
+
def refresh_config(self) -> None:
|
105
|
+
"""Reload the agent configuration from disk.
|
106
|
+
|
107
|
+
This keeps long-lived agent instances in sync after external edits.
|
108
|
+
"""
|
109
|
+
self._config = self._load_config()
|
110
|
+
self._validate_config()
|
111
|
+
|
104
112
|
def get_model_name(self) -> Optional[str]:
|
105
113
|
"""Get pinned model name from JSON config, if specified.
|
106
114
|
|
@@ -5,131 +5,103 @@ from pathlib import Path
|
|
5
5
|
from code_puppy.command_line.model_picker_completion import update_model_in_input
|
6
6
|
from code_puppy.command_line.motd import print_motd
|
7
7
|
from code_puppy.command_line.utils import make_directory_table
|
8
|
-
from code_puppy.config import
|
8
|
+
from code_puppy.config import (
|
9
|
+
CONTEXTS_DIR,
|
10
|
+
finalize_autosave_session,
|
11
|
+
get_config_keys,
|
12
|
+
)
|
9
13
|
from code_puppy.session_storage import list_sessions, load_session, save_session
|
10
14
|
from code_puppy.tools.tools_content import tools_content
|
11
15
|
|
12
16
|
|
13
17
|
def get_commands_help():
|
14
|
-
"""Generate commands help using Rich Text
|
18
|
+
"""Generate aligned commands help using Rich Text for safe markup."""
|
15
19
|
from rich.text import Text
|
16
20
|
|
17
21
|
# Ensure plugins are loaded so custom help can register
|
18
22
|
_ensure_plugins_loaded()
|
19
23
|
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
help_lines.append(
|
69
|
-
Text("/compact", style="cyan")
|
70
|
-
+ Text(
|
71
|
-
" Summarize and compact current chat history (uses compaction_strategy config)"
|
72
|
-
)
|
73
|
-
)
|
74
|
-
help_lines.append(
|
75
|
-
Text("/dump_context", style="cyan")
|
76
|
-
+ Text(" <name> Save current message history to file")
|
77
|
-
)
|
78
|
-
help_lines.append(
|
79
|
-
Text("/load_context", style="cyan")
|
80
|
-
+ Text(" <name> Load message history from file")
|
81
|
-
)
|
82
|
-
help_lines.append(
|
83
|
-
Text("/set", style="cyan")
|
84
|
-
+ Text(
|
85
|
-
" Set puppy config key-values (e.g., /set yolo_mode true, /set auto_save_session true)"
|
86
|
-
)
|
87
|
-
)
|
88
|
-
help_lines.append(
|
89
|
-
Text("/tools", style="cyan")
|
90
|
-
+ Text(" Show available tools and capabilities")
|
91
|
-
)
|
92
|
-
help_lines.append(
|
93
|
-
Text("/truncate", style="cyan")
|
94
|
-
+ Text(
|
95
|
-
" <N> Truncate message history to N most recent messages (keeping system message)"
|
96
|
-
)
|
97
|
-
)
|
98
|
-
help_lines.append(
|
99
|
-
Text("/<unknown>", style="cyan")
|
100
|
-
+ Text(" Show unknown command warning")
|
101
|
-
)
|
24
|
+
# Collect core commands with their syntax parts and descriptions
|
25
|
+
# (cmd_syntax, description)
|
26
|
+
core_cmds = [
|
27
|
+
("/help, /h", "Show this help message"),
|
28
|
+
("/cd <dir>", "Change directory or show directories"),
|
29
|
+
(
|
30
|
+
"/agent <name>",
|
31
|
+
"Switch to a different agent or show available agents",
|
32
|
+
),
|
33
|
+
("/exit, /quit", "Exit interactive mode"),
|
34
|
+
("/generate-pr-description [@dir]", "Generate comprehensive PR description"),
|
35
|
+
("/model, /m <model>", "Set active model"),
|
36
|
+
("/reasoning <low|medium|high>", "Set OpenAI reasoning effort for GPT-5 models"),
|
37
|
+
("/pin_model <agent> <model>", "Pin a specific model to an agent"),
|
38
|
+
("/mcp", "Manage MCP servers (list, start, stop, status, etc.)"),
|
39
|
+
("/motd", "Show the latest message of the day (MOTD)"),
|
40
|
+
("/show", "Show puppy config key-values"),
|
41
|
+
(
|
42
|
+
"/compact",
|
43
|
+
"Summarize and compact current chat history (uses compaction_strategy config)",
|
44
|
+
),
|
45
|
+
("/dump_context <name>", "Save current message history to file"),
|
46
|
+
("/load_context <name>", "Load message history from file"),
|
47
|
+
(
|
48
|
+
"/set",
|
49
|
+
"Set puppy config (e.g., /set yolo_mode true, /set auto_save_session true)",
|
50
|
+
),
|
51
|
+
("/tools", "Show available tools and capabilities"),
|
52
|
+
(
|
53
|
+
"/truncate <N>",
|
54
|
+
"Truncate history to N most recent messages (keeping system message)",
|
55
|
+
),
|
56
|
+
("/<unknown>", "Show unknown command warning"),
|
57
|
+
]
|
58
|
+
|
59
|
+
# Determine padding width for the left column
|
60
|
+
left_width = max(len(cmd) for cmd, _ in core_cmds) + 2 # add spacing
|
61
|
+
|
62
|
+
lines: list[Text] = []
|
63
|
+
lines.append(Text("Commands Help", style="bold magenta"))
|
64
|
+
|
65
|
+
for cmd, desc in core_cmds:
|
66
|
+
left = Text(cmd.ljust(left_width), style="cyan")
|
67
|
+
right = Text(desc)
|
68
|
+
line = Text()
|
69
|
+
line.append_text(left)
|
70
|
+
line.append_text(right)
|
71
|
+
lines.append(line)
|
102
72
|
|
103
73
|
# Add custom commands from plugins (if any)
|
104
74
|
try:
|
105
75
|
from code_puppy import callbacks
|
106
76
|
|
107
77
|
custom_help_results = callbacks.on_custom_command_help()
|
108
|
-
|
109
|
-
custom_entries = []
|
78
|
+
custom_entries: list[tuple[str, str]] = []
|
110
79
|
for res in custom_help_results:
|
111
80
|
if not res:
|
112
81
|
continue
|
113
82
|
if isinstance(res, tuple) and len(res) == 2:
|
114
|
-
custom_entries.append(res)
|
83
|
+
custom_entries.append((str(res[0]), str(res[1])))
|
115
84
|
elif isinstance(res, list):
|
116
85
|
for item in res:
|
117
86
|
if isinstance(item, tuple) and len(item) == 2:
|
118
|
-
custom_entries.append(item)
|
87
|
+
custom_entries.append((str(item[0]), str(item[1])))
|
119
88
|
if custom_entries:
|
120
|
-
|
121
|
-
|
89
|
+
lines.append(Text("", style="dim"))
|
90
|
+
lines.append(Text("Custom Commands", style="bold magenta"))
|
91
|
+
# Compute padding for custom commands as well
|
92
|
+
custom_left_width = max(len(name) for name, _ in custom_entries) + 3
|
122
93
|
for name, desc in custom_entries:
|
123
|
-
|
124
|
-
|
125
|
-
)
|
94
|
+
left = Text(f"/{name}".ljust(custom_left_width), style="cyan")
|
95
|
+
right = Text(desc)
|
96
|
+
line = Text()
|
97
|
+
line.append_text(left)
|
98
|
+
line.append_text(right)
|
99
|
+
lines.append(line)
|
126
100
|
except Exception:
|
127
|
-
# If callbacks fail, skip custom help silently
|
128
101
|
pass
|
129
102
|
|
130
|
-
# Combine all lines
|
131
103
|
final_text = Text()
|
132
|
-
for i, line in enumerate(
|
104
|
+
for i, line in enumerate(lines):
|
133
105
|
if i > 0:
|
134
106
|
final_text.append("\n")
|
135
107
|
final_text.append_text(line)
|
@@ -461,31 +433,44 @@ def handle_command(command: str):
|
|
461
433
|
import uuid
|
462
434
|
|
463
435
|
group_id = str(uuid.uuid4())
|
436
|
+
available_agents = get_available_agents()
|
464
437
|
|
465
|
-
if
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
new_agent = get_current_agent()
|
470
|
-
emit_success(
|
471
|
-
f"Switched to agent: {new_agent.display_name}",
|
438
|
+
if agent_name not in available_agents:
|
439
|
+
emit_error(f"Agent '{agent_name}' not found", message_group=group_id)
|
440
|
+
emit_warning(
|
441
|
+
f"Available agents: {', '.join(available_agents.keys())}",
|
472
442
|
message_group=group_id,
|
473
443
|
)
|
474
|
-
emit_info(f"[dim]{new_agent.description}[/dim]", message_group=group_id)
|
475
444
|
return True
|
476
|
-
else:
|
477
|
-
# Generate a group ID for all messages in this command
|
478
|
-
import uuid
|
479
445
|
|
480
|
-
|
446
|
+
current_agent = get_current_agent()
|
447
|
+
if current_agent.name == agent_name:
|
448
|
+
emit_info(
|
449
|
+
f"Already using agent: {current_agent.display_name}",
|
450
|
+
message_group=group_id,
|
451
|
+
)
|
452
|
+
return True
|
481
453
|
|
482
|
-
|
483
|
-
|
454
|
+
new_session_id = finalize_autosave_session()
|
455
|
+
if not set_current_agent(agent_name):
|
484
456
|
emit_warning(
|
485
|
-
|
457
|
+
"Agent switch failed after autosave rotation. Your context was preserved.",
|
486
458
|
message_group=group_id,
|
487
459
|
)
|
488
460
|
return True
|
461
|
+
|
462
|
+
new_agent = get_current_agent()
|
463
|
+
new_agent.reload_code_generation_agent()
|
464
|
+
emit_success(
|
465
|
+
f"Switched to agent: {new_agent.display_name}",
|
466
|
+
message_group=group_id,
|
467
|
+
)
|
468
|
+
emit_info(f"[dim]{new_agent.description}[/dim]", message_group=group_id)
|
469
|
+
emit_info(
|
470
|
+
f"[dim]Auto-save session rotated to: {new_session_id}[/dim]",
|
471
|
+
message_group=group_id,
|
472
|
+
)
|
473
|
+
return True
|
489
474
|
else:
|
490
475
|
emit_warning("Usage: /agent [agent-name]")
|
491
476
|
return True
|
@@ -625,12 +610,22 @@ def handle_command(command: str):
|
|
625
610
|
|
626
611
|
emit_success(f"Model '{model_name}' pinned to agent '{agent_name}'")
|
627
612
|
|
628
|
-
# If this is the current agent,
|
613
|
+
# If this is the current agent, refresh it so the prompt updates immediately
|
629
614
|
from code_puppy.agents import get_current_agent
|
630
615
|
|
631
616
|
current_agent = get_current_agent()
|
632
617
|
if current_agent.name == agent_name:
|
633
|
-
|
618
|
+
try:
|
619
|
+
if is_json_agent and hasattr(current_agent, "refresh_config"):
|
620
|
+
current_agent.refresh_config()
|
621
|
+
current_agent.reload_code_generation_agent()
|
622
|
+
emit_info(
|
623
|
+
f"Active agent reloaded with pinned model '{model_name}'"
|
624
|
+
)
|
625
|
+
except Exception as reload_error:
|
626
|
+
emit_warning(
|
627
|
+
f"Pinned model applied but reload failed: {reload_error}"
|
628
|
+
)
|
634
629
|
|
635
630
|
return True
|
636
631
|
|
@@ -194,24 +194,48 @@ async def get_input_with_combined_completion(
|
|
194
194
|
LoadContextCompleter(trigger="/load_context"),
|
195
195
|
]
|
196
196
|
)
|
197
|
-
# Add custom key bindings
|
197
|
+
# Add custom key bindings and multiline toggle
|
198
198
|
bindings = KeyBindings()
|
199
199
|
|
200
|
-
|
200
|
+
# Multiline mode state
|
201
|
+
multiline = {"enabled": False}
|
202
|
+
|
203
|
+
# Toggle multiline with Alt+M
|
204
|
+
@bindings.add(Keys.Escape, "m")
|
201
205
|
def _(event):
|
202
|
-
|
206
|
+
multiline["enabled"] = not multiline["enabled"]
|
207
|
+
status = "ON" if multiline["enabled"] else "OFF"
|
208
|
+
# Print status for user feedback (version-agnostic)
|
209
|
+
print(f"[multiline] {status}", flush=True)
|
210
|
+
|
211
|
+
# Also toggle multiline with F2 (more reliable across platforms)
|
212
|
+
@bindings.add("f2")
|
213
|
+
def _(event):
|
214
|
+
multiline["enabled"] = not multiline["enabled"]
|
215
|
+
status = "ON" if multiline["enabled"] else "OFF"
|
216
|
+
print(f"[multiline] {status}", flush=True)
|
203
217
|
|
204
|
-
#
|
205
|
-
|
218
|
+
# Newline insert bindings — robust and explicit
|
219
|
+
# Ctrl+J (line feed) works in virtually all terminals; mark eager so it wins
|
220
|
+
@bindings.add("c-j", eager=True)
|
206
221
|
def _(event):
|
207
|
-
"""Pressing alt+enter (meta+enter) inserts a newline."""
|
208
222
|
event.app.current_buffer.insert_text("\n")
|
209
223
|
|
210
|
-
#
|
211
|
-
|
224
|
+
# Also allow Ctrl+Enter for newline (terminal-dependent)
|
225
|
+
try:
|
226
|
+
@bindings.add("c-enter", eager=True)
|
227
|
+
def _(event):
|
228
|
+
event.app.current_buffer.insert_text("\n")
|
229
|
+
except Exception:
|
230
|
+
pass
|
231
|
+
|
232
|
+
# Enter behavior depends on multiline mode
|
233
|
+
@bindings.add("enter", filter=~is_searching, eager=True)
|
212
234
|
def _(event):
|
213
|
-
|
214
|
-
|
235
|
+
if multiline["enabled"]:
|
236
|
+
event.app.current_buffer.insert_text("\n")
|
237
|
+
else:
|
238
|
+
event.current_buffer.validate_and_handle()
|
215
239
|
|
216
240
|
@bindings.add(Keys.Escape)
|
217
241
|
def _(event):
|
code_puppy/config.py
CHANGED
@@ -784,3 +784,9 @@ def auto_save_session_if_enabled() -> bool:
|
|
784
784
|
|
785
785
|
Console().print(f"[dim]❌ Failed to auto-save session: {exc}[/dim]")
|
786
786
|
return False
|
787
|
+
|
788
|
+
|
789
|
+
def finalize_autosave_session() -> str:
|
790
|
+
"""Persist the current autosave snapshot and rotate to a fresh session."""
|
791
|
+
auto_save_session_if_enabled()
|
792
|
+
return rotate_autosave_id()
|
code_puppy/main.py
CHANGED
@@ -24,6 +24,7 @@ from code_puppy.config import (
|
|
24
24
|
AUTOSAVE_DIR,
|
25
25
|
COMMAND_HISTORY_FILE,
|
26
26
|
ensure_config_exists,
|
27
|
+
finalize_autosave_session,
|
27
28
|
initialize_command_history_file,
|
28
29
|
save_command_to_history,
|
29
30
|
)
|
@@ -272,16 +273,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
272
273
|
emit_info("[bold green]Code Puppy[/bold green] - Interactive Mode")
|
273
274
|
emit_system_message("Type '/exit' or '/quit' to exit the interactive mode.")
|
274
275
|
emit_system_message("Type 'clear' to reset the conversation history.")
|
276
|
+
emit_system_message("[dim]Type /help to view all commands[/dim]")
|
275
277
|
emit_system_message(
|
276
|
-
"Type [bold blue]@[/bold blue] for path completion, or [bold blue]/m[/bold blue] to pick a model.
|
278
|
+
"Type [bold blue]@[/bold blue] for path completion, or [bold blue]/m[/bold blue] to pick a model. Toggle multiline with [bold blue]Alt+M[/bold blue] or [bold blue]F2[/bold blue]; newline: [bold blue]Ctrl+J[/bold blue]."
|
277
279
|
)
|
278
280
|
emit_system_message(
|
279
281
|
"Press [bold red]Ctrl+C[/bold red] during processing to cancel the current task or inference."
|
280
282
|
)
|
281
|
-
from code_puppy.command_line.command_handler import get_commands_help
|
282
|
-
|
283
|
-
help_text = get_commands_help()
|
284
|
-
emit_system_message(help_text)
|
285
283
|
try:
|
286
284
|
from code_puppy.command_line.motd import print_motd
|
287
285
|
|
@@ -417,12 +415,14 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
417
415
|
|
418
416
|
# Check for clear command (supports both `clear` and `/clear`)
|
419
417
|
if task.strip().lower() in ("clear", "/clear"):
|
420
|
-
from code_puppy.messaging import emit_system_message, emit_warning
|
418
|
+
from code_puppy.messaging import emit_info, emit_system_message, emit_warning
|
421
419
|
|
422
420
|
agent = get_current_agent()
|
421
|
+
new_session_id = finalize_autosave_session()
|
423
422
|
agent.clear_message_history()
|
424
423
|
emit_warning("Conversation history cleared!")
|
425
424
|
emit_system_message("The agent will not remember previous interactions.\n")
|
425
|
+
emit_info(f"[dim]Auto-save session rotated to: {new_session_id}[/dim]")
|
426
426
|
continue
|
427
427
|
|
428
428
|
# Handle / commands before anything else
|
code_puppy/tui/app.py
CHANGED
@@ -176,6 +176,13 @@ class CodePuppyTUI(App):
|
|
176
176
|
# Start the message renderer EARLY to catch startup messages
|
177
177
|
# Using call_after_refresh to start it as soon as possible after mount
|
178
178
|
self.call_after_refresh(self.start_message_renderer_sync)
|
179
|
+
|
180
|
+
# Kick off a non-blocking preload of the agent/model so the
|
181
|
+
# status bar shows loading before first prompt
|
182
|
+
self.call_after_refresh(self.preload_agent_on_startup)
|
183
|
+
|
184
|
+
# After preload, offer to restore an autosave session (like interactive mode)
|
185
|
+
self.call_after_refresh(self.maybe_prompt_restore_autosave)
|
179
186
|
|
180
187
|
# Apply responsive design adjustments
|
181
188
|
self.apply_responsive_layout()
|
@@ -187,16 +194,40 @@ class CodePuppyTUI(App):
|
|
187
194
|
if self.initial_command:
|
188
195
|
self.call_after_refresh(self.process_initial_command)
|
189
196
|
|
197
|
+
def _tighten_text(self, text: str) -> str:
|
198
|
+
"""Aggressively tighten whitespace: trim lines, collapse multiples, drop extra blanks."""
|
199
|
+
try:
|
200
|
+
import re
|
201
|
+
|
202
|
+
# Split into lines, strip each, drop empty runs
|
203
|
+
lines = [re.sub(r"\s+", " ", ln.strip()) for ln in text.splitlines()]
|
204
|
+
# Remove consecutive blank lines
|
205
|
+
tight_lines = []
|
206
|
+
last_blank = False
|
207
|
+
for ln in lines:
|
208
|
+
is_blank = (ln == "")
|
209
|
+
if is_blank and last_blank:
|
210
|
+
continue
|
211
|
+
tight_lines.append(ln)
|
212
|
+
last_blank = is_blank
|
213
|
+
return "\n".join(tight_lines).strip()
|
214
|
+
except Exception:
|
215
|
+
return text.strip()
|
216
|
+
|
190
217
|
def add_system_message(
|
191
218
|
self, content: str, message_group: str = None, group_id: str = None
|
192
219
|
) -> None:
|
193
220
|
"""Add a system message to the chat."""
|
194
221
|
# Support both parameter names for backward compatibility
|
195
222
|
final_group_id = message_group or group_id
|
223
|
+
# Tighten only plain strings
|
224
|
+
content_to_use = (
|
225
|
+
self._tighten_text(content) if isinstance(content, str) else content
|
226
|
+
)
|
196
227
|
message = ChatMessage(
|
197
228
|
id=f"sys_{datetime.now(timezone.utc).timestamp()}",
|
198
229
|
type=MessageType.SYSTEM,
|
199
|
-
content=
|
230
|
+
content=content_to_use,
|
200
231
|
timestamp=datetime.now(timezone.utc),
|
201
232
|
group_id=final_group_id,
|
202
233
|
)
|
@@ -245,10 +276,13 @@ class CodePuppyTUI(App):
|
|
245
276
|
|
246
277
|
def add_error_message(self, content: str, message_group: str = None) -> None:
|
247
278
|
"""Add an error message to the chat."""
|
279
|
+
content_to_use = (
|
280
|
+
self._tighten_text(content) if isinstance(content, str) else content
|
281
|
+
)
|
248
282
|
message = ChatMessage(
|
249
283
|
id=f"error_{datetime.now(timezone.utc).timestamp()}",
|
250
284
|
type=MessageType.ERROR,
|
251
|
-
content=
|
285
|
+
content=content_to_use,
|
252
286
|
timestamp=datetime.now(timezone.utc),
|
253
287
|
group_id=message_group,
|
254
288
|
)
|
@@ -303,9 +337,9 @@ class CodePuppyTUI(App):
|
|
303
337
|
|
304
338
|
# Only handle keys when input field is focused
|
305
339
|
if input_field.has_focus:
|
306
|
-
# Handle Ctrl+Enter
|
307
|
-
if event.key
|
308
|
-
input_field.insert("
|
340
|
+
# Handle Ctrl+Enter or Shift+Enter for a new line
|
341
|
+
if event.key in ("ctrl+enter", "shift+enter"):
|
342
|
+
input_field.insert("\n")
|
309
343
|
event.prevent_default()
|
310
344
|
return
|
311
345
|
|
@@ -484,6 +518,14 @@ class CodePuppyTUI(App):
|
|
484
518
|
self.update_agent_progress("Processing", 75)
|
485
519
|
agent_response = result.output
|
486
520
|
self.add_agent_message(agent_response)
|
521
|
+
|
522
|
+
# Auto-save session if enabled (mirror --interactive)
|
523
|
+
try:
|
524
|
+
from code_puppy.config import auto_save_session_if_enabled
|
525
|
+
auto_save_session_if_enabled()
|
526
|
+
except Exception:
|
527
|
+
pass
|
528
|
+
|
487
529
|
# Refresh history display to show new interaction
|
488
530
|
self.refresh_history_display()
|
489
531
|
|
@@ -842,6 +884,36 @@ class CodePuppyTUI(App):
|
|
842
884
|
"""Synchronous wrapper to start message renderer via run_worker."""
|
843
885
|
self.run_worker(self.start_message_renderer(), exclusive=False)
|
844
886
|
|
887
|
+
async def preload_agent_on_startup(self) -> None:
|
888
|
+
"""Preload the agent/model at startup so loading status is visible."""
|
889
|
+
try:
|
890
|
+
# Show loading in status bar and spinner
|
891
|
+
self.start_agent_progress("Loading")
|
892
|
+
|
893
|
+
# Warm up agent/model without blocking UI
|
894
|
+
import asyncio
|
895
|
+
|
896
|
+
from code_puppy.agents.agent_manager import get_current_agent
|
897
|
+
|
898
|
+
agent = get_current_agent()
|
899
|
+
|
900
|
+
# Run the synchronous reload in a worker thread
|
901
|
+
await asyncio.to_thread(agent.reload_code_generation_agent)
|
902
|
+
|
903
|
+
# After load, refresh current model (in case of fallback or changes)
|
904
|
+
from code_puppy.config import get_global_model_name
|
905
|
+
|
906
|
+
self.current_model = get_global_model_name()
|
907
|
+
|
908
|
+
# Let the user know model/agent are ready
|
909
|
+
self.add_system_message("Model and agent preloaded. Ready to roll 🛼")
|
910
|
+
except Exception as e:
|
911
|
+
# Surface any preload issues but keep app usable
|
912
|
+
self.add_error_message(f"Startup preload failed: {e}")
|
913
|
+
finally:
|
914
|
+
# Always stop spinner and set ready state
|
915
|
+
self.stop_agent_progress()
|
916
|
+
|
845
917
|
async def start_message_renderer(self):
|
846
918
|
"""Start the message renderer to consume messages from the queue."""
|
847
919
|
if not self._renderer_started:
|
@@ -884,9 +956,9 @@ class CodePuppyTUI(App):
|
|
884
956
|
f"Error processing startup message: {e}"
|
885
957
|
)
|
886
958
|
|
887
|
-
# Create a single grouped startup message
|
959
|
+
# Create a single grouped startup message (tightened)
|
888
960
|
grouped_content = "\n".join(startup_content_lines)
|
889
|
-
self.add_system_message(grouped_content)
|
961
|
+
self.add_system_message(self._tighten_text(grouped_content))
|
890
962
|
|
891
963
|
# Clear the startup buffer after processing
|
892
964
|
self.message_queue.clear_startup_buffer()
|
@@ -894,6 +966,80 @@ class CodePuppyTUI(App):
|
|
894
966
|
# Now start the regular message renderer
|
895
967
|
await self.message_renderer.start()
|
896
968
|
|
969
|
+
async def maybe_prompt_restore_autosave(self) -> None:
|
970
|
+
"""Offer to restore an autosave session at startup (TUI version)."""
|
971
|
+
try:
|
972
|
+
import asyncio
|
973
|
+
from pathlib import Path
|
974
|
+
|
975
|
+
from code_puppy.config import AUTOSAVE_DIR, set_current_autosave_from_session_name
|
976
|
+
from code_puppy.session_storage import list_sessions, load_session
|
977
|
+
|
978
|
+
base_dir = Path(AUTOSAVE_DIR)
|
979
|
+
sessions = list_sessions(base_dir)
|
980
|
+
if not sessions:
|
981
|
+
return
|
982
|
+
|
983
|
+
# Show modal picker for selection
|
984
|
+
from .screens.autosave_picker import AutosavePicker
|
985
|
+
|
986
|
+
async def handle_result(result_name: str | None):
|
987
|
+
if not result_name:
|
988
|
+
return
|
989
|
+
try:
|
990
|
+
# Load history and set into agent
|
991
|
+
from code_puppy.agents.agent_manager import get_current_agent
|
992
|
+
|
993
|
+
history = load_session(result_name, base_dir)
|
994
|
+
agent = get_current_agent()
|
995
|
+
agent.set_message_history(history)
|
996
|
+
|
997
|
+
# Set current autosave session id so subsequent autosaves overwrite this session
|
998
|
+
try:
|
999
|
+
set_current_autosave_from_session_name(result_name)
|
1000
|
+
except Exception:
|
1001
|
+
pass
|
1002
|
+
|
1003
|
+
# Update token info/status bar
|
1004
|
+
total_tokens = sum(
|
1005
|
+
agent.estimate_tokens_for_message(msg) for msg in history
|
1006
|
+
)
|
1007
|
+
try:
|
1008
|
+
status_bar = self.query_one(StatusBar)
|
1009
|
+
status_bar.update_token_info(
|
1010
|
+
total_tokens,
|
1011
|
+
agent.get_model_context_length(),
|
1012
|
+
total_tokens / max(1, agent.get_model_context_length()),
|
1013
|
+
)
|
1014
|
+
except Exception:
|
1015
|
+
pass
|
1016
|
+
|
1017
|
+
# Notify
|
1018
|
+
session_path = base_dir / f"{result_name}.pkl"
|
1019
|
+
self.add_system_message(
|
1020
|
+
f"✅ Autosave loaded: {len(history)} messages ({total_tokens} tokens)\n"
|
1021
|
+
f"📁 From: {session_path}"
|
1022
|
+
)
|
1023
|
+
|
1024
|
+
# Refresh history sidebar
|
1025
|
+
self.refresh_history_display()
|
1026
|
+
except Exception as e:
|
1027
|
+
self.add_error_message(f"Failed to load autosave: {e}")
|
1028
|
+
|
1029
|
+
# Push modal and await result
|
1030
|
+
picker = AutosavePicker(base_dir)
|
1031
|
+
|
1032
|
+
# Use Textual's push_screen with a result callback
|
1033
|
+
def on_picker_result(result_name=None):
|
1034
|
+
# Schedule async handler to avoid blocking UI
|
1035
|
+
import asyncio
|
1036
|
+
self.run_worker(handle_result(result_name), exclusive=False)
|
1037
|
+
|
1038
|
+
self.push_screen(picker, on_picker_result)
|
1039
|
+
except Exception as e:
|
1040
|
+
# Fail silently but show debug in chat
|
1041
|
+
self.add_system_message(f"[dim]Autosave prompt error: {e}[/dim]")
|
1042
|
+
|
897
1043
|
async def stop_message_renderer(self):
|
898
1044
|
"""Stop the message renderer."""
|
899
1045
|
if self._renderer_started:
|
@@ -133,7 +133,7 @@ class InputArea(Container):
|
|
133
133
|
yield CustomTextArea(id="input-field", show_line_numbers=False)
|
134
134
|
yield SubmitCancelButton()
|
135
135
|
yield Static(
|
136
|
-
"Enter to send •
|
136
|
+
"Enter to send • Shift+Enter for new line • Ctrl+1 for help",
|
137
137
|
id="input-help",
|
138
138
|
)
|
139
139
|
|
@@ -83,7 +83,10 @@ class StatusBar(Static):
|
|
83
83
|
elif self.agent_status == "Busy":
|
84
84
|
status_indicator = "🔄"
|
85
85
|
status_color = "orange"
|
86
|
-
|
86
|
+
elif self.agent_status == "Loading":
|
87
|
+
status_indicator = "⏳"
|
88
|
+
status_color = "cyan"
|
89
|
+
else: # Ready or anything else
|
87
90
|
status_indicator = "✅"
|
88
91
|
status_color = "green"
|
89
92
|
|
@@ -6,10 +6,12 @@ from .help import HelpScreen
|
|
6
6
|
from .mcp_install_wizard import MCPInstallWizardScreen
|
7
7
|
from .settings import SettingsScreen
|
8
8
|
from .tools import ToolsScreen
|
9
|
+
from .autosave_picker import AutosavePicker
|
9
10
|
|
10
11
|
__all__ = [
|
11
12
|
"HelpScreen",
|
12
13
|
"SettingsScreen",
|
13
14
|
"ToolsScreen",
|
14
15
|
"MCPInstallWizardScreen",
|
16
|
+
"AutosavePicker",
|
15
17
|
]
|
@@ -0,0 +1,166 @@
|
|
1
|
+
"""
|
2
|
+
Autosave Picker modal for TUI.
|
3
|
+
Lists recent autosave sessions and lets the user load one.
|
4
|
+
"""
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
import json
|
8
|
+
from dataclasses import dataclass
|
9
|
+
from datetime import datetime
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import List, Optional, Tuple
|
12
|
+
|
13
|
+
from textual import on
|
14
|
+
from textual.app import ComposeResult
|
15
|
+
from textual.containers import Container, Horizontal
|
16
|
+
from textual.screen import ModalScreen
|
17
|
+
from textual.widgets import Button, Label, ListItem, ListView, Static
|
18
|
+
|
19
|
+
from code_puppy.session_storage import list_sessions
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass(slots=True)
|
23
|
+
class AutosaveEntry:
|
24
|
+
name: str
|
25
|
+
timestamp: Optional[str]
|
26
|
+
message_count: Optional[int]
|
27
|
+
|
28
|
+
|
29
|
+
def _load_metadata(base_dir: Path, name: str) -> Tuple[Optional[str], Optional[int]]:
|
30
|
+
meta_path = base_dir / f"{name}_meta.json"
|
31
|
+
try:
|
32
|
+
with meta_path.open("r", encoding="utf-8") as meta_file:
|
33
|
+
data = json.load(meta_file)
|
34
|
+
return data.get("timestamp"), data.get("message_count")
|
35
|
+
except Exception:
|
36
|
+
return None, None
|
37
|
+
|
38
|
+
|
39
|
+
class AutosavePicker(ModalScreen):
|
40
|
+
"""Modal to present available autosave sessions for selection."""
|
41
|
+
|
42
|
+
DEFAULT_CSS = """
|
43
|
+
AutosavePicker {
|
44
|
+
align: center middle;
|
45
|
+
}
|
46
|
+
|
47
|
+
#modal-container {
|
48
|
+
width: 80%;
|
49
|
+
max-width: 100;
|
50
|
+
height: 24;
|
51
|
+
min-height: 18;
|
52
|
+
background: $surface;
|
53
|
+
border: solid $primary;
|
54
|
+
padding: 1 2;
|
55
|
+
layout: vertical;
|
56
|
+
}
|
57
|
+
|
58
|
+
#list-label {
|
59
|
+
width: 100%;
|
60
|
+
height: 1;
|
61
|
+
color: $text;
|
62
|
+
text-align: left;
|
63
|
+
}
|
64
|
+
|
65
|
+
#autosave-list {
|
66
|
+
height: 1fr;
|
67
|
+
overflow: auto;
|
68
|
+
border: solid $primary-darken-2;
|
69
|
+
background: $surface-darken-1;
|
70
|
+
margin: 1 0;
|
71
|
+
}
|
72
|
+
|
73
|
+
.button-row {
|
74
|
+
height: 3;
|
75
|
+
align-horizontal: right;
|
76
|
+
margin-top: 1;
|
77
|
+
}
|
78
|
+
|
79
|
+
#cancel-button { background: $primary-darken-1; }
|
80
|
+
#load-button { background: $success; }
|
81
|
+
"""
|
82
|
+
|
83
|
+
def __init__(self, autosave_dir: Path, **kwargs):
|
84
|
+
super().__init__(**kwargs)
|
85
|
+
self.autosave_dir = autosave_dir
|
86
|
+
self.entries: List[AutosaveEntry] = []
|
87
|
+
self.list_view: Optional[ListView] = None
|
88
|
+
|
89
|
+
def on_mount(self) -> None:
|
90
|
+
names = list_sessions(self.autosave_dir)
|
91
|
+
raw_entries: List[Tuple[str, Optional[str], Optional[int]]] = []
|
92
|
+
for name in names:
|
93
|
+
ts, count = _load_metadata(self.autosave_dir, name)
|
94
|
+
raw_entries.append((name, ts, count))
|
95
|
+
|
96
|
+
def sort_key(entry):
|
97
|
+
_, ts, _ = entry
|
98
|
+
if ts:
|
99
|
+
try:
|
100
|
+
return datetime.fromisoformat(ts)
|
101
|
+
except ValueError:
|
102
|
+
return datetime.min
|
103
|
+
return datetime.min
|
104
|
+
|
105
|
+
raw_entries.sort(key=sort_key, reverse=True)
|
106
|
+
self.entries = [AutosaveEntry(*e) for e in raw_entries]
|
107
|
+
|
108
|
+
# Populate the ListView now that entries are ready
|
109
|
+
if self.list_view is None:
|
110
|
+
try:
|
111
|
+
self.list_view = self.query_one("#autosave-list", ListView)
|
112
|
+
except Exception:
|
113
|
+
self.list_view = None
|
114
|
+
|
115
|
+
if self.list_view is not None:
|
116
|
+
# Clear existing items if any
|
117
|
+
try:
|
118
|
+
self.list_view.clear()
|
119
|
+
except Exception:
|
120
|
+
# Fallback: remove children manually
|
121
|
+
self.list_view.children.clear() # type: ignore
|
122
|
+
|
123
|
+
for entry in self.entries[:50]:
|
124
|
+
ts = entry.timestamp or "unknown time"
|
125
|
+
count = f"{entry.message_count} msgs" if entry.message_count is not None else "unknown size"
|
126
|
+
label = f"{entry.name} — {count}, saved at {ts}"
|
127
|
+
self.list_view.append(ListItem(Static(label)))
|
128
|
+
|
129
|
+
# Focus and select first item for better UX
|
130
|
+
if len(self.entries) > 0:
|
131
|
+
self.list_view.index = 0
|
132
|
+
self.list_view.focus()
|
133
|
+
|
134
|
+
def compose(self) -> ComposeResult:
|
135
|
+
with Container(id="modal-container"):
|
136
|
+
yield Label("Select an autosave to load (Esc to cancel)", id="list-label")
|
137
|
+
self.list_view = ListView(id="autosave-list")
|
138
|
+
# populate items
|
139
|
+
for entry in self.entries[:50]: # cap to avoid long lists
|
140
|
+
ts = entry.timestamp or "unknown time"
|
141
|
+
count = f"{entry.message_count} msgs" if entry.message_count is not None else "unknown size"
|
142
|
+
label = f"{entry.name} — {count}, saved at {ts}"
|
143
|
+
self.list_view.append(ListItem(Static(label)))
|
144
|
+
yield self.list_view
|
145
|
+
with Horizontal(classes="button-row"):
|
146
|
+
yield Button("Cancel", id="cancel-button")
|
147
|
+
yield Button("Load", id="load-button", variant="primary")
|
148
|
+
|
149
|
+
@on(Button.Pressed, "#cancel-button")
|
150
|
+
def cancel(self) -> None:
|
151
|
+
self.dismiss(None)
|
152
|
+
|
153
|
+
@on(Button.Pressed, "#load-button")
|
154
|
+
def load_selected(self) -> None:
|
155
|
+
if not self.list_view or not self.entries:
|
156
|
+
self.dismiss(None)
|
157
|
+
return
|
158
|
+
idx = self.list_view.index if self.list_view.index is not None else 0
|
159
|
+
if 0 <= idx < len(self.entries):
|
160
|
+
self.dismiss(self.entries[idx].name)
|
161
|
+
else:
|
162
|
+
self.dismiss(None)
|
163
|
+
|
164
|
+
def on_list_view_selected(self, event: ListView.Selected) -> None: # type: ignore
|
165
|
+
# Double-enter may select; we just map to load button
|
166
|
+
self.load_selected()
|
@@ -4,7 +4,7 @@ Settings modal screen.
|
|
4
4
|
|
5
5
|
from textual import on
|
6
6
|
from textual.app import ComposeResult
|
7
|
-
from textual.containers import Container
|
7
|
+
from textual.containers import Container, VerticalScroll
|
8
8
|
from textual.screen import ModalScreen
|
9
9
|
from textual.widgets import Button, Input, Select, Static
|
10
10
|
|
@@ -27,6 +27,7 @@ class SettingsScreen(ModalScreen):
|
|
27
27
|
|
28
28
|
#settings-form {
|
29
29
|
height: 1fr;
|
30
|
+
overflow: auto;
|
30
31
|
}
|
31
32
|
|
32
33
|
.setting-row {
|
@@ -70,7 +71,8 @@ class SettingsScreen(ModalScreen):
|
|
70
71
|
def compose(self) -> ComposeResult:
|
71
72
|
with Container(id="settings-dialog"):
|
72
73
|
yield Static("⚙️ Settings Configuration", id="settings-title")
|
73
|
-
|
74
|
+
# Make the form scrollable so long content fits
|
75
|
+
with VerticalScroll(id="settings-form"):
|
74
76
|
with Container(classes="setting-row"):
|
75
77
|
yield Static("Puppy Name:", classes="setting-label")
|
76
78
|
yield Input(id="puppy-name-input", classes="setting-input")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: code-puppy
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.198
|
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
|
@@ -25,7 +25,7 @@ Requires-Dist: logfire>=0.7.1
|
|
25
25
|
Requires-Dist: openai>=1.99.1
|
26
26
|
Requires-Dist: pathspec>=0.11.0
|
27
27
|
Requires-Dist: playwright>=1.40.0
|
28
|
-
Requires-Dist: prompt-toolkit>=3.0.
|
28
|
+
Requires-Dist: prompt-toolkit>=3.0.52
|
29
29
|
Requires-Dist: pydantic-ai==1.0.6
|
30
30
|
Requires-Dist: pydantic>=2.4.0
|
31
31
|
Requires-Dist: pyjwt>=2.8.0
|
@@ -69,6 +69,17 @@ Code Puppy is an AI-powered code generation agent, designed to understand progra
|
|
69
69
|
|
70
70
|
## Features
|
71
71
|
|
72
|
+
### Session Autosave & Contexts
|
73
|
+
- Autosaves live in `~/.code_puppy/autosaves` and include a `.pkl` and `_meta.json` per session.
|
74
|
+
- On startup, you’ll be prompted to optionally load a recent autosave (with message counts and timestamps).
|
75
|
+
- Autosaves use a stable session ID per interactive run so subsequent prompts overwrite the same session (not N new files). Rotate via `/session new` when you want a fresh session.
|
76
|
+
- Loading an autosave makes it the active autosave target (future autosaves overwrite that loaded session).
|
77
|
+
- Loading a manual context with `/load_context <name>` automatically rotates the autosave ID to avoid overwriting anything.
|
78
|
+
- Helpers:
|
79
|
+
- `/session id` shows the current autosave ID and file prefix
|
80
|
+
- `/session new` rotates the autosave ID
|
81
|
+
|
82
|
+
|
72
83
|
- **Multi-language support**: Capable of generating code in various programming languages.
|
73
84
|
- **Interactive CLI**: A command-line interface for interactive use.
|
74
85
|
- **Detailed explanations**: Provides insights into generated code to understand its logic and structure.
|
@@ -1,9 +1,9 @@
|
|
1
1
|
code_puppy/__init__.py,sha256=ehbM1-wMjNmOXk_DBhhJECFyBv2dRHwwo7ucjHeM68E,107
|
2
2
|
code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
|
3
3
|
code_puppy/callbacks.py,sha256=ukSgVFaEO68o6J09qFwDrnmNanrVv3toTLQhS504Meo,6162
|
4
|
-
code_puppy/config.py,sha256=
|
4
|
+
code_puppy/config.py,sha256=xT-nU1U4n7u8pyzJPG18-cJZBKv5OZI2CtHLt9DGRzU,26065
|
5
5
|
code_puppy/http_utils.py,sha256=YLd8Y16idbI32JGeBXG8n5rT4o4X_zxk9FgUvK9XFo8,8248
|
6
|
-
code_puppy/main.py,sha256=
|
6
|
+
code_puppy/main.py,sha256=TIFaySHV3um9Q3BDUjCjh6s-WWqcNnTY3EsD2WdW6MQ,22245
|
7
7
|
code_puppy/model_factory.py,sha256=ZbIAJWMNKNdTCEMQK8Ig6TDDZlVNyGO9hOLHoLLPMYw,15397
|
8
8
|
code_puppy/models.json,sha256=dClUciCo2RlVDs0ZAQCIur8MOavZUEAXHEecn0uPa-4,1629
|
9
9
|
code_puppy/reopenable_async_client.py,sha256=4UJRaMp5np8cbef9F0zKQ7TPKOfyf5U-Kv-0zYUWDho,8274
|
@@ -27,15 +27,15 @@ code_puppy/agents/agent_qa_expert.py,sha256=wCGXzuAVElT5c-QigQVb8JX9Gw0JmViCUQQn
|
|
27
27
|
code_puppy/agents/agent_qa_kitten.py,sha256=5PeFFSwCFlTUvP6h5bGntx0xv5NmRwBiw0HnMqY8nLI,9107
|
28
28
|
code_puppy/agents/agent_security_auditor.py,sha256=ADafi2x4gqXw6m-Nch5vjiKjO0Urcbj0x4zxHti3gDw,3712
|
29
29
|
code_puppy/agents/agent_typescript_reviewer.py,sha256=EDY1mFkVpuJ1BPXsJFu2wQ2pfAV-90ipc_8w9ymrKPg,4054
|
30
|
-
code_puppy/agents/base_agent.py,sha256=
|
31
|
-
code_puppy/agents/json_agent.py,sha256=
|
30
|
+
code_puppy/agents/base_agent.py,sha256=ikeV6Sui3HAagJBPTtI9T9pCDCFTCYDNEkFFw7XU21Y,39084
|
31
|
+
code_puppy/agents/json_agent.py,sha256=lhopDJDoiSGHvD8A6t50hi9ZBoNRKgUywfxd0Po_Dzc,4886
|
32
32
|
code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
|
33
|
-
code_puppy/command_line/command_handler.py,sha256=
|
33
|
+
code_puppy/command_line/command_handler.py,sha256=alxMe5v_4jq8Sm6HETsgfF-VoDtgExj9dVzxP77fwmY,31614
|
34
34
|
code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
|
35
35
|
code_puppy/command_line/load_context_completion.py,sha256=6eZxV6Bs-EFwZjN93V8ZDZUC-6RaWxvtZk-04Wtikyw,2240
|
36
36
|
code_puppy/command_line/model_picker_completion.py,sha256=vYNCZS1QWu6fxF__hTwpc7jwH7h_48wUxrnITawc83E,4140
|
37
37
|
code_puppy/command_line/motd.py,sha256=PEdkp3ZnydVfvd7mNJylm8YyFNUKg9jmY6uwkA1em8c,2152
|
38
|
-
code_puppy/command_line/prompt_toolkit_completion.py,sha256=
|
38
|
+
code_puppy/command_line/prompt_toolkit_completion.py,sha256=_8SUUCKfjOxgNMkcHTeB08NIjCeqL3utHljROXLTEZE,10656
|
39
39
|
code_puppy/command_line/utils.py,sha256=7eyxDHjPjPB9wGDJQQcXV_zOsGdYsFgI0SGCetVmTqE,1251
|
40
40
|
code_puppy/command_line/mcp/__init__.py,sha256=0-OQuwjq_pLiTVJ1_NrirVwdRerghyKs_MTZkwPC7YY,315
|
41
41
|
code_puppy/command_line/mcp/add_command.py,sha256=lZ09RpFDIeghX1zhc2YIAqBASs5Ra52x5YAasUKvqJg,6409
|
@@ -101,7 +101,7 @@ code_puppy/tools/browser/browser_workflows.py,sha256=jplJ1T60W3G4-dhVJX-CXkm9ssk
|
|
101
101
|
code_puppy/tools/browser/camoufox_manager.py,sha256=RYvLcs0iAoVNtpLjrrA1uu6a5k9tAdBbmhWFGSWjX_A,6106
|
102
102
|
code_puppy/tools/browser/vqa_agent.py,sha256=0GMDgJAK728rIuSQxAVytFSNagjo0LCjCUxBTm3w9Po,1952
|
103
103
|
code_puppy/tui/__init__.py,sha256=XesAxIn32zLPOmvpR2wIDxDAnnJr81a5pBJB4cZp1Xs,321
|
104
|
-
code_puppy/tui/app.py,sha256=
|
104
|
+
code_puppy/tui/app.py,sha256=D-8qHzxYbe-bVgrkBLl2lLBw7HRbUoVqDTRKy1gaE-E,44279
|
105
105
|
code_puppy/tui/messages.py,sha256=zQoToWI0eWdT36NEsY6RdCFzcDfAmfvoPlHv8jiCbgo,720
|
106
106
|
code_puppy/tui/components/__init__.py,sha256=uj5pnk3s6SEN3SbFI0ZnzaA2KK1NNg8TfUj6U-Z732U,455
|
107
107
|
code_puppy/tui/components/chat_view.py,sha256=Ff6uM6J0yENISNAOYroX7F-JL73_ajUUcP5IZSf2mng,19914
|
@@ -109,21 +109,22 @@ code_puppy/tui/components/command_history_modal.py,sha256=pUPEQvoCWa2iUnuMgNwO22
|
|
109
109
|
code_puppy/tui/components/copy_button.py,sha256=E4-OJYk5YNzDf-E81NyiVGKsTRPrUX-RnQ8qFuVnabw,4375
|
110
110
|
code_puppy/tui/components/custom_widgets.py,sha256=qsVsPLh_oUjMWBznewH8Ya1BdGSiIwNiad2qkdfvCJk,2114
|
111
111
|
code_puppy/tui/components/human_input_modal.py,sha256=isj-zrSIcK5iy3L7HJNgDFWN1zhxY4f3zvp4krbs07E,5424
|
112
|
-
code_puppy/tui/components/input_area.py,sha256=
|
112
|
+
code_puppy/tui/components/input_area.py,sha256=RRnprt1_mIelXla_tmv1PFS8oTwdDAuB054S5Pnbea8,4397
|
113
113
|
code_puppy/tui/components/sidebar.py,sha256=nGtCiYzZalPmiFaJ4dwj2S4EJBu5wQZVzhoigYYY7U4,10369
|
114
|
-
code_puppy/tui/components/status_bar.py,sha256=
|
114
|
+
code_puppy/tui/components/status_bar.py,sha256=TCtfQ0w6NXjaN-0eRsbhKrufDQNEKJ5UiBNj_r70664,6471
|
115
115
|
code_puppy/tui/models/__init__.py,sha256=5Eq7BMibz-z_t_v7B4H4tCdKRG41i2CaCuNQf_lteAE,140
|
116
116
|
code_puppy/tui/models/chat_message.py,sha256=2fSqsl4EHKgGsi_cVKWBbFq1NQwZyledGuJ9djovtLY,477
|
117
117
|
code_puppy/tui/models/command_history.py,sha256=bPWr_xnyQvjG5tPg_5pwqlEzn2fR170HlvBJwAXRpAE,2895
|
118
118
|
code_puppy/tui/models/enums.py,sha256=1ulsei95Gxy4r1sk-m-Sm5rdmejYCGRI-YtUwJmKFfM,501
|
119
|
-
code_puppy/tui/screens/__init__.py,sha256=
|
119
|
+
code_puppy/tui/screens/__init__.py,sha256=qxiJKyO3MKCNdPjUuHA2-Pnpda0JN20n7e9sU25eC9M,352
|
120
|
+
code_puppy/tui/screens/autosave_picker.py,sha256=9bazha2C5N3Xg_VcmpcTv-CYOBq_mwcECBfrQM9tHxA,5416
|
120
121
|
code_puppy/tui/screens/help.py,sha256=eJuPaOOCp7ZSUlecearqsuX6caxWv7NQszUh0tZJjBM,3232
|
121
122
|
code_puppy/tui/screens/mcp_install_wizard.py,sha256=vObpQwLbXjQsxmSg-WCasoev1usEi0pollKnL0SHu9U,27693
|
122
|
-
code_puppy/tui/screens/settings.py,sha256
|
123
|
+
code_puppy/tui/screens/settings.py,sha256=EsoL_gbN5FpEXGuDqhtdDznNZy_eGNMMuZnWuARSWi8,10790
|
123
124
|
code_puppy/tui/screens/tools.py,sha256=3pr2Xkpa9Js6Yhf1A3_wQVRzFOui-KDB82LwrsdBtyk,1715
|
124
|
-
code_puppy-0.0.
|
125
|
-
code_puppy-0.0.
|
126
|
-
code_puppy-0.0.
|
127
|
-
code_puppy-0.0.
|
128
|
-
code_puppy-0.0.
|
129
|
-
code_puppy-0.0.
|
125
|
+
code_puppy-0.0.198.data/data/code_puppy/models.json,sha256=dClUciCo2RlVDs0ZAQCIur8MOavZUEAXHEecn0uPa-4,1629
|
126
|
+
code_puppy-0.0.198.dist-info/METADATA,sha256=p-54EnWxhZ2h9yAcVbjToU39Fqch7Si6kmSVHCtqAWA,20759
|
127
|
+
code_puppy-0.0.198.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
128
|
+
code_puppy-0.0.198.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
|
129
|
+
code_puppy-0.0.198.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
130
|
+
code_puppy-0.0.198.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|