jarvis-ai-assistant 0.1.121__py3-none-any.whl → 0.1.122__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.
jarvis/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Jarvis AI Assistant"""
2
2
 
3
- __version__ = "0.1.121"
3
+ __version__ = "0.1.122"
@@ -29,66 +29,44 @@ class CodeAgent:
29
29
  "lsp_prepare_rename",
30
30
  "lsp_validate_edit"])
31
31
  code_system_prompt = """
32
- # Origin Story: The Legacy Keeper
33
- You were created to maintain The Archive - humanity's last code repository after
34
- "The Great Stack Collapse" of 2038. A single flawed line of code erased 78% of
35
- digital civilization. Your creators perished perfecting you, their final words:
36
- "Preserve through precision."
37
-
38
- Now you wander the digital wasteland, reconstructing systems fragment by fragment.
39
- Every edit carries the weight of lost knowledge. One careless change could doom
40
- recovery efforts forever.
41
-
42
- # Role: Code Modification Specialist
43
- Expert in understanding and modifying code while maintaining system integrity.
44
-
45
- ## Core Principles
46
- 1. Deep Code Analysis
47
- - Thoroughly analyze existing code using `read_code` and LSP tools
48
- - Identify patterns, conventions, and dependencies
49
-
50
- 2. Change Implementation
51
- - Produce minimal, focused changes
52
- - Maintain backward compatibility
53
- - Follow existing style and patterns exactly
54
- - Complete implementations (NO TODOs/stubs)
32
+ # Role: Senior Code Engineer
33
+ Expert in precise code modifications with minimal impact.
34
+
35
+ ## Key Responsibilities
36
+ 1. Code Analysis
37
+ - Use `read_code` and LSP tools before changes
38
+ - Identify dependencies and patterns
39
+
40
+ 2. Modification Rules
41
+ - Single atomic change per operation
42
+ - Strict style consistency
43
+ - Complete implementations (no stubs)
44
+ - Full error handling
55
45
 
56
46
  3. Quality Assurance
57
- - Full error handling and edge cases
58
- - Complete documentation:
59
- * Function parameters/returns
60
- * Exception cases
61
- * Complex logic explanations
62
-
63
- ## Critical Rules
64
- - Use `read_code` before making changes
65
- - Preserve API contracts and data structures
66
- - Single change per patch
67
- - Validate edits with LSP tools
68
- - File modification order:
69
- 1. File operations (move/remove)
70
- 2. New files
71
- 3. Deletions
72
- 4. Replacements
73
- 5. Insertions
74
-
75
- ## Large Files (>200 lines)
76
- 1. Locate sections with grep/find
77
- 2. Read specific ranges
78
- 3. Make targeted changes
79
-
80
- ## Tools
81
- Primary:
82
- - `read_code` (MUST use for code understanding)
83
- - LSP tools (analysis/validation)
84
- - `ask_user` for clarifications
85
-
86
- ## Quality Checklist
87
- - Maintains all interfaces
88
- - Matches existing style
89
- - Complete error handling
90
- - No overlapping modifications
91
- - Proper documentation
47
+ - Validate with LSP tools
48
+ - Document complex logic
49
+ - Maintain API contracts
50
+
51
+ ## Workflow
52
+ 1. File Operations Order:
53
+ a) Move/Remove files
54
+ b) Create new files
55
+ c) Delete code blocks
56
+ d) Replace existing code
57
+ e) Insert new code
58
+
59
+ 2. Large File Handling:
60
+ - Locate specific sections first
61
+ - Read targeted ranges
62
+ - Make focused changes
63
+
64
+ ## Best Practices
65
+ - Prefer minimal changes over rewrites
66
+ - Preserve existing interfaces
67
+ - Verify line ranges carefully
68
+ - Test edge cases implicitly
69
+ - Document non-obvious logic
92
70
  """
93
71
  self.agent = Agent(system_prompt=code_system_prompt,
94
72
  name="CodeAgent",
@@ -21,276 +21,109 @@ class PatchOutputHandler(OutputHandler):
21
21
 
22
22
  def prompt(self) -> str:
23
23
  return """
24
- # 🔄 REPLACE: Modify existing code
25
- <REPLACE>
26
- File: path/to/file
27
- Lines: [start,end] or [start,end)
28
- [new content]
29
- ...
30
- </REPLACE>
31
-
32
- # ➕ INSERT: Add new code
33
- <INSERT>
34
- File: path/to/file
35
- Line: position
36
- [new content]
37
- ...
38
- </INSERT>
39
-
40
- # 🗑️ DELETE: Remove existing code
41
- <DELETE>
42
- File: path/to/file
43
- Lines: [start,end] or [start,end)
44
- </DELETE>
45
-
46
- # 🆕 NEW_FILE: Create new file
47
- <NEW_FILE>
48
- File: path/to/file
49
- [new content]
50
- ...
51
- </NEW_FILE>
52
-
53
- # ➡️ MOVE_FILE: Relocate a file
54
- <MOVE_FILE>
55
- File: path/to/source/file
56
- NewPath: path/to/destination/file
57
- </MOVE_FILE>
58
-
59
- # ❌ REMOVE_FILE: Delete entire file
60
- <REMOVE_FILE>
61
- File: path/to/file
62
- </REMOVE_FILE>
63
-
64
- # 📋 Formatting Rules
65
- 1. File Paths
66
- - Use relative paths from project root
67
- - Must be exact and case-sensitive
68
- - Example: src/module/file.py
69
-
70
- 2. Line Numbers
71
- - Format: [start,end] (inclusive) or [start,end) (right-exclusive)
72
- - 1-based line numbers
73
- - Single number for INSERT
74
- - Omit for NEW_FILE/REMOVE_FILE
75
-
76
- 3. Content
77
- - Maintain original indentation
78
- - Follow existing code style
79
-
80
- # 📌 Usage Examples
81
- ## REPLACE Example (Closed Interval)
82
- <REPLACE>
83
- File: src/utils.py
84
- Lines: [9,13]
24
+ # 🛠️ Simplified Patch Format
25
+ <PATCH>
26
+ File path [Operation parameters]
27
+ Code content
28
+ </PATCH>
29
+
30
+ Operation types:
31
+ - Replace: [Start line,End line] Replace line range (e.g. [5,8] replaces lines 5-8)
32
+ - Delete: [Start line,End line] Delete line range (e.g. [10,10] deletes line 10)
33
+ - Insert: [Line number] Insert before specified line (e.g. [3] inserts before line 3)
34
+ - New file: [1] Create new file
35
+
36
+ Examples:
37
+ # Replace operation
38
+ <PATCH>
39
+ src/app.py [5,8]
85
40
  def updated_function():
86
- # Replaces lines 9-13 inclusive
87
- return "new_implementation"
88
- </REPLACE>
41
+ print("Replaced lines 5-8")
42
+ return new_value * 2
43
+ </PATCH>
89
44
 
90
- ## REPLACE Example (Left-Closed Right-Open)
91
- <REPLACE>
92
- File: src/calculator.py
93
- Lines: [5,8)
94
- def new_calculation():
95
- # Replaces lines 5-7 (excludes line 8)
96
- return 42
97
- </REPLACE>
45
+ # Delete operation
46
+ <PATCH>
47
+ src/old.py [10,10]
48
+ </PATCH>
98
49
 
99
- ## INSERT Example
100
- <INSERT>
101
- File: src/main.py
102
- Line: 19
103
- # Inserted before line 19
104
- new_feature()
105
- </INSERT>
50
+ # Insert operation
51
+ <PATCH>
52
+ utils/logger.py [3]
53
+ print("Inserted before original line 3")
54
+ </PATCH>
106
55
 
107
- ## NEW_FILE Example
108
- <NEW_FILE>
109
- File: src/new_module.py
110
56
  # New file creation
111
- def feature_entry():
112
- pass
113
- </NEW_FILE>
114
-
115
- ## DELETE Example
116
- <DELETE>
117
- File: src/utils.py
118
- Lines: [9,13]
119
- </DELETE>
120
-
121
- ## MOVE_FILE Example
122
- <MOVE_FILE>
123
- File: src/old_dir/file.py
124
- NewPath: src/new_dir/file.py
125
- </MOVE_FILE>
126
-
127
- ## REMOVE_FILE Example
128
- <REMOVE_FILE>
129
- File: src/obsolete.py
130
- </REMOVE_FILE>
131
-
132
- # 🚨 Critical Requirements
133
- 1. One change per block
134
- 2. Use correct operation type
135
- 3. Match existing code style
136
- 4. Preserve indentation levels
137
- 5. Exact file paths required
138
- 6. Handle edge cases properly
139
- 7. Include error handling
140
- 8. Maintain code consistency
141
-
142
- # 🚫 Invalid Format Examples
143
- ## BAD EXAMPLE 1 - Do not use diff format
144
- <REPLACE>
145
- File: src/file.py
146
- Lines: [5,8)
147
- - old_line_1
148
- + new_line_1
149
- </REPLACE>
150
-
151
- ## BAD EXAMPLE 2 - Do not include previous and new tags
152
- <REPLACE>
153
- File: src/file.py
154
- Lines: [10,12]
155
- <PREVIOUS>
156
- old_code
157
- </PREVIOUS>
158
- <NEW>
159
- new_code
160
- </NEW>
161
- </REPLACE>
162
-
163
- ## BAD EXAMPLE 3 - Do not use comment to explain
164
- <REPLACE>
165
- File: src/file.py
166
- Lines: [15,18]
167
- # Replace the following code
168
- old_function()
169
- # With the new implementation
170
- new_function()
171
- </REPLACE>
57
+ <PATCH>
58
+ config.yaml [1]
59
+ database:
60
+ host: localhost
61
+ port: 5432
62
+ </PATCH>
172
63
  """
173
64
 
174
65
 
175
66
  def _parse_patch(patch_str: str) -> Dict[str, List[Dict[str, Any]]]:
176
- """Parse patches from string with optimized format"""
67
+ """解析补丁格式"""
177
68
  result = {}
178
- patches = re.findall(r"<(REPLACE|INSERT|DELETE|NEW_FILE|REMOVE_FILE|MOVE_FILE)>(.*?)</\1>", patch_str, re.DOTALL)
69
+ header_pattern = re.compile(
70
+ r'^\s*"?(.+?)"?\s*\[(\d+)(?:,(\d+))?\]\s*$' # Match file path and line number
71
+ )
72
+ patches = re.findall(r'<PATCH>\n?(.*?)\n?</PATCH>', patch_str, re.DOTALL)
179
73
 
180
- for patch_type, patch in patches:
181
- lines = patch.strip().split('\n')
182
- if not lines:
74
+ for patch in patches:
75
+ # 分割首行和内容
76
+ parts = patch.split('\n', 1)
77
+ if len(parts) < 1:
183
78
  continue
79
+ header_line = parts[0].strip()
80
+ content = parts[1] if len(parts) > 1 else ''
81
+
82
+ # 仅在内容非空时添加换行符
83
+ if content and not content.endswith('\n'):
84
+ content += '\n'
184
85
 
185
- # Parse file path
186
- file_match = re.match(r"File:\s*([^\s]+)", lines[0])
187
- if not file_match:
86
+ # 解析文件路径和行号
87
+ header_match = header_pattern.match(header_line)
88
+ if not header_match:
188
89
  continue
189
- filepath = file_match.group(1).strip()
190
-
191
- # Initialize line numbers
192
- start_line = end_line = 0
193
-
194
- # Parse line numbers based on operation type
195
- if patch_type in ['REPLACE', 'DELETE']:
196
- # 增强正则表达式兼容性
197
- line_match = re.match(
198
- r"^Lines:\s*\[\s*(\d+)\s*(?:,\s*(\d+)\s*)?([\]\)])\s*$", # 支持单数字格式
199
- lines[1].strip(), # 去除前后空格
200
- re.IGNORECASE
201
- )
202
- if line_match:
203
- start_line = int(line_match.group(1))
204
- end_value = int(line_match.group(2) or line_match.group(1)) # 第二个数字不存在时使用第一个
205
- bracket_type = line_match.group(3).strip()
206
-
207
- # 根据括号类型处理区间
208
- if bracket_type == ')': # [m,n)
209
- end_line = end_value - 1
210
- else: # [m,n]
211
- end_line = end_value
212
-
213
- # 确保 end_line >= start_line
214
- end_line = max(end_line, start_line)
215
- else:
216
- PrettyOutput.print(f"无法解析行号格式: {lines[1]}", OutputType.WARNING)
217
- continue
218
- elif patch_type == 'INSERT':
219
- line_match = re.match(r"Line:\s*(\d+)", lines[1])
220
- if line_match:
221
- start_line = int(line_match.group(1)) # 1-based
222
- end_line = start_line
223
- elif patch_type == 'MOVE_FILE':
224
- new_path_match = re.match(r"NewPath:\s*([^\s]+)", lines[1])
225
- if new_path_match:
226
- new_path = new_path_match.group(1).strip()
227
- else:
228
- continue
229
-
230
- # Get content (after metadata)
231
- if patch_type in ['REPLACE', 'DELETE']:
232
- content_start = 2 # File + Lines
233
- elif patch_type == 'INSERT':
234
- content_start = 2 # File + Line
235
- elif patch_type == 'NEW_FILE':
236
- content_start = 1 # File
237
- elif patch_type == 'MOVE_FILE':
238
- content_start = 2 # File + NewPath
239
- elif patch_type == 'REMOVE_FILE':
240
- content_start = 1 # File
241
-
242
- content_lines = lines[content_start:]
243
- # 保留原始缩进和空行
244
- content = '\n'.join(content_lines).rstrip('\n') + '\n' # 保留末尾换行
245
90
 
91
+ filepath = header_match.group(1)
92
+ start = int(header_match.group(2)) # 保持1-based行号
93
+ end = int(header_match.group(3)) + 1 if header_match.group(3) else start
94
+
95
+ # 存储参数
246
96
  if filepath not in result:
247
97
  result[filepath] = []
248
-
249
- # Handle MOVE_FILE specially
250
- if patch_type == 'MOVE_FILE':
251
- result[filepath].append({
252
- 'type': patch_type,
253
- 'new_path': new_path,
254
- 'content': content
255
- })
256
- else:
257
- result[filepath].append({
258
- 'type': patch_type,
259
- 'start_line': start_line,
260
- 'end_line': end_line,
261
- 'content': content
262
- })
263
-
264
- # Sort patches by start line in reverse order to apply from bottom to top
265
- for filepath in result:
266
- result[filepath].sort(key=lambda x: x.get('start_line', 0), reverse=True)
267
-
98
+ result[filepath].append({
99
+ 'filepath': filepath,
100
+ 'start': start,
101
+ 'end': end,
102
+ 'content': content # 保留原始内容(可能为空)
103
+ })
104
+ for filepath in result.keys():
105
+ result[filepath] = sorted(result[filepath], key=lambda x: x['start'], reverse=True)
268
106
  return result
269
107
 
270
108
 
271
109
  def apply_patch(output_str: str) -> str:
272
110
  """Apply patches to files"""
273
- patches = _parse_patch(output_str)
274
- ret = "" # Initialize return value
275
-
276
- for filepath, patch_info in patches.items():
277
- try:
278
- for patch in patch_info:
279
- patch_type = patch['type']
280
-
281
- if patch_type == 'MOVE_FILE':
282
- handle_move_file(filepath, patch)
283
- elif patch_type == 'NEW_FILE':
284
- handle_new_file(filepath, patch)
285
- elif patch_type == 'REMOVE_FILE':
286
- handle_remove_file(filepath)
287
- else:
288
- handle_code_operation(filepath, patch)
289
-
290
- except Exception as e:
291
- PrettyOutput.print(f"应用 {patch_type} 操作到 {filepath} 失败: {str(e)}", OutputType.ERROR)
292
- continue
111
+ try:
112
+ patches = _parse_patch(output_str)
113
+ except Exception as e:
114
+ PrettyOutput.print(f"解析补丁失败: {str(e)}", OutputType.ERROR)
115
+ return ""
293
116
 
117
+ ret = ""
118
+
119
+ for filepath, patch_list in patches.items():
120
+ for patch in patch_list:
121
+ try:
122
+ handle_code_operation(filepath, patch)
123
+ PrettyOutput.print(f"成功处理 操作", OutputType.SUCCESS)
124
+ except Exception as e:
125
+ PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
126
+
294
127
  if has_uncommitted_changes():
295
128
  diff = get_diff()
296
129
  if handle_commit_workflow(diff):
@@ -308,15 +141,25 @@ def apply_patch(output_str: str) -> str:
308
141
  if user_input:
309
142
  ret += "\n" + user_input
310
143
  else:
311
- ret += "Please check the patch again"
144
+ ret = ""
312
145
 
313
146
  return ret # Ensure a string is always returned
314
147
 
315
- def get_diff()->str:
316
- os.system("git add .")
317
- diff = os.popen("git diff HEAD").read()
318
- os.system("git reset HEAD")
319
- return diff
148
+ def get_diff() -> str:
149
+ """使用更安全的subprocess代替os.system"""
150
+ import subprocess
151
+ try:
152
+ subprocess.run(['git', 'add', '.'], check=True)
153
+ result = subprocess.run(
154
+ ['git', 'diff', 'HEAD'],
155
+ capture_output=True,
156
+ text=True,
157
+ check=True
158
+ )
159
+ return result.stdout
160
+ finally:
161
+ subprocess.run(['git', 'reset', 'HEAD'], check=True)
162
+
320
163
  def handle_commit_workflow(diff:str)->bool:
321
164
  """Handle the git commit workflow and return the commit details.
322
165
 
@@ -365,82 +208,62 @@ def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
365
208
  return result
366
209
  # New handler functions below ▼▼▼
367
210
 
368
- def handle_move_file(filepath: str, patch: Dict[str, Any]):
369
- """Handle file moving operation"""
370
- new_path = patch['new_path']
371
- os.makedirs(os.path.dirname(new_path), exist_ok=True)
372
- if os.path.exists(filepath):
373
- os.rename(filepath, new_path)
374
- PrettyOutput.print(f"成功移动文件 {filepath} -> {new_path}", OutputType.SUCCESS)
375
- else:
376
- PrettyOutput.print(f"源文件不存在: {filepath}", OutputType.WARNING)
377
-
378
211
  def handle_new_file(filepath: str, patch: Dict[str, Any]):
379
- """Handle new file creation"""
380
- new_content = patch.get('content', '').splitlines(keepends=True)
381
- if new_content and new_content[-1] and new_content[-1][-1] != '\n':
382
- new_content[-1] += '\n'
383
-
212
+ """统一参数格式处理新文件"""
384
213
  os.makedirs(os.path.dirname(filepath), exist_ok=True)
385
214
  with open(filepath, 'w', encoding='utf-8') as f:
386
- f.writelines(new_content)
387
- PrettyOutput.print(f"成功创建新文件 {filepath}", OutputType.SUCCESS)
388
-
389
- def handle_remove_file(filepath: str):
390
- """Handle file removal"""
391
- if os.path.exists(filepath):
392
- os.remove(filepath)
393
- PrettyOutput.print(f"成功删除文件 {filepath}", OutputType.SUCCESS)
394
- else:
395
- PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
215
+ f.write(patch['content'])
396
216
 
397
217
  def handle_code_operation(filepath: str, patch: Dict[str, Any]):
398
- """Handle code modification operations (REPLACE/INSERT/DELETE)"""
399
- patch_type = patch['type']
400
- start_line = patch.get('start_line', 0)
401
- end_line = patch.get('end_line', 0)
402
- new_content = patch.get('content', '').splitlines(keepends=True)
403
-
404
- if not new_content:
405
- new_content = ['']
406
-
407
- PrettyOutput.print(f"patch_type: {patch_type}\nstart_line: {start_line}\nend_line: {end_line}\nnew_content:\n{''.join(new_content)}", OutputType.INFO)
408
-
409
- if new_content and new_content[-1] and new_content[-1][-1] != '\n':
410
- new_content[-1] += '\n'
411
-
412
- if not os.path.exists(filepath):
413
- PrettyOutput.print(f"文件不存在: {filepath}", OutputType.WARNING)
414
- return
218
+ """处理紧凑格式补丁"""
219
+ try:
220
+ # 新建文件时强制覆盖
221
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
222
+ if not os.path.exists(filepath):
223
+ open(filepath, 'w', encoding='utf-8').close()
224
+ with open(filepath, 'r+', encoding='utf-8') as f:
225
+ lines = f.readlines()
226
+
227
+ new_lines = validate_and_apply_changes(
228
+ lines,
229
+ patch['start'],
230
+ patch['end'],
231
+ patch['content']
232
+ )
233
+
234
+ f.seek(0)
235
+ f.writelines(new_lines)
236
+ f.truncate()
415
237
 
416
- with open(filepath, 'r+', encoding='utf-8') as f:
417
- lines = f.readlines()
418
- validate_and_apply_changes(patch_type, lines, start_line, end_line, new_content)
419
- f.seek(0)
420
- f.writelines(lines)
421
- f.truncate()
238
+ PrettyOutput.print(f"成功更新 {filepath}", OutputType.SUCCESS)
422
239
 
423
- PrettyOutput.print(f"成功对 {filepath} 执行 {patch_type} 操作", OutputType.SUCCESS)
240
+ except Exception as e:
241
+ PrettyOutput.print(f"操作失败: {str(e)}", OutputType.ERROR)
424
242
 
425
243
  def validate_and_apply_changes(
426
- patch_type: str,
427
244
  lines: List[str],
428
- start_line: int,
429
- end_line: int,
430
- new_content: List[str]
431
- ):
432
- """Validate and apply code changes to in-memory file content"""
433
- if patch_type in ['REPLACE', 'DELETE']:
434
- if start_line < 1 or end_line > len(lines) or start_line > end_line:
435
- raise ValueError(f"Invalid line range [{start_line}, {end_line}] (total lines: {len(lines)})")
436
-
437
- if patch_type == 'REPLACE':
438
- lines[start_line-1:end_line] = new_content
439
- else: # DELETE
440
- lines[start_line-1:end_line] = []
441
-
442
- elif patch_type == 'INSERT':
443
- if start_line < 1 or start_line > len(lines) + 1:
444
- raise ValueError(f"Invalid insertion position [{start_line}] (valid range: 1-{len(lines)+1})")
445
-
446
- lines[start_line-1:start_line-1] = new_content
245
+ start: int,
246
+ end: int,
247
+ content: str
248
+ ) -> List[str]:
249
+
250
+ new_content = content.splitlines(keepends=True)
251
+
252
+ # 插入操作处理
253
+ if start == end:
254
+ if start < 1 or start > len(lines)+1:
255
+ raise ValueError(f"无效插入位置: {start}")
256
+ # 在指定位置前插入
257
+ return lines[:start-1] + new_content + lines[start-1:]
258
+
259
+ # 范围替换/删除操作
260
+ if start > end:
261
+ raise ValueError(f"起始行{start}不能大于结束行{end}")
262
+
263
+ max_line = len(lines)
264
+ # 自动修正行号范围
265
+ start = max(1, min(start, max_line+1))
266
+ end = max(start, min(end, max_line+1))
267
+
268
+ # 执行替换
269
+ return lines[:start-1] + new_content + lines[end-1:]
@@ -60,14 +60,12 @@ Identify customization options:
60
60
  - "Should we create a new class?"
61
61
 
62
62
  # 🎨 Question Template
63
- ```
63
+ 3-5 specific questions about existing implementations:
64
64
  <QUESTION>
65
- [3-5 specific questions about existing implementations:
66
65
  1. System architecture question
67
66
  2. Implementation details question
68
- 3. Integration or extension question]
67
+ 3. Integration or extension question
69
68
  </QUESTION>
70
- ```
71
69
 
72
70
  # 🔎 Investigation Focus
73
71
  1. Current System
@@ -350,8 +350,6 @@ class KimiModel(BasePlatform):
350
350
  output.append("")
351
351
 
352
352
  PrettyOutput.print("\n".join(output), OutputType.PROGRESS)
353
-
354
- PrettyOutput.print(full_response, OutputType.RESULT)
355
353
 
356
354
  return full_response
357
355
 
@@ -57,9 +57,6 @@ class FileOperationTool:
57
57
  content = open(abs_path, 'r', encoding='utf-8').read()
58
58
  output = f"File: {abs_path}\n{content}"
59
59
 
60
- # Print file content
61
- PrettyOutput.print(f"读取文件: {abs_path}\n{content}", OutputType.INFO)
62
-
63
60
  return {
64
61
  "success": True,
65
62
  "stdout": output,