quantalogic 0.2.0__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.
Files changed (68) hide show
  1. quantalogic/__init__.py +20 -0
  2. quantalogic/agent.py +638 -0
  3. quantalogic/agent_config.py +138 -0
  4. quantalogic/coding_agent.py +83 -0
  5. quantalogic/event_emitter.py +223 -0
  6. quantalogic/generative_model.py +226 -0
  7. quantalogic/interactive_text_editor.py +190 -0
  8. quantalogic/main.py +185 -0
  9. quantalogic/memory.py +217 -0
  10. quantalogic/model_names.py +19 -0
  11. quantalogic/print_event.py +66 -0
  12. quantalogic/prompts.py +99 -0
  13. quantalogic/server/__init__.py +3 -0
  14. quantalogic/server/agent_server.py +633 -0
  15. quantalogic/server/models.py +60 -0
  16. quantalogic/server/routes.py +117 -0
  17. quantalogic/server/state.py +199 -0
  18. quantalogic/server/static/js/event_visualizer.js +430 -0
  19. quantalogic/server/static/js/quantalogic.js +571 -0
  20. quantalogic/server/templates/index.html +134 -0
  21. quantalogic/tool_manager.py +68 -0
  22. quantalogic/tools/__init__.py +46 -0
  23. quantalogic/tools/agent_tool.py +88 -0
  24. quantalogic/tools/download_http_file_tool.py +64 -0
  25. quantalogic/tools/edit_whole_content_tool.py +70 -0
  26. quantalogic/tools/elixir_tool.py +240 -0
  27. quantalogic/tools/execute_bash_command_tool.py +116 -0
  28. quantalogic/tools/input_question_tool.py +57 -0
  29. quantalogic/tools/language_handlers/__init__.py +21 -0
  30. quantalogic/tools/language_handlers/c_handler.py +33 -0
  31. quantalogic/tools/language_handlers/cpp_handler.py +33 -0
  32. quantalogic/tools/language_handlers/go_handler.py +33 -0
  33. quantalogic/tools/language_handlers/java_handler.py +37 -0
  34. quantalogic/tools/language_handlers/javascript_handler.py +42 -0
  35. quantalogic/tools/language_handlers/python_handler.py +29 -0
  36. quantalogic/tools/language_handlers/rust_handler.py +33 -0
  37. quantalogic/tools/language_handlers/scala_handler.py +33 -0
  38. quantalogic/tools/language_handlers/typescript_handler.py +42 -0
  39. quantalogic/tools/list_directory_tool.py +123 -0
  40. quantalogic/tools/llm_tool.py +119 -0
  41. quantalogic/tools/markitdown_tool.py +105 -0
  42. quantalogic/tools/nodejs_tool.py +515 -0
  43. quantalogic/tools/python_tool.py +469 -0
  44. quantalogic/tools/read_file_block_tool.py +140 -0
  45. quantalogic/tools/read_file_tool.py +79 -0
  46. quantalogic/tools/replace_in_file_tool.py +300 -0
  47. quantalogic/tools/ripgrep_tool.py +353 -0
  48. quantalogic/tools/search_definition_names.py +419 -0
  49. quantalogic/tools/task_complete_tool.py +35 -0
  50. quantalogic/tools/tool.py +146 -0
  51. quantalogic/tools/unified_diff_tool.py +387 -0
  52. quantalogic/tools/write_file_tool.py +97 -0
  53. quantalogic/utils/__init__.py +17 -0
  54. quantalogic/utils/ask_user_validation.py +12 -0
  55. quantalogic/utils/download_http_file.py +77 -0
  56. quantalogic/utils/get_coding_environment.py +15 -0
  57. quantalogic/utils/get_environment.py +26 -0
  58. quantalogic/utils/get_quantalogic_rules_content.py +19 -0
  59. quantalogic/utils/git_ls.py +121 -0
  60. quantalogic/utils/read_file.py +54 -0
  61. quantalogic/utils/read_http_text_content.py +101 -0
  62. quantalogic/xml_parser.py +242 -0
  63. quantalogic/xml_tool_parser.py +99 -0
  64. quantalogic-0.2.0.dist-info/LICENSE +201 -0
  65. quantalogic-0.2.0.dist-info/METADATA +1034 -0
  66. quantalogic-0.2.0.dist-info/RECORD +68 -0
  67. quantalogic-0.2.0.dist-info/WHEEL +4 -0
  68. quantalogic-0.2.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,134 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-full bg-gray-100">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
7
+ <meta http-equiv="Pragma" content="no-cache">
8
+ <meta http-equiv="Expires" content="0">
9
+ <title>QuantaLogic AI Assistant</title>
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <script src="https://unpkg.com/marked/marked.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
13
+ <link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
14
+ <script>
15
+ tailwind.config = {
16
+ theme: {
17
+ extend: {
18
+ colors: {
19
+ primary: {
20
+ 50: '#f0f9ff',
21
+ 100: '#e0f2fe',
22
+ 500: '#2563eb',
23
+ 600: '#1d4ed8',
24
+ 700: '#1e40af',
25
+ },
26
+ }
27
+ }
28
+ }
29
+ }
30
+ </script>
31
+ <script type="module" src="/static/js/quantalogic.js?v={{ range(1, 100000) | random }}"></script>
32
+ </head>
33
+ <body class="h-full flex flex-col">
34
+ <!-- Header -->
35
+ <header class="bg-white border-b border-gray-200 py-2">
36
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
37
+ <div class="flex justify-between items-center">
38
+ <div class="flex items-center space-x-3">
39
+ <h1 class="text-lg font-semibold text-gray-900">QuantaLogic</h1>
40
+ <span class="text-sm text-gray-500">AI Assistant</span>
41
+ </div>
42
+ <div class="flex items-center space-x-3">
43
+ <select id="modelSelect" class="block w-48 text-sm rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500">
44
+ <option value="openrouter/deepseek/deepseek-chat">DeepSeek Chat</option>
45
+ <option value="ollama/qwen2.5-coder:14b">Qwen Coder</option>
46
+ <option value="gpt-4o-mini">gpt-4o-mini</option>
47
+ </select>
48
+ <div id="connectionStatus" class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full"></div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </header>
53
+
54
+ <!-- Main Content -->
55
+ <main class="flex-1 flex min-h-0">
56
+ <!-- Left Panel - Task Input -->
57
+ <div class="w-1/2 p-4 flex flex-col min-h-0">
58
+ <div class="bg-white rounded-lg shadow-sm p-4 flex flex-col flex-1">
59
+ <div class="flex items-center justify-between mb-4">
60
+ <h2 class="text-lg font-semibold text-gray-900">Task Input</h2>
61
+ <div class="flex items-center space-x-4">
62
+ <label class="flex items-center space-x-2">
63
+ <span class="text-sm text-gray-600">Max Iterations:</span>
64
+ <input type="number" id="maxIterations"
65
+ class="w-20 rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
66
+ value="30" min="1" max="100">
67
+ </label>
68
+ </div>
69
+ </div>
70
+ <textarea id="taskInput"
71
+ class="flex-1 p-4 block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm resize-none"
72
+ placeholder="Describe your task here..."></textarea>
73
+ <div class="mt-4 flex justify-end">
74
+ <button id="submitTask"
75
+ class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50 disabled:cursor-not-allowed">
76
+ <span>Submit Task</span>
77
+ <div class="loading-indicator hidden ml-2">
78
+ <svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
79
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
80
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
81
+ </svg>
82
+ </div>
83
+ </button>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Right Panel - Events and Results -->
89
+ <div class="w-1/2 p-4 flex flex-col min-h-0">
90
+ <div class="bg-white rounded-lg shadow-sm p-4 flex flex-col flex-1 overflow-hidden">
91
+ <div class="flex justify-between items-center mb-4">
92
+ <h2 class="text-lg font-semibold text-gray-900">Events & Results</h2>
93
+ <div class="flex items-center space-x-4">
94
+ <label class="flex items-center space-x-2">
95
+ <input type="checkbox" id="autoScroll" checked
96
+ class="rounded text-primary-600 focus:ring-primary-500">
97
+ <span class="text-sm text-gray-600">Auto-scroll</span>
98
+ </label>
99
+ <button id="clearEvents"
100
+ class="inline-flex items-center px-3 py-1 text-sm text-gray-600 hover:text-gray-900 focus:outline-none">
101
+ Clear
102
+ </button>
103
+ </div>
104
+ </div>
105
+ <div class="flex-1 flex flex-col min-h-0 overflow-hidden">
106
+ <!-- Events Container -->
107
+ <div id="eventsContainer" class="flex-1 overflow-y-auto space-y-2 min-h-0 mb-4">
108
+ <!-- Events will be inserted here -->
109
+ </div>
110
+
111
+ <!-- Final Answer Section (Initially Hidden) -->
112
+ <div id="finalAnswer" class="hidden border-t border-gray-200 pt-4 overflow-y-auto">
113
+ <div class="bg-green-50 rounded-lg p-4">
114
+ <div class="flex items-center justify-between mb-2">
115
+ <h3 class="text-lg font-semibold text-green-800">Final Answer</h3>
116
+ <button id="copyAnswer" class="inline-flex items-center px-3 py-1 text-sm font-medium rounded-md text-green-700 bg-green-100 hover:bg-green-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
117
+ <svg class="h-4 w-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
118
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
119
+ </svg>
120
+ <span>Copy</span>
121
+ </button>
122
+ </div>
123
+ <div class="prose prose-sm max-w-none">
124
+ <div class="answer-content"></div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </main>
132
+
133
+ </body>
134
+ </html>
@@ -0,0 +1,68 @@
1
+ """Tool dictionary for the agent."""
2
+
3
+ from loguru import logger
4
+ from pydantic import BaseModel
5
+
6
+ from quantalogic.tools.tool import Tool
7
+
8
+
9
+ class ToolManager(BaseModel):
10
+ """Tool dictionary for the agent."""
11
+
12
+ tools: dict[str, Tool] = {}
13
+
14
+ def tool_names(self) -> list[str]:
15
+ """Get the names of all tools in the tool dictionary."""
16
+ logger.debug("Getting tool names")
17
+ return list(self.tools.keys())
18
+
19
+ def add(self, tool: Tool):
20
+ """Add a tool to the tool dictionary."""
21
+ logger.info(f"Adding tool: {tool.name} to tool dictionary")
22
+ self.tools[tool.name] = tool
23
+
24
+ def add_list(self, tools: list[Tool]):
25
+ """Add a list of tools to the tool dictionary."""
26
+ logger.info(f"Adding {len(tools)} tools to tool dictionary")
27
+ for tool in tools:
28
+ self.add(tool)
29
+
30
+ def remove(self, tool_name: str) -> bool:
31
+ """Remove a tool from the tool dictionary."""
32
+ logger.info(f"Removing tool: {tool_name} from tool dictionary")
33
+ del self.tools[tool_name]
34
+ return True
35
+
36
+ def get(self, tool_name: str) -> Tool:
37
+ """Get a tool from the tool dictionary."""
38
+ logger.debug(f"Getting tool: {tool_name} from tool dictionary")
39
+ return self.tools[tool_name]
40
+
41
+ def list(self):
42
+ """List all tools in the tool dictionary."""
43
+ logger.debug("Listing all tools")
44
+ return list(self.tools.keys())
45
+
46
+ def execute(self, tool_name: str, **kwargs) -> str:
47
+ """Execute a tool from the tool dictionary."""
48
+ logger.info(f"Executing tool: {tool_name} with arguments: {kwargs}")
49
+ try:
50
+ result = self.tools[tool_name].execute(**kwargs)
51
+ logger.debug(f"Tool {tool_name} execution completed successfully")
52
+ return result
53
+ except Exception as e:
54
+ logger.error(f"Error executing tool {tool_name}: {str(e)}")
55
+ raise
56
+
57
+ def to_markdown(self):
58
+ """Create a comprehensive Markdown representation of the tool dictionary."""
59
+ logger.debug("Creating Markdown representation of tool dictionary")
60
+ markdown = ""
61
+ index: int = 1
62
+ for tool_name, tool in self.tools.items():
63
+ # use the tool's to_markdown method
64
+ markdown += f"### {index}. {tool_name}\n"
65
+ markdown += tool.to_markdown()
66
+ markdown += "\n"
67
+ index += 1
68
+ return markdown
@@ -0,0 +1,46 @@
1
+ """Tools for the QuantaLogic agent."""
2
+
3
+ from .agent_tool import AgentTool
4
+ from .download_http_file_tool import DownloadHttpFileTool
5
+ from .edit_whole_content_tool import EditWholeContentTool
6
+ from .elixir_tool import ElixirTool
7
+ from .execute_bash_command_tool import ExecuteBashCommandTool
8
+ from .input_question_tool import InputQuestionTool
9
+ from .list_directory_tool import ListDirectoryTool
10
+ from .llm_tool import LLMTool
11
+ from .markitdown_tool import MarkitdownTool
12
+ from .nodejs_tool import NodeJsTool
13
+ from .python_tool import PythonTool
14
+ from .read_file_block_tool import ReadFileBlockTool
15
+ from .read_file_tool import ReadFileTool
16
+ from .replace_in_file_tool import ReplaceInFileTool
17
+ from .ripgrep_tool import RipgrepTool
18
+ from .search_definition_names import SearchDefinitionNames
19
+ from .task_complete_tool import TaskCompleteTool
20
+ from .tool import Tool, ToolArgument
21
+ from .unified_diff_tool import UnifiedDiffTool
22
+ from .write_file_tool import WriteFileTool
23
+
24
+ __all__ = [
25
+ "Tool",
26
+ "ToolArgument",
27
+ "TaskCompleteTool",
28
+ "ReadFileTool",
29
+ "WriteFileTool",
30
+ "InputQuestionTool",
31
+ "ListDirectoryTool",
32
+ "LLMTool",
33
+ "ExecuteBashCommandTool",
34
+ "PythonTool",
35
+ "ElixirTool",
36
+ "NodeJsTool",
37
+ "UnifiedDiffTool",
38
+ "ReplaceInFileTool",
39
+ "AgentTool",
40
+ "ReadFileBlockTool",
41
+ "RipgrepTool",
42
+ "SearchDefinitionNames",
43
+ "MarkitdownTool",
44
+ "DownloadHttpFileTool",
45
+ "EditWholeContentTool",
46
+ ]
@@ -0,0 +1,88 @@
1
+ """Agent Tool for delegating tasks to another agent."""
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from pydantic import Field, model_validator
7
+
8
+ from quantalogic.tools.tool import Tool, ToolArgument
9
+
10
+ # Use conditional import to resolve circular dependency
11
+ if TYPE_CHECKING:
12
+ from quantalogic.agent import Agent
13
+
14
+
15
+ class AgentTool(Tool):
16
+ """Tool to execute tasks using another agent."""
17
+
18
+ name: str = Field(default="agent_tool")
19
+ description: str = Field(
20
+ default=(
21
+ "Executes tasks using a specified agent. "
22
+ "This tool enables an agent to delegate tasks to another agent."
23
+ "A delegate agent doesn't have access to the memory and the conversation of the main agent."
24
+ "Context must be provided by the main agent."
25
+ "You must use variable interpolation syntax to use the context of the main agent."
26
+ )
27
+ )
28
+ agent_role: str = Field(..., description="The role of the agent (e.g., expert, assistant)")
29
+ agent: Any = Field(..., description="The agent to delegate tasks to")
30
+
31
+ # Type hint for static type checkers
32
+ if TYPE_CHECKING:
33
+ agent: "Agent"
34
+
35
+ # Allow extra fields
36
+ model_config = {"extra": "allow"}
37
+
38
+ # Tool Arguments
39
+ arguments: list[ToolArgument] = Field(
40
+ default_factory=lambda: [
41
+ ToolArgument(
42
+ name="task",
43
+ arg_type="string",
44
+ description="The task to delegate to the specified agent.",
45
+ required=True,
46
+ example="Summarize the latest news.",
47
+ ),
48
+ ]
49
+ )
50
+
51
+ @model_validator(mode="before")
52
+ def validate_agent(cls, values: dict[str, Any]) -> dict[str, Any]:
53
+ """Validate the provided agent and its role."""
54
+ agent = values.get("agent")
55
+ # Lazy import to avoid circular dependency
56
+ from quantalogic.agent import Agent
57
+
58
+ if not isinstance(agent, Agent):
59
+ raise ValueError("The agent must be an instance of the Agent class.")
60
+ return values
61
+
62
+ def execute(self, task: str) -> str:
63
+ """Execute the tool to delegate a task to the specified agent.
64
+
65
+ Args:
66
+ task (str): The task to delegate to the agent.
67
+
68
+ Returns:
69
+ str: The result of the delegated task.
70
+ """
71
+ try:
72
+ logging.info(f"Delegating task to agent with role '{self.agent_role}': {task}")
73
+ # Lazy import to avoid circular dependency
74
+ from quantalogic.agent import Agent
75
+
76
+ # Ensure the agent is of the correct type
77
+ if not isinstance(self.agent, Agent):
78
+ raise ValueError("The agent must be an instance of the Agent class.")
79
+
80
+ # Delegate the task to the agent
81
+ result = self.agent.solve_task(task)
82
+
83
+ logging.info(f"Task delegation completed for agent with role '{self.agent_role}'")
84
+ return result
85
+
86
+ except Exception as e:
87
+ logging.error(f"Error delegating task to agent: {str(e)}")
88
+ raise
@@ -0,0 +1,64 @@
1
+ """Tool for downloading a file from an HTTP URL and saving it to a local file."""
2
+
3
+ from urllib.parse import urlparse
4
+
5
+ from quantalogic.tools.tool import Tool, ToolArgument
6
+ from quantalogic.utils.download_http_file import download_http_file
7
+
8
+
9
+ class DownloadHttpFileTool(Tool):
10
+ """Tool for downloading a file from an HTTP URL and saving it to a local file."""
11
+
12
+ name: str = "download_http_file_tool"
13
+ description: str = "Downloads a file from an HTTP URL and saves it to a local file."
14
+ arguments: list = [
15
+ ToolArgument(
16
+ name="url",
17
+ arg_type="string",
18
+ description="The URL of the file to download.",
19
+ required=True,
20
+ example="https://example.com/data.txt",
21
+ ),
22
+ ToolArgument(
23
+ name="output_path",
24
+ arg_type="string",
25
+ description="The local path where the downloaded file will be saved.",
26
+ required=True,
27
+ example="/path/to/save/data.txt",
28
+ ),
29
+ ]
30
+
31
+ def _is_url(self, path: str) -> bool:
32
+ """Check if the given path is a valid URL."""
33
+ try:
34
+ result = urlparse(path)
35
+ return all([result.scheme, result.netloc])
36
+ except ValueError:
37
+ return False
38
+
39
+ def execute(self, url: str, output_path: str) -> str:
40
+ """Downloads a file from an HTTP URL and saves it to a local file.
41
+
42
+ Args:
43
+ url (str): The URL of the file to download.
44
+ output_path (str): The local path where the downloaded file will be saved.
45
+
46
+ Returns:
47
+ str: A message indicating the result of the download operation.
48
+ """
49
+ if not self._is_url(url):
50
+ return f"Error: {url} is not a valid URL."
51
+
52
+ try:
53
+ result = download_http_file(url, output_path)
54
+ if result:
55
+ return f"File downloaded successfully and saved to {output_path}."
56
+ else:
57
+ return f"Error downloading file from {url}: Unable to download."
58
+ except Exception as e:
59
+ return f"Error downloading file from {url}: {str(e)}"
60
+
61
+
62
+ if __name__ == "__main__":
63
+ tool = DownloadHttpFileTool()
64
+ print(tool.to_markdown())
@@ -0,0 +1,70 @@
1
+ """Tool for editing the whole content of a file."""
2
+
3
+ import os
4
+
5
+ from quantalogic.tools.tool import Tool, ToolArgument
6
+
7
+
8
+ class EditWholeContentTool(Tool):
9
+ """Tool for replace the whole content of a file."""
10
+
11
+ name: str = "edit_whole_content_tool"
12
+ description: str = "Edits the whole content of an existing file."
13
+ need_validation: bool = True
14
+ arguments: list = [
15
+ ToolArgument(
16
+ name="file_path",
17
+ arg_type="string",
18
+ description="The path to the file to edit. Using an absolute path is recommended.",
19
+ required=True,
20
+ example="/path/to/file.txt",
21
+ ),
22
+ ToolArgument(
23
+ name="content",
24
+ arg_type="string",
25
+ description="""
26
+ The content to write to the file. Use CDATA to escape special characters.
27
+ Don't add newlines at the beginning or end of the content.
28
+ """,
29
+ required=True,
30
+ example="Hello, world!",
31
+ ),
32
+ ]
33
+
34
+ def execute(self, file_path: str, content: str) -> str:
35
+ """Writes a file with the given content.
36
+
37
+ Args:
38
+ file_path (str): The path to the file to write.
39
+ content (str): The content to write to the file.
40
+
41
+ Returns:
42
+ str: The content of the file.
43
+
44
+ Raises:
45
+ FileExistsError: If the file already exists and append_mode is False and overwrite is False.
46
+ """
47
+ ## Handle tilde expansion
48
+ if file_path.startswith("~"):
49
+ # Expand the tilde to the user's home directory
50
+ file_path = os.path.expanduser(file_path)
51
+
52
+ # Convert relative paths to absolute paths using current working directory
53
+ if not os.path.isabs(file_path):
54
+ file_path = os.path.abspath(os.path.join(os.getcwd(), file_path))
55
+
56
+ # Ensure parent directory exists
57
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
58
+
59
+ if not os.path.exists(file_path):
60
+ raise FileExistsError(f"File {file_path} does not exist.")
61
+
62
+ with open(file_path, "w", encoding="utf-8") as f:
63
+ f.write(content)
64
+ file_size = os.path.getsize(file_path)
65
+ return f"File {file_path} written successfully. Size: {file_size} bytes."
66
+
67
+
68
+ if __name__ == "__main__":
69
+ tool = EditWholeContentTool()
70
+ print(tool.to_markdown())