code-puppy 0.0.159__py3-none-any.whl → 0.0.161__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.
@@ -494,104 +494,6 @@ def share_your_reasoning(
494
494
  return ReasoningOutput(**{"success": True})
495
495
 
496
496
 
497
- def register_command_runner_tools(agent):
498
- @agent.tool
499
- def agent_run_shell_command(
500
- context: RunContext, command: str = "", cwd: str = None, timeout: int = 60
501
- ) -> ShellCommandOutput:
502
- """Execute a shell command with comprehensive monitoring and safety features.
503
-
504
- This tool provides robust shell command execution with streaming output,
505
- timeout handling, user confirmation (when not in yolo mode), and proper
506
- process lifecycle management. Commands are executed in a controlled
507
- environment with cross-platform process group handling.
508
-
509
- Args:
510
- context (RunContext): The PydanticAI runtime context for the agent.
511
- command (str): The shell command to execute. Cannot be empty or whitespace-only.
512
- cwd (str, optional): Working directory for command execution. If None,
513
- uses the current working directory. Defaults to None.
514
- timeout (int, optional): Inactivity timeout in seconds. If no output is
515
- produced for this duration, the process will be terminated.
516
- Defaults to 60 seconds.
517
-
518
- Returns:
519
- ShellCommandOutput: A structured response containing:
520
- - success (bool): True if command executed successfully (exit code 0)
521
- - command (str | None): The executed command string
522
- - error (str | None): Error message if execution failed
523
- - stdout (str | None): Standard output from the command (last 1000 lines)
524
- - stderr (str | None): Standard error from the command (last 1000 lines)
525
- - exit_code (int | None): Process exit code
526
- - execution_time (float | None): Total execution time in seconds
527
- - timeout (bool | None): True if command was terminated due to timeout
528
- - user_interrupted (bool | None): True if user killed the process
529
-
530
- Note:
531
- - In interactive mode (not yolo), user confirmation is required before execution
532
- - Commands have an absolute timeout of 270 seconds regardless of activity
533
- - Process groups are properly managed for clean termination
534
- - Output is streamed in real-time and displayed to the user
535
- - Large output is truncated to the last 1000 lines for memory efficiency
536
-
537
- Examples:
538
- >>> result = agent_run_shell_command(ctx, "ls -la", cwd="/tmp", timeout=30)
539
- >>> if result.success:
540
- ... print(f"Command completed in {result.execution_time:.2f}s")
541
- ... print(result.stdout)
542
-
543
- Warning:
544
- This tool can execute arbitrary shell commands. Exercise caution when
545
- running untrusted commands, especially those that modify system state.
546
- """
547
- result = run_shell_command(context, command, cwd, timeout)
548
- on_run_shell_command(result)
549
-
550
- @agent.tool
551
- def agent_share_your_reasoning(
552
- context: RunContext, reasoning: str = "", next_steps: str | None = None
553
- ) -> ReasoningOutput:
554
- """Share the agent's current reasoning and planned next steps with the user.
555
-
556
- This tool provides transparency into the agent's decision-making process
557
- by displaying the current reasoning and upcoming actions in a formatted,
558
- user-friendly manner. It's essential for building trust and understanding
559
- between the agent and user.
560
-
561
- Args:
562
- context (RunContext): The PydanticAI runtime context for the agent.
563
- reasoning (str): The agent's current thought process, analysis, or
564
- reasoning for the current situation. This should be clear,
565
- comprehensive, and explain the 'why' behind decisions.
566
- next_steps (str | None, optional): Planned upcoming actions or steps
567
- the agent intends to take. Can be None if no specific next steps
568
- are determined. Defaults to None.
569
-
570
- Returns:
571
- ReasoningOutput: A simple response object containing:
572
- - success (bool): Always True, indicating the reasoning was shared
573
-
574
- Note:
575
- - Reasoning is displayed with Markdown formatting for better readability
576
- - Next steps are only shown if provided and non-empty
577
- - Output is visually separated with dividers in TUI mode
578
- - This tool should be called before major actions to explain intent
579
-
580
- Examples:
581
- >>> reasoning = "I need to analyze the codebase structure before making changes"
582
- >>> next_steps = "First, I'll list the directory contents, then read key files"
583
- >>> result = agent_share_your_reasoning(ctx, reasoning, next_steps)
584
-
585
- Best Practice:
586
- Use this tool frequently to maintain transparency. Call it:
587
- - Before starting complex operations
588
- - When changing strategy or approach
589
- - To explain why certain decisions are being made
590
- - When encountering unexpected situations
591
- """
592
- return share_your_reasoning(context, reasoning, next_steps)
593
-
594
-
595
497
  def register_agent_run_shell_command(agent):
596
498
  """Register only the agent_run_shell_command tool."""
597
499
 
@@ -448,168 +448,6 @@ def _delete_file(
448
448
  return res
449
449
 
450
450
 
451
- def register_file_modifications_tools(agent):
452
- """Attach file-editing tools to *agent* with mandatory diff rendering."""
453
-
454
- @agent.tool(retries=5)
455
- def edit_file(context: RunContext, payload: EditFilePayload) -> Dict[str, Any]:
456
- """Comprehensive file editing tool supporting multiple modification strategies.
457
-
458
- This is the primary file modification tool that supports three distinct editing
459
- approaches: full content replacement, targeted text replacements, and snippet
460
- deletion. It provides robust diff generation, error handling, and automatic
461
- retry capabilities for reliable file operations.
462
-
463
- Args:
464
- context (RunContext): The PydanticAI runtime context for the agent.
465
- payload (EditFilePayload): One of three payload types:
466
-
467
- ContentPayload:
468
- - content (str): Full file content to write
469
- - overwrite (bool, optional): Whether to overwrite existing files.
470
- Defaults to False (safe mode).
471
-
472
- ReplacementsPayload:
473
- - replacements (List[Replacement]): List of text replacements where
474
- each Replacement contains:
475
- - old_str (str): Exact text to find and replace
476
- - new_str (str): Replacement text
477
-
478
- DeleteSnippetPayload:
479
- - delete_snippet (str): Exact text snippet to remove from file
480
-
481
- file_path (str): Path to the target file. Can be relative or absolute.
482
- File will be created if it doesn't exist (for ContentPayload).
483
-
484
- Returns:
485
- Dict[str, Any]: Operation result containing:
486
- - success (bool): True if operation completed successfully
487
- - path (str): Absolute path to the modified file
488
- - message (str): Human-readable description of what occurred
489
- - changed (bool): True if file content was actually modified
490
- - error (str, optional): Error message if operation failed
491
-
492
- Note:
493
- - Automatic retry (up to 5 attempts) for transient failures
494
- - Unified diff is generated and displayed for all operations
495
- - Fuzzy matching (Jaro-Winkler) used for replacements when exact match fails
496
- - Minimum similarity threshold of 0.95 for fuzzy replacements
497
- - Creates parent directories automatically when needed
498
- - UTF-8 encoding enforced for all file operations
499
-
500
- Examples:
501
- >>> # Create new file
502
- >>> payload = ContentPayload(file_path="foo.py", content="print('Hello World')")
503
- >>> result = edit_file(context, payload)
504
-
505
- >>> # Replace specific text
506
- >>> replacements = [Replacement(old_str="foo", new_str="bar")]
507
- >>> payload = ReplacementsPayload(file_path="foo.py", replacements=replacements)
508
- >>> result = edit_file(context, payload)
509
-
510
- >>> # Delete code block
511
- >>> payload = DeleteSnippetPayload(file_path="foo.py", delete_snippet="# TODO: remove this")
512
- >>> result = edit_file(context, payload)
513
-
514
- Warning:
515
- - Always verify file contents after modification
516
- - Use overwrite=False by default to prevent accidental data loss
517
- - Large files may be slow due to diff generation
518
- - Exact string matching required for reliable replacements
519
-
520
- Best Practice:
521
- - Use ReplacementsPayload for targeted changes to preserve file structure
522
- - Read file first to understand current content before modifications
523
- - Keep replacement strings specific and unique to avoid unintended matches
524
- - Test modifications on non-critical files first
525
- """
526
- # Generate group_id for edit_file tool execution
527
- if isinstance(payload, str):
528
- # Fallback for weird models that just can't help but send json strings...
529
- payload = json.loads(json_repair.repair_json(payload))
530
- if "replacements" in payload:
531
- payload = ReplacementsPayload(**payload)
532
- elif "delete_snippet" in payload:
533
- payload = DeleteSnippetPayload(**payload)
534
- elif "content" in payload:
535
- payload = ContentPayload(**payload)
536
- else:
537
- file_path = "Unknown"
538
- if "file_path" in payload:
539
- file_path = payload["file_path"]
540
- return {
541
- "success": False,
542
- "path": file_path,
543
- "message": "One of 'content', 'replacements', or 'delete_snippet' must be provided in payload.",
544
- "changed": False,
545
- }
546
- group_id = generate_group_id("edit_file", payload.file_path)
547
- result = _edit_file(context, payload, group_id)
548
- on_edit_file(result)
549
- if "diff" in result:
550
- del result["diff"]
551
- return result
552
-
553
- @agent.tool(retries=5)
554
- def delete_file(context: RunContext, file_path: str) -> Dict[str, Any]:
555
- """Safely delete files with comprehensive logging and diff generation.
556
-
557
- This tool provides safe file deletion with automatic diff generation to show
558
- exactly what content was removed. It includes proper error handling and
559
- automatic retry capabilities for reliable operation.
560
-
561
- Args:
562
- context (RunContext): The PydanticAI runtime context for the agent.
563
- file_path (str): Path to the file to delete. Can be relative or absolute.
564
- Must be an existing regular file (not a directory).
565
-
566
- Returns:
567
- Dict[str, Any]: Operation result containing:
568
- - success (bool): True if file was successfully deleted
569
- - path (str): Absolute path to the deleted file
570
- - message (str): Human-readable description of the operation
571
- - changed (bool): True if file was actually removed
572
- - error (str, optional): Error message if deletion failed
573
-
574
- Note:
575
- - Automatic retry (up to 5 attempts) for transient failures
576
- - Complete file content is captured and shown in diff before deletion
577
- - Only deletes regular files, not directories or special files
578
- - Generates unified diff showing all removed content
579
- - Error if file doesn't exist or is not accessible
580
-
581
- Examples:
582
- >>> # Delete temporary file
583
- >>> result = delete_file(ctx, "temp_output.txt")
584
- >>> if result['success']:
585
- ... print(f"Successfully deleted {result['path']}")
586
-
587
- >>> # Delete with error handling
588
- >>> result = delete_file(ctx, "config.bak")
589
- >>> if 'error' in result:
590
- ... print(f"Deletion failed: {result['error']}")
591
-
592
- Warning:
593
- - File deletion is irreversible - ensure you have backups if needed
594
- - Will not delete directories (use appropriate directory removal tools)
595
- - No "trash" or "recycle bin" - files are permanently removed
596
- - Check file importance before deletion
597
-
598
- Best Practice:
599
- - Always verify file path before deletion
600
- - Review the generated diff to confirm deletion scope
601
- - Consider moving files to backup location instead of deleting
602
- - Use in combination with list_files to verify target
603
- """
604
- # Generate group_id for delete_file tool execution
605
- group_id = generate_group_id("delete_file", file_path)
606
- result = _delete_file(context, file_path, message_group=group_id)
607
- on_delete_file(result)
608
- if "diff" in result:
609
- del result["diff"]
610
- return result
611
-
612
-
613
451
  def register_edit_file(agent):
614
452
  """Register only the edit_file tool."""
615
453
 
@@ -682,39 +520,31 @@ def register_edit_file(agent):
682
520
  """
683
521
  # Handle string payload parsing (for models that send JSON strings)
684
522
  if isinstance(payload, str):
685
- # Fallback for weird models that just can't help but send json strings...
686
- payload = json.loads(json_repair.repair_json(payload))
687
- if "replacements" in payload and "file_path" in payload:
688
- payload = ReplacementsPayload(**payload)
689
- elif "delete_snippet" in payload and "file_path" in payload:
690
- payload = DeleteSnippetPayload(**payload)
691
- elif "content" in payload and "file_path" in payload:
692
- payload = ContentPayload(**payload)
693
- else:
694
- file_path = "Unknown"
695
- if "file_path" in payload:
696
- file_path = payload["file_path"]
697
- # Diagnose what's missing
698
- missing = []
699
- if "file_path" not in payload:
700
- missing.append("file_path")
701
-
702
- payload_type = "unknown"
703
- if "content" in payload:
704
- payload_type = "content"
705
- elif "replacements" in payload:
706
- payload_type = "replacements"
523
+ try:
524
+ # Fallback for weird models that just can't help but send json strings...
525
+ payload = json.loads(json_repair.repair_json(payload))
526
+ if "replacements" in payload:
527
+ payload = ReplacementsPayload(**payload)
707
528
  elif "delete_snippet" in payload:
708
- payload_type = "delete_snippet"
529
+ payload = DeleteSnippetPayload(**payload)
530
+ elif "content" in payload:
531
+ payload = ContentPayload(**payload)
709
532
  else:
710
- missing.append("content/replacements/delete_snippet")
711
-
712
- missing_str = ", ".join(missing) if missing else "none"
533
+ file_path = "Unknown"
534
+ if "file_path" in payload:
535
+ file_path = payload["file_path"]
536
+ return {
537
+ "success": False,
538
+ "path": file_path,
539
+ "message": "One of 'content', 'replacements', or 'delete_snippet' must be provided in payload.",
540
+ "changed": False,
541
+ }
542
+ except Exception as e:
713
543
  return {
714
544
  "success": False,
715
545
  "path": file_path,
716
- "message": f"Invalid payload for {payload_type} operation. Missing required fields: {missing_str}. Payload keys: {list(payload.keys())}",
717
- "changed": False,
546
+ "message": f"edit_file call failed: {str(e)}",
547
+ "changed": False
718
548
  }
719
549
 
720
550
  # Call _edit_file which will extract file_path from payload and handle group_id generation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.159
3
+ Version: 0.0.161
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
@@ -78,9 +78,9 @@ code_puppy/messaging/spinner/textual_spinner.py,sha256=Omx9A-FSPkxYDMYgBXgYMBQnK
78
78
  code_puppy/plugins/__init__.py,sha256=fksDqMUiXPJ5WNuMsYsVR8ulueQRCXPlvECEyicHPtQ,1312
79
79
  code_puppy/tools/__init__.py,sha256=YiiXRqxU1BEJ5t0Oe163lSqOneI9sKtwDW0swCPgBt4,2119
80
80
  code_puppy/tools/agent_tools.py,sha256=bHMrFIbYRhuubR41G_XdLsk3cUKWfIPl2O4bVzo2pE0,5591
81
- code_puppy/tools/command_runner.py,sha256=GVNsgwjTFD9tkNlycgMNmMoVPdmMkZkbAcHH5y6iMww,26070
81
+ code_puppy/tools/command_runner.py,sha256=yH5v8An3N6wYmUVxcMITvPcpcBxXdvtZEcI5CalmWys,20939
82
82
  code_puppy/tools/common.py,sha256=pL-9xcRs3rxU7Fl9X9EUgbDp2-csh2LLJ5DHH_KAHKY,10596
83
- code_puppy/tools/file_modifications.py,sha256=oeNEQItqwMhGOeEN2TzGR7TjmgLsfFFdPaVMzWbfXIQ,30398
83
+ code_puppy/tools/file_modifications.py,sha256=YhOUK0EyhByKpjC85yzrepfhKcyYMV_9TwkToVubOaU,22125
84
84
  code_puppy/tools/file_operations.py,sha256=dfG1MDmpvbxsdapSAyo-FJoctC1XM6_AO8HM1PlxUIE,30894
85
85
  code_puppy/tools/tools_content.py,sha256=bsBqW-ppd1XNAS_g50B3UHDQBWEALC1UneH6-afz1zo,2365
86
86
  code_puppy/tui/__init__.py,sha256=XesAxIn32zLPOmvpR2wIDxDAnnJr81a5pBJB4cZp1Xs,321
@@ -104,9 +104,9 @@ code_puppy/tui/screens/help.py,sha256=eJuPaOOCp7ZSUlecearqsuX6caxWv7NQszUh0tZJjB
104
104
  code_puppy/tui/screens/mcp_install_wizard.py,sha256=xqwN5omltMkfxWZwXj3D2PbXbtrxUi1dT0XT77oxOKk,27685
105
105
  code_puppy/tui/screens/settings.py,sha256=GMpv-qa08rorAE9mj3AjmqjZFPhmeJ_GWd-DBHG6iAA,10671
106
106
  code_puppy/tui/screens/tools.py,sha256=3pr2Xkpa9Js6Yhf1A3_wQVRzFOui-KDB82LwrsdBtyk,1715
107
- code_puppy-0.0.159.data/data/code_puppy/models.json,sha256=iXmLZGflnQcu2DRh4WUlgAhoXdvoxUc7KBhB8YxawXM,3088
108
- code_puppy-0.0.159.dist-info/METADATA,sha256=jRdT4LBZq38nevqbxsJkIscP7I4KHAwvf02FdfCmlus,19567
109
- code_puppy-0.0.159.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
110
- code_puppy-0.0.159.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
111
- code_puppy-0.0.159.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
112
- code_puppy-0.0.159.dist-info/RECORD,,
107
+ code_puppy-0.0.161.data/data/code_puppy/models.json,sha256=iXmLZGflnQcu2DRh4WUlgAhoXdvoxUc7KBhB8YxawXM,3088
108
+ code_puppy-0.0.161.dist-info/METADATA,sha256=0Fotiov1TnXl1fljBt_unzio7R4_1VsOwe4Ekf5uKsY,19567
109
+ code_puppy-0.0.161.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
110
+ code_puppy-0.0.161.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
111
+ code_puppy-0.0.161.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
112
+ code_puppy-0.0.161.dist-info/RECORD,,