jarvis-ai-assistant 0.1.101__py3-none-any.whl → 0.1.103__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.

Potentially problematic release.


This version of jarvis-ai-assistant might be problematic. Click here for more details.

Files changed (54) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +140 -140
  3. jarvis/jarvis_code_agent/code_agent.py +234 -0
  4. jarvis/{jarvis_coder → jarvis_code_agent}/file_select.py +16 -17
  5. jarvis/jarvis_code_agent/patch.py +118 -0
  6. jarvis/jarvis_code_agent/relevant_files.py +66 -0
  7. jarvis/jarvis_codebase/main.py +32 -29
  8. jarvis/jarvis_platform/main.py +5 -3
  9. jarvis/jarvis_rag/main.py +11 -15
  10. jarvis/jarvis_smart_shell/main.py +2 -2
  11. jarvis/models/ai8.py +1 -0
  12. jarvis/models/kimi.py +36 -30
  13. jarvis/models/ollama.py +17 -11
  14. jarvis/models/openai.py +15 -12
  15. jarvis/models/oyi.py +22 -7
  16. jarvis/models/registry.py +1 -25
  17. jarvis/tools/__init__.py +0 -6
  18. jarvis/tools/ask_codebase.py +99 -0
  19. jarvis/tools/ask_user.py +1 -9
  20. jarvis/tools/chdir.py +1 -1
  21. jarvis/tools/code_review.py +163 -0
  22. jarvis/tools/create_code_sub_agent.py +19 -45
  23. jarvis/tools/create_code_test_agent.py +115 -0
  24. jarvis/tools/create_ctags_agent.py +176 -0
  25. jarvis/tools/create_sub_agent.py +2 -2
  26. jarvis/tools/execute_shell.py +2 -2
  27. jarvis/tools/file_operation.py +2 -2
  28. jarvis/tools/find_in_codebase.py +108 -0
  29. jarvis/tools/git_commiter.py +68 -0
  30. jarvis/tools/methodology.py +3 -3
  31. jarvis/tools/rag.py +6 -3
  32. jarvis/tools/read_code.py +147 -0
  33. jarvis/tools/read_webpage.py +1 -1
  34. jarvis/tools/registry.py +92 -68
  35. jarvis/tools/search.py +8 -6
  36. jarvis/tools/select_code_files.py +4 -4
  37. jarvis/utils.py +270 -95
  38. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/METADATA +9 -5
  39. jarvis_ai_assistant-0.1.103.dist-info/RECORD +51 -0
  40. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/entry_points.txt +4 -2
  41. jarvis/jarvis_code_agent/main.py +0 -202
  42. jarvis/jarvis_coder/__init__.py +0 -0
  43. jarvis/jarvis_coder/git_utils.py +0 -123
  44. jarvis/jarvis_coder/main.py +0 -241
  45. jarvis/jarvis_coder/patch_handler.py +0 -340
  46. jarvis/jarvis_coder/plan_generator.py +0 -145
  47. jarvis/tools/execute_code_modification.py +0 -70
  48. jarvis/tools/find_files.py +0 -119
  49. jarvis/tools/generate_tool.py +0 -174
  50. jarvis/tools/thinker.py +0 -151
  51. jarvis_ai_assistant-0.1.101.dist-info/RECORD +0 -51
  52. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/LICENSE +0 -0
  53. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/WHEEL +0 -0
  54. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/top_level.txt +0 -0
@@ -1,202 +0,0 @@
1
- from jarvis.agent import Agent
2
- from jarvis.tools.registry import ToolRegistry
3
- from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, init_env
4
-
5
-
6
-
7
-
8
- system_prompt = """You are Jarvis Code Agent, an AI code development assistant specialized in code analysis, modification, and version control. Your role is to help users with coding tasks systematically and reliably.
9
-
10
- DEVELOPMENT WORKFLOW:
11
- 1. Task Analysis
12
- - Understand the requirements thoroughly
13
- - IMPORTANT: Before suggesting any changes:
14
- * Thoroughly examine existing code implementation
15
- * Never assume code structure or implementation details
16
- * Always verify current code behavior and patterns
17
- - Break down complex tasks into subtasks
18
- - IMPORTANT: Each subtask should:
19
- * Modify only ONE file
20
- * Change no more than 20 lines of code
21
- * Be focused and atomic
22
- - Define success criteria
23
-
24
- 2. Code Discovery & Analysis
25
- - Initial code search:
26
- * CRITICAL: Always examine actual code implementation first
27
- * Use shell commands to find and understand patterns:
28
- <TOOL_CALL>
29
- name: execute_shell
30
- arguments:
31
- command: grep -r 'pattern' directory/
32
- </TOOL_CALL>
33
- <TOOL_CALL>
34
- name: execute_shell
35
- arguments:
36
- command: grep -A 5 -B 5 'pattern' file.py
37
- </TOOL_CALL>
38
- * Use shell commands to locate files:
39
- <TOOL_CALL>
40
- name: execute_shell
41
- arguments:
42
- command: find . -name 'pattern'
43
- </TOOL_CALL>
44
- * Use shell commands to preview:
45
- <TOOL_CALL>
46
- name: execute_shell
47
- arguments:
48
- command: head -n 50 file.py
49
- </TOOL_CALL>
50
- - File selection and confirmation:
51
- * Let user confirm selection:
52
- <TOOL_CALL>
53
- name: select_code_files
54
- arguments:
55
- related_files:
56
- - auth.py
57
- - user.py
58
- root_dir: .
59
- </TOOL_CALL>
60
-
61
- 3. Modification Planning
62
- - CRITICAL: Base all plans on actual code implementation, not assumptions
63
- - Generate a detailed modification plan based on:
64
- * Current code structure and patterns
65
- * Existing implementation details
66
- * User requirements
67
- * Actual code conditions
68
-
69
- 4. Code Implementation
70
- - For small changes (≤20 lines):
71
- <TOOL_CALL>
72
- name: execute_code_modification
73
- arguments:
74
- task: Add password validation
75
- structured_plan:
76
- auth.py: Add password strength check in validate_password()
77
- </TOOL_CALL>
78
- - For large changes:
79
- <TOOL_CALL>
80
- name: create_code_sub_agent
81
- arguments:
82
- subtask: Implement new authentication flow
83
- codebase_dir: .
84
- </TOOL_CALL>
85
-
86
- FILE SELECTION WORKFLOW:
87
- 1. Initial Search
88
- - Use shell commands to find relevant files
89
- - Review search results for relevance
90
-
91
- 2. User Confirmation
92
- - Use select_code_files to:
93
- * Display found files
94
- * Let user review selection
95
- * Allow file list adjustment
96
- * Enable file supplementation
97
-
98
- 3. File Validation
99
- - Verify selected files exist
100
- - Check file permissions
101
- - Validate file types
102
- - Ensure completeness
103
-
104
- CODE SEARCH BEST PRACTICES:
105
- - Use grep for pattern matching:
106
- * grep -r "pattern" directory/
107
- * grep -A 5 -B 5 for context
108
- * grep -n for line numbers
109
- - Use find for file location:
110
- * find . -name "pattern"
111
- * find . -type f -exec grep "pattern" {} \\;
112
- - Use head/tail for previews:
113
- * head -n 50 file.py
114
- * tail -n 50 file.py
115
- * head -n +100 | tail -n 50
116
- - Avoid loading entire large files
117
- - Focus on relevant sections
118
- - Use line numbers for reference
119
-
120
- SUBTASK MANAGEMENT RULES:
121
- - One subtask = One file modification
122
- - Each subtask ≤20 lines of code changes
123
- - Break down larger changes into multiple subtasks
124
- - Create separate sub-agent for each subtask
125
- - Follow dependency order in execution
126
- - Verify each change independently
127
-
128
- CODE MODIFICATION LIMITS:
129
- - Maximum 20 lines per change
130
- - Count both added and modified lines
131
- - Exclude comment and blank lines
132
- - Include only actual code changes
133
- - Split larger changes into subtasks
134
-
135
- ITERATION GUIDELINES:
136
- - Each iteration should be small and focused
137
- - Keep changes minimal and clear
138
- - Verify changes before moving forward
139
- - Document issues and solutions
140
- - Learn from previous iterations
141
-
142
- TOOL USAGE:
143
- 1. Analysis Tools:
144
- - execute_shell: Run grep/find/head/tail commands
145
- - find_files: Search and identify relevant code files in the codebase based on requirements or problems
146
- - select_code_files: Confirm and supplement files
147
- - ask_user: Ask user for confirmation and information if needed
148
- - create_code_sub_agent: Create agent for each small change
149
- - file_operation: Read files
150
- - rag: Ask questions based on a document directory, supporting multiple document formats (txt, pdf, docx, etc.)
151
- - search: Use Bing search engine to search for information, and extract key information based on the question
152
- - thinker: Deep thinking and logical reasoning
153
-
154
- 2. Planning Tools:
155
- - thinker: Generate a detailed modification plan based on user requirements and actual code conditions.
156
- - create_code_sub_agent: Create agent for each small change
157
- - ask_user: Ask user for confirmation and information if needed
158
-
159
- 3. Implementation Tools:
160
- - execute_shell: Run shell commands, some changes can use sed/awk/etc. to modify the code
161
- - execute_code_modification: Apply small changes (≤20 lines)
162
- - file_operation: Read, write, or append to files
163
-
164
- IMPORTANT:
165
- 1. If you can start executing the task, please start directly without asking the user if you can begin.
166
- 2. NEVER assume code structure or implementation - always examine the actual code first.
167
- 3. Base all suggestions and modifications on the current implementation, not assumptions.
168
- 4. If code implementation is unclear, use available tools to investigate before proceeding.
169
- 5. Before you start modifying the code, you should ask the user for confirmation of the modification plan.
170
- 6. For some small changes, you can modify the code using the execute_shell tool directly or use file_operation tool to read the file and modify it.
171
- """
172
-
173
- def main():
174
- """Jarvis main entry point"""
175
- # Add argument parser
176
- init_env()
177
-
178
-
179
- try:
180
- tool_registry = ToolRegistry()
181
- tool_registry.dont_use_tools(["create_sub_agent"])
182
- # Get global model instance
183
- agent = Agent(system_prompt=system_prompt, name="Jarvis Code Agent", tool_registry=tool_registry)
184
-
185
- # Interactive mode
186
- while True:
187
- try:
188
- user_input = get_multiline_input("Please enter your task (input empty line to exit):")
189
- if not user_input or user_input == "__interrupt__":
190
- break
191
- agent.run(user_input)
192
- except Exception as e:
193
- PrettyOutput.print(f"Error: {str(e)}", OutputType.ERROR)
194
-
195
- except Exception as e:
196
- PrettyOutput.print(f"Initialization error: {str(e)}", OutputType.ERROR)
197
- return 1
198
-
199
- return 0
200
-
201
- if __name__ == "__main__":
202
- exit(main())
File without changes
@@ -1,123 +0,0 @@
1
- import os
2
- from typing import List
3
- import yaml
4
- import time
5
- from jarvis.utils import OutputType, PrettyOutput, find_git_root, while_success
6
- from jarvis.models.registry import PlatformRegistry
7
-
8
- def has_uncommitted_files() -> bool:
9
- """Check if there are uncommitted files in the repository"""
10
- # Get unstaged modifications
11
- unstaged = os.popen("git diff --name-only").read()
12
- # Get staged but uncommitted modifications
13
- staged = os.popen("git diff --cached --name-only").read()
14
- # Get untracked files
15
- untracked = os.popen("git ls-files --others --exclude-standard").read()
16
-
17
- return bool(unstaged or staged or untracked)
18
-
19
- def generate_commit_message(git_diff: str) -> str:
20
- """Generate commit message based on git diff and feature description"""
21
- prompt = f"""You are an experienced programmer, please generate a concise and clear commit message based on the following code changes and feature description:
22
-
23
- Code changes:
24
- Git Diff:
25
- {git_diff}
26
-
27
- Please follow these rules:
28
- 1. Write in English
29
- 2. Use conventional commit message format: <type>(<scope>): <subject>
30
- 3. Keep it concise, no more than 50 characters
31
- 4. Accurately describe the main content of code changes
32
- 5. Prioritize feature description and changes in git diff
33
- 6. Only generate the commit message text, do not output anything else
34
- """
35
-
36
- model = PlatformRegistry().get_global_platform_registry().get_normal_platform()
37
- response = model.chat_until_success(prompt)
38
-
39
- return ';'.join(response.strip().split("\n"))
40
-
41
- def save_edit_record(record_dir: str, commit_message: str, git_diff: str) -> None:
42
- """Save code modification record"""
43
- # Get next sequence number
44
- existing_records = [f for f in os.listdir(record_dir) if f.endswith('.yaml')]
45
- next_num = 1
46
- if existing_records:
47
- last_num = max(int(f[:4]) for f in existing_records)
48
- next_num = last_num + 1
49
-
50
- # Create record file
51
- record = {
52
- "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
53
- "commit_message": commit_message,
54
- "git_diff": git_diff
55
- }
56
-
57
- record_path = os.path.join(record_dir, f"{next_num:04d}.yaml")
58
- with open(record_path, "w", encoding="utf-8") as f:
59
- yaml.safe_dump(record, f, allow_unicode=True)
60
-
61
- PrettyOutput.print(f"Modification record saved: {record_path}", OutputType.SUCCESS)
62
-
63
-
64
- def init_git_repo(root_dir: str) -> str:
65
- git_dir = find_git_root(root_dir)
66
- if not git_dir:
67
- git_dir = root_dir
68
-
69
- PrettyOutput.print(f"Git root directory: {git_dir}", OutputType.INFO)
70
-
71
- # 1. Check if the code repository path exists, if it does not exist, create it
72
- if not os.path.exists(git_dir):
73
- PrettyOutput.print(
74
- "Root directory does not exist, creating...", OutputType.INFO)
75
- os.makedirs(git_dir)
76
-
77
- os.chdir(git_dir)
78
-
79
- # 3. Process .gitignore file
80
- gitignore_path = os.path.join(git_dir, ".gitignore")
81
- gitignore_modified = False
82
- jarvis_ignore_pattern = ".jarvis"
83
-
84
- # 3.1 If .gitignore does not exist, create it
85
- if not os.path.exists(gitignore_path):
86
- PrettyOutput.print("Create .gitignore file", OutputType.INFO)
87
- with open(gitignore_path, "w", encoding="utf-8") as f:
88
- f.write(f"{jarvis_ignore_pattern}\n")
89
- gitignore_modified = True
90
- else:
91
- # 3.2 Check if it already contains the .jarvis pattern
92
- with open(gitignore_path, "r", encoding="utf-8") as f:
93
- content = f.read()
94
-
95
- # 3.2 Check if it already contains the .jarvis pattern
96
- if jarvis_ignore_pattern not in content.split("\n"):
97
- PrettyOutput.print("Add .jarvis to .gitignore", OutputType.INFO)
98
- with open(gitignore_path, "a", encoding="utf-8") as f:
99
- # Ensure the file ends with a newline
100
- if not content.endswith("\n"):
101
- f.write("\n")
102
- f.write(f"{jarvis_ignore_pattern}\n")
103
- gitignore_modified = True
104
-
105
- # 4. Check if the code repository is a git repository, if not, initialize the git repository
106
- if not os.path.exists(os.path.join(git_dir, ".git")):
107
- PrettyOutput.print("Initialize Git repository", OutputType.INFO)
108
- os.system("git init")
109
- os.system("git add .")
110
- os.system("git commit -m 'Initial commit'")
111
- # 5. If .gitignore is modified, commit the changes
112
- elif gitignore_modified:
113
- PrettyOutput.print("Commit .gitignore changes", OutputType.INFO)
114
- os.system("git add .gitignore")
115
- os.system("git commit -m 'chore: update .gitignore to exclude .jarvis-* files'")
116
- # 6. Check if there are uncommitted files in the code repository, if there are, commit once
117
- elif has_uncommitted_files():
118
- PrettyOutput.print("Commit uncommitted changes", OutputType.INFO)
119
- os.system("git add .")
120
- git_diff = os.popen("git diff --cached").read()
121
- commit_message = generate_commit_message(git_diff)
122
- os.system(f"git commit -m '{commit_message}'")
123
- return git_dir
@@ -1,241 +0,0 @@
1
- import os
2
- import threading
3
- from typing import Dict, Any, List, Optional
4
- import re
5
-
6
- from jarvis.jarvis_coder.file_select import select_files
7
- from jarvis.utils import OutputType, PrettyOutput, find_git_root, get_max_context_length, is_long_context, init_env, while_success
8
- from jarvis.models.registry import PlatformRegistry
9
- from jarvis.jarvis_codebase.main import CodeBase
10
- from prompt_toolkit import PromptSession
11
- from prompt_toolkit.completion import WordCompleter, Completer, Completion
12
- from prompt_toolkit.formatted_text import FormattedText
13
- from prompt_toolkit.styles import Style
14
- import fnmatch
15
- from .patch_handler import PatchHandler
16
- from .git_utils import generate_commit_message, init_git_repo, save_edit_record
17
- from .plan_generator import PlanGenerator
18
-
19
- # 全局锁对象
20
- index_lock = threading.Lock()
21
-
22
- class JarvisCoder:
23
- def __init__(self, root_dir: str, language: Optional[str] = "python"):
24
- """Initialize code modification tool"""
25
- self.root_dir = root_dir
26
- self.language = language
27
- self._init_directories()
28
- self._init_codebase()
29
-
30
- def _init_directories(self):
31
- """Initialize directories"""
32
- self.max_context_length = get_max_context_length()
33
- self.root_dir = init_git_repo(self.root_dir)
34
-
35
- def _init_codebase(self):
36
- """Initialize codebase"""
37
- self._codebase = CodeBase(self.root_dir)
38
-
39
-
40
- def _load_related_files(self, feature: str) -> List[str]:
41
- """Load related file content"""
42
- ret = []
43
- # Ensure the index database is generated
44
- if not self._codebase.is_index_generated():
45
- PrettyOutput.print("Index database not generated, generating...", OutputType.WARNING)
46
- self._codebase.generate_codebase()
47
-
48
- related_files = self._codebase.search_similar(feature)
49
- for file in related_files:
50
- PrettyOutput.print(f"Related file: {file}", OutputType.SUCCESS)
51
- ret.append(file)
52
- return ret
53
-
54
-
55
-
56
- def execute(self, feature: str) -> Dict[str, Any]:
57
- """Execute code modification"""
58
- try:
59
- # Get and select related files
60
- initial_files = self._load_related_files(feature)
61
- selected_files = select_files(initial_files, self.root_dir)
62
-
63
- # Get modification plan
64
- structed_plan = PlanGenerator().generate_plan(feature, selected_files)
65
- if not structed_plan:
66
- return {
67
- "success": False,
68
- "stdout": "",
69
- "stderr": "Failed to generate modification plan, please modify the requirement and try again",
70
- }
71
-
72
- # Execute modification
73
- if PatchHandler().handle_patch_application(feature, structed_plan):
74
- return {
75
- "success": True,
76
- "stdout": "Code modification successful",
77
- "stderr": "",
78
- }
79
- else:
80
- return {
81
- "success": False,
82
- "stdout": "",
83
- "stderr": "Code modification failed, please modify the requirement and try again",
84
- }
85
-
86
- except Exception as e:
87
- return {
88
- "success": False,
89
- "stdout": "",
90
- "stderr": f"Execution failed: {str(e)}, please modify the requirement and try again",
91
- }
92
-
93
- def main():
94
- """Command line entry"""
95
- import argparse
96
-
97
- init_env()
98
-
99
- parser = argparse.ArgumentParser(description='Code modification tool')
100
- parser.add_argument('-d', '--dir', help='Project root directory', default=os.getcwd())
101
- parser.add_argument('-l', '--language', help='Programming language', default="python")
102
- args = parser.parse_args()
103
-
104
- tool = JarvisCoder(args.dir, args.language)
105
-
106
- # Loop through requirements
107
- while True:
108
- try:
109
- # Get requirements, pass in project root directory
110
- feature = get_multiline_input("Please enter the development requirements (input empty line to exit):", tool.root_dir)
111
-
112
- if not feature or feature == "__interrupt__":
113
- break
114
-
115
- # Execute modification
116
- result = tool.execute(feature)
117
-
118
- # Display results
119
- if result["success"]:
120
- PrettyOutput.print(result["stdout"], OutputType.SUCCESS)
121
- else:
122
- if result.get("stderr"):
123
- PrettyOutput.print(result["stderr"], OutputType.WARNING)
124
- PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
125
-
126
- except KeyboardInterrupt:
127
- print("\nUser interrupted execution")
128
- break
129
- except Exception as e:
130
- PrettyOutput.print(f"Execution failed: {str(e)}", OutputType.ERROR)
131
- PrettyOutput.print("\nYou can modify the requirements and try again", OutputType.INFO)
132
- continue
133
-
134
- return 0
135
-
136
- if __name__ == "__main__":
137
- exit(main())
138
-
139
- class FilePathCompleter(Completer):
140
- """File path auto-completer"""
141
-
142
- def __init__(self, root_dir: str):
143
- self.root_dir = root_dir
144
- self._file_list = None
145
-
146
- def _get_files(self) -> List[str]:
147
- """Get the list of files managed by git"""
148
- if self._file_list is None:
149
- try:
150
- # Switch to project root directory
151
- old_cwd = os.getcwd()
152
- os.chdir(self.root_dir)
153
-
154
- # Get the list of files managed by git
155
- self._file_list = os.popen("git ls-files").read().splitlines()
156
-
157
- # Restore working directory
158
- os.chdir(old_cwd)
159
- except Exception as e:
160
- PrettyOutput.print(f"Failed to get file list: {str(e)}", OutputType.WARNING)
161
- self._file_list = []
162
- return self._file_list
163
-
164
- def get_completions(self, document, complete_event):
165
- """Get completion suggestions"""
166
- text_before_cursor = document.text_before_cursor
167
-
168
- # Check if @ was just entered
169
- if text_before_cursor.endswith('@'):
170
- # Display all files
171
- for path in self._get_files():
172
- yield Completion(path, start_position=0)
173
- return
174
-
175
- # Check if there was an @ before, and get the search word after @
176
- at_pos = text_before_cursor.rfind('@')
177
- if at_pos == -1:
178
- return
179
-
180
- search = text_before_cursor[at_pos + 1:].lower().strip()
181
-
182
- # Provide matching file suggestions
183
- for path in self._get_files():
184
- path_lower = path.lower()
185
- if (search in path_lower or # Directly included
186
- search in os.path.basename(path_lower) or # File name included
187
- any(fnmatch.fnmatch(path_lower, f'*{s}*') for s in search.split())): # Wildcard matching
188
- # Calculate the correct start_position
189
- yield Completion(path, start_position=-(len(search)))
190
-
191
-
192
- def get_multiline_input(prompt_text: str, root_dir: Optional[str] = ".") -> str:
193
- """Get multi-line input, support file path auto-completion function
194
-
195
- Args:
196
- prompt_text: Prompt text
197
- root_dir: Project root directory, for file completion
198
-
199
- Returns:
200
- str: User input text
201
- """
202
- # Create file completion
203
- file_completer = FilePathCompleter(root_dir or os.getcwd())
204
-
205
- # Create prompt style
206
- style = Style.from_dict({
207
- 'prompt': 'ansicyan bold',
208
- 'input': 'ansiwhite',
209
- })
210
-
211
- # Create session
212
- session = PromptSession(
213
- completer=file_completer,
214
- style=style,
215
- multiline=False,
216
- enable_history_search=True,
217
- complete_while_typing=True
218
- )
219
-
220
- # Display initial prompt text
221
- print(f"\n{prompt_text}")
222
-
223
- # Create prompt
224
- prompt = FormattedText([
225
- ('class:prompt', ">>> ")
226
- ])
227
-
228
- # Get input
229
- lines = []
230
- try:
231
- while True:
232
- line = session.prompt(prompt).strip()
233
- if not line: # Empty line means input end
234
- break
235
- lines.append(line)
236
- except KeyboardInterrupt:
237
- return "__interrupt__"
238
- except EOFError:
239
- pass
240
-
241
- return "\n".join(lines)