code-puppy 0.0.325__py3-none-any.whl → 0.0.341__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 +110 -124
- code_puppy/claude_cache_client.py +208 -2
- code_puppy/cli_runner.py +152 -32
- code_puppy/command_line/add_model_menu.py +4 -0
- code_puppy/command_line/autosave_menu.py +23 -24
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +5 -0
- code_puppy/command_line/config_commands.py +24 -1
- code_puppy/command_line/core_commands.py +85 -0
- code_puppy/command_line/diff_menu.py +5 -0
- code_puppy/command_line/mcp/custom_server_form.py +4 -0
- code_puppy/command_line/mcp/install_menu.py +5 -1
- code_puppy/command_line/model_settings_menu.py +5 -0
- code_puppy/command_line/motd.py +13 -7
- code_puppy/command_line/onboarding_slides.py +180 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/prompt_toolkit_completion.py +118 -0
- code_puppy/config.py +3 -2
- code_puppy/http_utils.py +201 -279
- code_puppy/keymap.py +10 -8
- code_puppy/mcp_/managed_server.py +7 -11
- code_puppy/messaging/messages.py +3 -0
- code_puppy/messaging/rich_renderer.py +114 -22
- code_puppy/model_factory.py +102 -15
- code_puppy/models.json +2 -2
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +668 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +664 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -0
- code_puppy/plugins/claude_code_oauth/utils.py +126 -7
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/terminal_utils.py +295 -3
- code_puppy/tools/command_runner.py +43 -54
- code_puppy/tools/common.py +3 -9
- code_puppy/uvx_detection.py +242 -0
- {code_puppy-0.0.325.data → code_puppy-0.0.341.data}/data/code_puppy/models.json +2 -2
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/METADATA +26 -49
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/RECORD +52 -36
- {code_puppy-0.0.325.data → code_puppy-0.0.341.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.325.dist-info → code_puppy-0.0.341.dist-info}/licenses/LICENSE +0 -0
code_puppy/cli_runner.py
CHANGED
|
@@ -17,14 +17,12 @@ 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
|
|
27
24
|
from code_puppy.command_line.attachments import parse_prompt_attachments
|
|
25
|
+
from code_puppy.command_line.clipboard import get_clipboard_manager
|
|
28
26
|
from code_puppy.config import (
|
|
29
27
|
AUTOSAVE_DIR,
|
|
30
28
|
COMMAND_HISTORY_FILE,
|
|
@@ -43,6 +41,7 @@ from code_puppy.keymap import (
|
|
|
43
41
|
)
|
|
44
42
|
from code_puppy.messaging import emit_info
|
|
45
43
|
from code_puppy.terminal_utils import (
|
|
44
|
+
print_truecolor_warning,
|
|
46
45
|
reset_unix_terminal,
|
|
47
46
|
reset_windows_terminal_ansi,
|
|
48
47
|
reset_windows_terminal_full,
|
|
@@ -91,7 +90,6 @@ async def main():
|
|
|
91
90
|
"command", nargs="*", help="Run a single command (deprecated, use -p instead)"
|
|
92
91
|
)
|
|
93
92
|
args = parser.parse_args()
|
|
94
|
-
from rich.console import Console
|
|
95
93
|
|
|
96
94
|
from code_puppy.messaging import (
|
|
97
95
|
RichConsoleRenderer,
|
|
@@ -146,6 +144,9 @@ async def main():
|
|
|
146
144
|
except ImportError:
|
|
147
145
|
emit_system_message("🐶 Code Puppy is Loading...")
|
|
148
146
|
|
|
147
|
+
# Truecolor warning moved to interactive_mode() so it prints LAST
|
|
148
|
+
# after all the help stuff - max visibility for the ugly red box!
|
|
149
|
+
|
|
149
150
|
available_port = find_available_port()
|
|
150
151
|
if available_port is None:
|
|
151
152
|
emit_error("No available ports in range 8090-9010!")
|
|
@@ -171,6 +172,45 @@ async def main():
|
|
|
171
172
|
emit_error(str(e))
|
|
172
173
|
sys.exit(1)
|
|
173
174
|
|
|
175
|
+
# Show uvx detection notice if we're on Windows + uvx
|
|
176
|
+
# Also disable Ctrl+C at the console level to prevent terminal bricking
|
|
177
|
+
try:
|
|
178
|
+
from code_puppy.uvx_detection import should_use_alternate_cancel_key
|
|
179
|
+
|
|
180
|
+
if should_use_alternate_cancel_key():
|
|
181
|
+
from code_puppy.terminal_utils import (
|
|
182
|
+
disable_windows_ctrl_c,
|
|
183
|
+
set_keep_ctrl_c_disabled,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Disable Ctrl+C at the console input level
|
|
187
|
+
# This prevents Ctrl+C from being processed as a signal at all
|
|
188
|
+
disable_windows_ctrl_c()
|
|
189
|
+
|
|
190
|
+
# Set flag to keep it disabled (prompt_toolkit may re-enable it)
|
|
191
|
+
set_keep_ctrl_c_disabled(True)
|
|
192
|
+
|
|
193
|
+
# Use print directly - emit_system_message can get cleared by ANSI codes
|
|
194
|
+
print(
|
|
195
|
+
"🔧 Detected uvx launch on Windows - using Ctrl+K for cancellation "
|
|
196
|
+
"(Ctrl+C is disabled to prevent terminal issues)"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Also install a SIGINT handler as backup
|
|
200
|
+
import signal
|
|
201
|
+
|
|
202
|
+
from code_puppy.terminal_utils import reset_windows_terminal_full
|
|
203
|
+
|
|
204
|
+
def _uvx_protective_sigint_handler(_sig, _frame):
|
|
205
|
+
"""Protective SIGINT handler for Windows+uvx."""
|
|
206
|
+
reset_windows_terminal_full()
|
|
207
|
+
# Re-disable Ctrl+C in case something re-enabled it
|
|
208
|
+
disable_windows_ctrl_c()
|
|
209
|
+
|
|
210
|
+
signal.signal(signal.SIGINT, _uvx_protective_sigint_handler)
|
|
211
|
+
except ImportError:
|
|
212
|
+
pass # uvx_detection module not available, ignore
|
|
213
|
+
|
|
174
214
|
# Load API keys from puppy.cfg into environment variables
|
|
175
215
|
from code_puppy.config import load_api_keys_to_environment
|
|
176
216
|
|
|
@@ -315,6 +355,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
315
355
|
emit_system_message(
|
|
316
356
|
"Type @ for path completion, or /model to pick a model. Toggle multiline with Alt+M or F2; newline: Ctrl+J."
|
|
317
357
|
)
|
|
358
|
+
emit_system_message("Paste images: Ctrl+V (even on Mac!), F3, or /paste command.")
|
|
359
|
+
import platform
|
|
360
|
+
|
|
361
|
+
if platform.system() == "Darwin":
|
|
362
|
+
emit_system_message(
|
|
363
|
+
"💡 macOS tip: Use Ctrl+V (not Cmd+V) to paste images in terminal."
|
|
364
|
+
)
|
|
318
365
|
cancel_key = get_cancel_agent_display_name()
|
|
319
366
|
emit_system_message(
|
|
320
367
|
f"Press {cancel_key} during processing to cancel the current task or inference. Use Ctrl+X to interrupt running shell commands."
|
|
@@ -325,6 +372,7 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
325
372
|
emit_system_message(
|
|
326
373
|
"Use /diff to configure diff highlighting colors for file changes."
|
|
327
374
|
)
|
|
375
|
+
emit_system_message("To re-run the tutorial, use /tutorial.")
|
|
328
376
|
try:
|
|
329
377
|
from code_puppy.command_line.motd import print_motd
|
|
330
378
|
|
|
@@ -334,6 +382,10 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
334
382
|
|
|
335
383
|
emit_warning(f"MOTD error: {e}")
|
|
336
384
|
|
|
385
|
+
# Print truecolor warning LAST so it's the most visible thing on startup
|
|
386
|
+
# Big ugly red box should be impossible to miss! 🔴
|
|
387
|
+
print_truecolor_warning(display_console)
|
|
388
|
+
|
|
337
389
|
# Initialize the runtime agent manager
|
|
338
390
|
if initial_command:
|
|
339
391
|
from code_puppy.agents import get_current_agent
|
|
@@ -417,6 +469,45 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
417
469
|
|
|
418
470
|
# Autosave loading is now manual - use /autosave_load command
|
|
419
471
|
|
|
472
|
+
# Auto-run tutorial on first startup
|
|
473
|
+
try:
|
|
474
|
+
from code_puppy.command_line.onboarding_wizard import should_show_onboarding
|
|
475
|
+
|
|
476
|
+
if should_show_onboarding():
|
|
477
|
+
import asyncio
|
|
478
|
+
import concurrent.futures
|
|
479
|
+
|
|
480
|
+
from code_puppy.command_line.onboarding_wizard import run_onboarding_wizard
|
|
481
|
+
from code_puppy.config import set_model_name
|
|
482
|
+
from code_puppy.messaging import emit_info
|
|
483
|
+
|
|
484
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
485
|
+
future = executor.submit(lambda: asyncio.run(run_onboarding_wizard()))
|
|
486
|
+
result = future.result(timeout=300)
|
|
487
|
+
|
|
488
|
+
if result == "chatgpt":
|
|
489
|
+
emit_info("🔐 Starting ChatGPT OAuth flow...")
|
|
490
|
+
from code_puppy.plugins.chatgpt_oauth.oauth_flow import run_oauth_flow
|
|
491
|
+
|
|
492
|
+
run_oauth_flow()
|
|
493
|
+
set_model_name("chatgpt-gpt-5.2-codex")
|
|
494
|
+
elif result == "claude":
|
|
495
|
+
emit_info("🔐 Starting Claude Code OAuth flow...")
|
|
496
|
+
from code_puppy.plugins.claude_code_oauth.register_callbacks import (
|
|
497
|
+
_perform_authentication,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
_perform_authentication()
|
|
501
|
+
set_model_name("claude-code-claude-opus-4-5-20251101")
|
|
502
|
+
elif result == "completed":
|
|
503
|
+
emit_info("🎉 Tutorial complete! Happy coding!")
|
|
504
|
+
elif result == "skipped":
|
|
505
|
+
emit_info("⏭️ Tutorial skipped. Run /tutorial anytime!")
|
|
506
|
+
except Exception as e:
|
|
507
|
+
from code_puppy.messaging import emit_warning
|
|
508
|
+
|
|
509
|
+
emit_warning(f"Tutorial auto-start failed: {e}")
|
|
510
|
+
|
|
420
511
|
# Track the current agent task for cancellation on quit
|
|
421
512
|
current_agent_task = None
|
|
422
513
|
|
|
@@ -440,6 +531,15 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
440
531
|
task = await get_input_with_combined_completion(
|
|
441
532
|
get_prompt_with_active_model(), history_file=COMMAND_HISTORY_FILE
|
|
442
533
|
)
|
|
534
|
+
|
|
535
|
+
# Windows+uvx: Re-disable Ctrl+C after prompt_toolkit
|
|
536
|
+
# (prompt_toolkit restores console mode which re-enables Ctrl+C)
|
|
537
|
+
try:
|
|
538
|
+
from code_puppy.terminal_utils import ensure_ctrl_c_disabled
|
|
539
|
+
|
|
540
|
+
ensure_ctrl_c_disabled()
|
|
541
|
+
except ImportError:
|
|
542
|
+
pass
|
|
443
543
|
except ImportError:
|
|
444
544
|
# Fall back to basic input if prompt_toolkit is not available
|
|
445
545
|
task = input(">>> ")
|
|
@@ -479,6 +579,7 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
479
579
|
|
|
480
580
|
# Check for clear command (supports both `clear` and `/clear`)
|
|
481
581
|
if task.strip().lower() in ("clear", "/clear"):
|
|
582
|
+
from code_puppy.command_line.clipboard import get_clipboard_manager
|
|
482
583
|
from code_puppy.messaging import (
|
|
483
584
|
emit_info,
|
|
484
585
|
emit_system_message,
|
|
@@ -491,6 +592,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
491
592
|
emit_warning("Conversation history cleared!")
|
|
492
593
|
emit_system_message("The agent will not remember previous interactions.")
|
|
493
594
|
emit_info(f"Auto-save session rotated to: {new_session_id}")
|
|
595
|
+
|
|
596
|
+
# Also clear pending clipboard images
|
|
597
|
+
clipboard_manager = get_clipboard_manager()
|
|
598
|
+
clipboard_count = clipboard_manager.get_pending_count()
|
|
599
|
+
clipboard_manager.clear_pending()
|
|
600
|
+
if clipboard_count > 0:
|
|
601
|
+
emit_info(f"Cleared {clipboard_count} pending clipboard image(s)")
|
|
494
602
|
continue
|
|
495
603
|
|
|
496
604
|
# Parse attachments first so leading paths aren't misread as commands
|
|
@@ -591,8 +699,6 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
591
699
|
save_command_to_history(task)
|
|
592
700
|
|
|
593
701
|
try:
|
|
594
|
-
prettier_code_blocks()
|
|
595
|
-
|
|
596
702
|
# No need to get agent directly - use manager's run methods
|
|
597
703
|
|
|
598
704
|
# Use our custom helper to enable attachment handling with spinner support
|
|
@@ -605,6 +711,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
605
711
|
if result is None:
|
|
606
712
|
# Windows-specific: Reset terminal state after cancellation
|
|
607
713
|
reset_windows_terminal_ansi()
|
|
714
|
+
# Re-disable Ctrl+C if needed (uvx mode)
|
|
715
|
+
try:
|
|
716
|
+
from code_puppy.terminal_utils import ensure_ctrl_c_disabled
|
|
717
|
+
|
|
718
|
+
ensure_ctrl_c_disabled()
|
|
719
|
+
except ImportError:
|
|
720
|
+
pass
|
|
608
721
|
continue
|
|
609
722
|
# Get the structured response
|
|
610
723
|
agent_response = result.output
|
|
@@ -645,27 +758,14 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
645
758
|
|
|
646
759
|
auto_save_session_if_enabled()
|
|
647
760
|
|
|
761
|
+
# Re-disable Ctrl+C if needed (uvx mode) - must be done after
|
|
762
|
+
# each iteration as various operations may restore console mode
|
|
763
|
+
try:
|
|
764
|
+
from code_puppy.terminal_utils import ensure_ctrl_c_disabled
|
|
648
765
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
class SimpleCodeBlock(CodeBlock):
|
|
653
|
-
def __rich_console__(
|
|
654
|
-
self, console: Console, options: ConsoleOptions
|
|
655
|
-
) -> RenderResult:
|
|
656
|
-
code = str(self.text).rstrip()
|
|
657
|
-
yield Text(self.lexer_name, style="dim")
|
|
658
|
-
syntax = Syntax(
|
|
659
|
-
code,
|
|
660
|
-
self.lexer_name,
|
|
661
|
-
theme=self.theme,
|
|
662
|
-
background_color="default",
|
|
663
|
-
line_numbers=True,
|
|
664
|
-
)
|
|
665
|
-
yield syntax
|
|
666
|
-
yield Text(f"/{self.lexer_name}", style="dim")
|
|
667
|
-
|
|
668
|
-
Markdown.elements["fence"] = SimpleCodeBlock
|
|
766
|
+
ensure_ctrl_c_disabled()
|
|
767
|
+
except ImportError:
|
|
768
|
+
pass
|
|
669
769
|
|
|
670
770
|
|
|
671
771
|
async def run_prompt_with_attachments(
|
|
@@ -681,6 +781,7 @@ async def run_prompt_with_attachments(
|
|
|
681
781
|
tuple: (result, task) where result is the agent response and task is the asyncio task
|
|
682
782
|
"""
|
|
683
783
|
import asyncio
|
|
784
|
+
import re
|
|
684
785
|
|
|
685
786
|
from code_puppy.messaging import emit_system_message, emit_warning
|
|
686
787
|
|
|
@@ -689,21 +790,41 @@ async def run_prompt_with_attachments(
|
|
|
689
790
|
for warning in processed_prompt.warnings:
|
|
690
791
|
emit_warning(warning)
|
|
691
792
|
|
|
793
|
+
# Get clipboard images and merge with file attachments
|
|
794
|
+
clipboard_manager = get_clipboard_manager()
|
|
795
|
+
clipboard_images = clipboard_manager.get_pending_images()
|
|
796
|
+
|
|
797
|
+
# Clear pending clipboard images after retrieval
|
|
798
|
+
clipboard_manager.clear_pending()
|
|
799
|
+
|
|
800
|
+
# Build summary of all attachments
|
|
692
801
|
summary_parts = []
|
|
693
802
|
if processed_prompt.attachments:
|
|
694
|
-
summary_parts.append(f"
|
|
803
|
+
summary_parts.append(f"files: {len(processed_prompt.attachments)}")
|
|
804
|
+
if clipboard_images:
|
|
805
|
+
summary_parts.append(f"clipboard images: {len(clipboard_images)}")
|
|
695
806
|
if processed_prompt.link_attachments:
|
|
696
807
|
summary_parts.append(f"urls: {len(processed_prompt.link_attachments)}")
|
|
697
808
|
if summary_parts:
|
|
698
809
|
emit_system_message("Attachments detected -> " + ", ".join(summary_parts))
|
|
699
810
|
|
|
700
|
-
|
|
811
|
+
# Clean up clipboard placeholders from the prompt text
|
|
812
|
+
cleaned_prompt = processed_prompt.prompt
|
|
813
|
+
if clipboard_images and cleaned_prompt:
|
|
814
|
+
cleaned_prompt = re.sub(
|
|
815
|
+
r"\[📋 clipboard image \d+\]\s*", "", cleaned_prompt
|
|
816
|
+
).strip()
|
|
817
|
+
|
|
818
|
+
if not cleaned_prompt:
|
|
701
819
|
emit_warning(
|
|
702
820
|
"Prompt is empty after removing attachments; add instructions and retry."
|
|
703
821
|
)
|
|
704
822
|
return None, None
|
|
705
823
|
|
|
824
|
+
# Combine file attachments with clipboard images
|
|
706
825
|
attachments = [attachment.content for attachment in processed_prompt.attachments]
|
|
826
|
+
attachments.extend(clipboard_images) # Add clipboard images
|
|
827
|
+
|
|
707
828
|
link_attachments = [link.url_part for link in processed_prompt.link_attachments]
|
|
708
829
|
|
|
709
830
|
# IMPORTANT: Set the shared console on the agent so that streaming output
|
|
@@ -715,7 +836,7 @@ async def run_prompt_with_attachments(
|
|
|
715
836
|
# Create the agent task first so we can track and cancel it
|
|
716
837
|
agent_task = asyncio.create_task(
|
|
717
838
|
agent.run_with_mcp(
|
|
718
|
-
|
|
839
|
+
cleaned_prompt, # Use cleaned prompt (clipboard placeholders removed)
|
|
719
840
|
attachments=attachments,
|
|
720
841
|
link_attachments=link_attachments,
|
|
721
842
|
)
|
|
@@ -790,6 +911,5 @@ def main_entry():
|
|
|
790
911
|
DBOS.destroy()
|
|
791
912
|
return 0
|
|
792
913
|
finally:
|
|
793
|
-
# Reset terminal on
|
|
794
|
-
reset_windows_terminal_full() # Safe no-op on non-Windows
|
|
914
|
+
# Reset terminal on Unix-like systems (not Windows)
|
|
795
915
|
reset_unix_terminal()
|
|
@@ -995,6 +995,10 @@ class AddModelMenu:
|
|
|
995
995
|
# Reset awaiting input flag
|
|
996
996
|
set_awaiting_user_input(False)
|
|
997
997
|
|
|
998
|
+
# Clear exit message (unless we're about to prompt for more input)
|
|
999
|
+
if self.result not in ("pending_credentials", "pending_custom_model"):
|
|
1000
|
+
emit_info("✓ Exited model browser")
|
|
1001
|
+
|
|
998
1002
|
# Handle unsupported provider
|
|
999
1003
|
if self.result == "unsupported" and self.current_provider:
|
|
1000
1004
|
reason = UNSUPPORTED_PROVIDERS.get(
|
|
@@ -69,12 +69,21 @@ def _get_session_entries(base_dir: Path) -> List[Tuple[str, dict]]:
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def _extract_last_user_message(history: list) -> str:
|
|
72
|
-
"""Extract the most recent user message from history.
|
|
72
|
+
"""Extract the most recent user message from history.
|
|
73
|
+
|
|
74
|
+
Joins all content parts from the message since messages can have
|
|
75
|
+
multiple parts (e.g., text + attachments, multi-part prompts).
|
|
76
|
+
"""
|
|
73
77
|
# Walk backwards through history to find last user message
|
|
74
78
|
for msg in reversed(history):
|
|
79
|
+
content_parts = []
|
|
75
80
|
for part in msg.parts:
|
|
76
81
|
if hasattr(part, "content"):
|
|
77
|
-
|
|
82
|
+
content = part.content
|
|
83
|
+
if isinstance(content, str) and content.strip():
|
|
84
|
+
content_parts.append(content)
|
|
85
|
+
if content_parts:
|
|
86
|
+
return "\n\n".join(content_parts)
|
|
78
87
|
return "[No messages found]"
|
|
79
88
|
|
|
80
89
|
|
|
@@ -298,19 +307,13 @@ def _render_message_browser_panel(
|
|
|
298
307
|
# Don't override Rich's ANSI styling - use empty style
|
|
299
308
|
text_color = ""
|
|
300
309
|
|
|
301
|
-
#
|
|
302
|
-
message_lines = rendered.split("\n")
|
|
303
|
-
is_truncated = len(rendered.split("\n")) > 35
|
|
310
|
+
# Show full message without truncation
|
|
311
|
+
message_lines = rendered.split("\n")
|
|
304
312
|
|
|
305
313
|
for line in message_lines:
|
|
306
314
|
lines.append((text_color, f" {line}"))
|
|
307
315
|
lines.append(("", "\n"))
|
|
308
316
|
|
|
309
|
-
if is_truncated:
|
|
310
|
-
lines.append(("", "\n"))
|
|
311
|
-
lines.append(("fg:yellow", " ... truncated (message too long)"))
|
|
312
|
-
lines.append(("", "\n"))
|
|
313
|
-
|
|
314
317
|
except Exception as e:
|
|
315
318
|
lines.append(("fg:red", f" Error rendering message: {e}"))
|
|
316
319
|
lines.append(("", "\n"))
|
|
@@ -359,7 +362,7 @@ def _render_preview_panel(base_dir: Path, entry: Optional[Tuple[str, dict]]) ->
|
|
|
359
362
|
lines.append(("", "\n\n"))
|
|
360
363
|
|
|
361
364
|
lines.append(("bold", " Last Message:"))
|
|
362
|
-
lines.append(("fg:ansibrightblack", " (press 'e' to browse
|
|
365
|
+
lines.append(("fg:ansibrightblack", " (press 'e' to browse full history)"))
|
|
363
366
|
lines.append(("", "\n"))
|
|
364
367
|
|
|
365
368
|
# Try to load and preview the last message
|
|
@@ -367,15 +370,11 @@ def _render_preview_panel(base_dir: Path, entry: Optional[Tuple[str, dict]]) ->
|
|
|
367
370
|
history = load_session(session_name, base_dir)
|
|
368
371
|
last_message = _extract_last_user_message(history)
|
|
369
372
|
|
|
370
|
-
#
|
|
371
|
-
original_lines = last_message.split("\n") if last_message else []
|
|
372
|
-
is_long = len(original_lines) > 30
|
|
373
|
-
|
|
374
|
-
# Render markdown with rich but strip ANSI codes
|
|
373
|
+
# Render markdown with rich
|
|
375
374
|
console = Console(
|
|
376
375
|
file=StringIO(),
|
|
377
376
|
legacy_windows=False,
|
|
378
|
-
no_color=False,
|
|
377
|
+
no_color=False,
|
|
379
378
|
force_terminal=False,
|
|
380
379
|
width=76,
|
|
381
380
|
)
|
|
@@ -383,19 +382,14 @@ def _render_preview_panel(base_dir: Path, entry: Optional[Tuple[str, dict]]) ->
|
|
|
383
382
|
console.print(md)
|
|
384
383
|
rendered = console.file.getvalue()
|
|
385
384
|
|
|
386
|
-
#
|
|
387
|
-
message_lines = rendered.split("\n")
|
|
385
|
+
# Show full message without truncation
|
|
386
|
+
message_lines = rendered.split("\n")
|
|
388
387
|
|
|
389
388
|
for line in message_lines:
|
|
390
389
|
# Rich already rendered the markdown, just display it dimmed
|
|
391
390
|
lines.append(("fg:ansibrightblack", f" {line}"))
|
|
392
391
|
lines.append(("", "\n"))
|
|
393
392
|
|
|
394
|
-
if is_long:
|
|
395
|
-
lines.append(("", "\n"))
|
|
396
|
-
lines.append(("fg:yellow", " ... truncated"))
|
|
397
|
-
lines.append(("", "\n"))
|
|
398
|
-
|
|
399
393
|
except Exception as e:
|
|
400
394
|
lines.append(("fg:red", f" Error loading preview: {e}"))
|
|
401
395
|
lines.append(("", "\n"))
|
|
@@ -603,4 +597,9 @@ async def interactive_autosave_picker() -> Optional[str]:
|
|
|
603
597
|
# Reset awaiting input flag
|
|
604
598
|
set_awaiting_user_input(False)
|
|
605
599
|
|
|
600
|
+
# Clear exit message
|
|
601
|
+
from code_puppy.messaging import emit_info
|
|
602
|
+
|
|
603
|
+
emit_info("✓ Exited session browser")
|
|
604
|
+
|
|
606
605
|
return result[0]
|