autocoder-nano 0.1.30__py3-none-any.whl → 0.1.33__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 (34) hide show
  1. autocoder_nano/agent/agent_base.py +4 -4
  2. autocoder_nano/agent/agentic_edit.py +1584 -0
  3. autocoder_nano/agent/agentic_edit_tools/__init__.py +28 -0
  4. autocoder_nano/agent/agentic_edit_tools/ask_followup_question_tool.py +51 -0
  5. autocoder_nano/agent/agentic_edit_tools/attempt_completion_tool.py +36 -0
  6. autocoder_nano/agent/agentic_edit_tools/base_tool_resolver.py +31 -0
  7. autocoder_nano/agent/agentic_edit_tools/execute_command_tool.py +65 -0
  8. autocoder_nano/agent/agentic_edit_tools/list_code_definition_names_tool.py +78 -0
  9. autocoder_nano/agent/agentic_edit_tools/list_files_tool.py +123 -0
  10. autocoder_nano/agent/agentic_edit_tools/list_package_info_tool.py +42 -0
  11. autocoder_nano/agent/agentic_edit_tools/plan_mode_respond_tool.py +35 -0
  12. autocoder_nano/agent/agentic_edit_tools/read_file_tool.py +73 -0
  13. autocoder_nano/agent/agentic_edit_tools/replace_in_file_tool.py +148 -0
  14. autocoder_nano/agent/agentic_edit_tools/search_files_tool.py +135 -0
  15. autocoder_nano/agent/agentic_edit_tools/write_to_file_tool.py +57 -0
  16. autocoder_nano/agent/agentic_edit_types.py +151 -0
  17. autocoder_nano/auto_coder_nano.py +145 -91
  18. autocoder_nano/git_utils.py +63 -1
  19. autocoder_nano/llm_client.py +170 -3
  20. autocoder_nano/llm_types.py +53 -14
  21. autocoder_nano/rules/rules_learn.py +221 -0
  22. autocoder_nano/templates.py +1 -1
  23. autocoder_nano/utils/formatted_log_utils.py +128 -0
  24. autocoder_nano/utils/printer_utils.py +5 -4
  25. autocoder_nano/utils/shell_utils.py +85 -0
  26. autocoder_nano/version.py +1 -1
  27. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/METADATA +3 -2
  28. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/RECORD +33 -16
  29. autocoder_nano/agent/new/auto_new_project.py +0 -278
  30. /autocoder_nano/{agent/new → rules}/__init__.py +0 -0
  31. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/LICENSE +0 -0
  32. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/WHEEL +0 -0
  33. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/entry_points.txt +0 -0
  34. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,7 @@ class AutoCoderArgs(BaseModel):
18
18
  index_filter_level: Optional[int] = 0 # 用于查找相关文件的过滤级别
19
19
  index_filter_file_num: Optional[int] = -1 #
20
20
  index_filter_workers: Optional[int] = 1 # 过滤文件的线程数量
21
+ index_model_max_input_length: Optional[int] = 6000 # 模型最大输入长度
21
22
  filter_batch_size: Optional[int] = 5 #
22
23
  anti_quota_limit: Optional[int] = 1 # 请求模型时的间隔时间(s)
23
24
  skip_build_index: Optional[bool] = False # 是否跳过索引构建(索引可以帮助您通过查询找到相关文件)
@@ -46,7 +47,7 @@ class AutoCoderArgs(BaseModel):
46
47
  full_text_ratio: Optional[float] = 0.7
47
48
  segment_ratio: Optional[float] = 0.2
48
49
  buff_ratio: Optional[float] = 0.1
49
- required_exts: Optional[str] = None # 指定处理的文件后缀,例如.pdf,.doc
50
+ required_exts: Optional[str] = None # 指定处理的文件后缀,例如.pdf,.doc
50
51
  monitor_mode: bool = False # 监控模式,会监控doc_dir目录中的文件变化
51
52
  enable_hybrid_index: bool = False # 开启混合索引
52
53
  disable_auto_window: bool = False
@@ -57,26 +58,32 @@ class AutoCoderArgs(BaseModel):
57
58
  enable_rag_context: Optional[Union[bool, str]] = False
58
59
  disable_segment_reorder: bool = False
59
60
  disable_inference_enhance: bool = False
60
- duckdb_vector_dim: Optional[int] = 1024 # DuckDB 向量化存储的维度
61
+ duckdb_vector_dim: Optional[int] = 1024 # DuckDB 向量化存储的维度
61
62
  duckdb_query_similarity: Optional[float] = 0.7 # DuckDB 向量化检索 相似度 阈值
62
- duckdb_query_top_k: Optional[int] = 50 # DuckDB 向量化检索 返回 TopK个结果(且大于相似度)
63
+ duckdb_query_top_k: Optional[int] = 50 # DuckDB 向量化检索 返回 TopK个结果(且大于相似度)
63
64
 
64
65
  # Git 相关参数
65
66
  skip_commit: Optional[bool] = False
66
67
 
68
+ # Rules 相关参数
69
+ enable_rules: Optional[bool] = False
70
+
71
+ # Agent 相关参数
72
+ generate_max_rounds: Optional[int] = 5
73
+
67
74
  # 模型相关参数
68
75
  current_chat_model: Optional[str] = ""
69
76
  current_code_model: Optional[str] = ""
70
- model: Optional[str] = "" # 默认模型
71
- chat_model: Optional[str] = "" # AI Chat交互模型
72
- index_model: Optional[str] = "" # 代码索引生成模型
73
- code_model: Optional[str] = "" # 编码模型
74
- commit_model: Optional[str] = "" # Git Commit 模型
75
- emb_model: Optional[str] = "" # RAG Emb 模型
76
- recall_model: Optional[str] = "" # RAG 召回阶段模型
77
- chunk_model: Optional[str] = "" # 段落重排序模型
78
- qa_model: Optional[str] = "" # RAG 提问模型
79
- vl_model: Optional[str] = "" # 多模态模型
77
+ model: Optional[str] = "" # 默认模型
78
+ chat_model: Optional[str] = "" # AI Chat交互模型
79
+ index_model: Optional[str] = "" # 代码索引生成模型
80
+ code_model: Optional[str] = "" # 编码模型
81
+ commit_model: Optional[str] = "" # Git Commit 模型
82
+ emb_model: Optional[str] = "" # RAG Emb 模型
83
+ recall_model: Optional[str] = "" # RAG 召回阶段模型
84
+ chunk_model: Optional[str] = "" # 段落重排序模型
85
+ qa_model: Optional[str] = "" # RAG 提问模型
86
+ vl_model: Optional[str] = "" # 多模态模型
80
87
 
81
88
  class Config:
82
89
  protected_namespaces = ()
@@ -117,6 +124,14 @@ class SourceCode(BaseModel):
117
124
  metadata: Dict[str, Any] = Field(default_factory=dict)
118
125
 
119
126
 
127
+ class SourceCodeList:
128
+ def __init__(self, sources: List[SourceCode]):
129
+ self.sources = sources
130
+
131
+ def to_str(self):
132
+ return "\n".join([f"##File: {source.module_name}\n{source.source_code}\n" for source in self.sources])
133
+
134
+
120
135
  class LLMRequest(BaseModel):
121
136
  model: str # 指定使用的语言模型名称
122
137
  messages: List[Dict[str, str]] # 包含对话消息的列表,每个消息是一个字典,包含 "role"(角色)和 "content"(内容)
@@ -138,6 +153,21 @@ class LLMResponse(BaseModel):
138
153
  )
139
154
 
140
155
 
156
+ class SingleOutputMeta:
157
+ def __init__(self, input_tokens_count: int = 0,
158
+ generated_tokens_count: int = 0,
159
+ reasoning_content: str = "",
160
+ finish_reason: str = "",
161
+ first_token_time: float = 0.0,
162
+ extra_info: Dict[str, Any] = {}):
163
+ self.input_tokens_count = input_tokens_count
164
+ self.generated_tokens_count = generated_tokens_count
165
+ self.reasoning_content = reasoning_content
166
+ self.finish_reason = finish_reason
167
+ self.first_token_time = first_token_time
168
+ self.extra_info = extra_info
169
+
170
+
141
171
  class IndexItem(BaseModel):
142
172
  module_name: str
143
173
  symbols: str
@@ -269,4 +299,13 @@ class FileInfo(BaseModel):
269
299
  file_path: str
270
300
  relative_path: str
271
301
  modify_time: float
272
- file_md5: str
302
+ file_md5: str
303
+
304
+
305
+ class RuleFile(BaseModel):
306
+ """规则文件的Pydantic模型"""
307
+ description: str = Field(default="", description="规则的描述")
308
+ globs: List[str] = Field(default_factory=list, description="文件匹配模式列表")
309
+ always_apply: bool = Field(default=False, description="是否总是应用规则")
310
+ content: str = Field(default="", description="规则文件的正文内容")
311
+ file_path: str = Field(default="", description="规则文件的路径")
@@ -0,0 +1,221 @@
1
+ import os
2
+ from typing import List, Tuple, Dict, Optional, Generator
3
+
4
+ from autocoder_nano.git_utils import get_commit_changes
5
+ from autocoder_nano.llm_client import AutoLLM
6
+ from autocoder_nano.llm_prompt import prompt
7
+ from autocoder_nano.llm_types import AutoCoderArgs, SourceCodeList
8
+ from autocoder_nano.utils.printer_utils import Printer
9
+
10
+
11
+ printer = Printer()
12
+
13
+
14
+ class AutoRulesLearn:
15
+
16
+ def __init__(self, args: AutoCoderArgs, llm: AutoLLM):
17
+ self.args = args
18
+ self.llm = llm
19
+
20
+ @prompt()
21
+ def _analyze_commit_changes(
22
+ self, querie_with_urls_and_changes: List[Tuple[str, List[str], Dict[str, Tuple[str, str]]]]
23
+ ):
24
+ """
25
+ 下面是用户一次提交的代码变更:
26
+ <changes>
27
+ {% for query,urls,changes in querie_with_urls_and_changes %}
28
+ ## 原始的任务需求
29
+ {{ query }}
30
+
31
+ 修改的文件:
32
+ {% for url in urls %}
33
+ - {{ url }}
34
+ {% endfor %}
35
+
36
+ 代码变更:
37
+ {% for file_path, (before, after) in changes.items() %}
38
+ ##File: {{ file_path }}
39
+ ##修改前:
40
+
41
+ {{ before or "New file" }}
42
+
43
+ ##File: {{ file_path }}
44
+ ##修改后:
45
+
46
+ {{ after or "File deleted" }}
47
+
48
+ {% endfor %}
49
+ {% endfor %}
50
+ </changes>
51
+
52
+ 请对根据上面的代码变更进行深入分析,提取具有通用价值的功能模式和设计模式,转化为可在其他项目中复用的代码规则(rules)。
53
+
54
+ - 识别代码变更中具有普遍应用价值的功能点和模式
55
+ - 将这些功能点提炼为结构化规则,便于在其他项目中快速复用
56
+ - 生成清晰的使用示例,包含完整依赖和调用方式
57
+
58
+ 最后,新生成的文件格式要是这种形态的:
59
+
60
+ <example_rules>
61
+ ---
62
+ description: [简明描述规则的功能,20字以内]
63
+ globs: [匹配应用此规则的文件路径,如"src/services/*.py"]
64
+ alwaysApply: [是否总是应用,通常为false]
65
+ ---
66
+
67
+ # [规则主标题]
68
+
69
+ ## 简要说明
70
+ [该规则的功能、适用场景和价值,100字以内]
71
+
72
+ ## 典型用法
73
+ ```python
74
+ # 完整的代码示例,包含:
75
+ # 1. 必要的import语句
76
+ # 2. 类/函数定义
77
+ # 3. 参数说明
78
+ # 4. 调用方式
79
+ # 5. 关键注释
80
+ ```
81
+
82
+ ## 依赖说明
83
+ - [必要的依赖库及版本]
84
+ - [环境要求]
85
+ - [初始化流程(如有)]
86
+
87
+ ## 学习来源
88
+ [从哪个提交变更的哪部分代码中提取的该功能点]
89
+ </example_rules>
90
+ """
91
+
92
+ @prompt()
93
+ def _analyze_modules(self, sources: SourceCodeList):
94
+ """
95
+ 下面是用户提供的需要抽取规则的代码:
96
+ <files>
97
+ {% for source in sources.sources %}
98
+ ##File: {{ source.module_name }}
99
+ {{ source.source_code }}
100
+ {% endfor %}
101
+ </files>
102
+
103
+ 请对对上面的代码进行深入分析,提取具有通用价值的功能模式和设计模式,转化为可在其他项目中复用的代码规则(rules)。
104
+
105
+ - 识别代码变更中具有普遍应用价值的功能点和模式
106
+ - 将这些功能点提炼为结构化规则,便于在其他项目中快速复用
107
+ - 生成清晰的使用示例,包含完整依赖和调用方式
108
+
109
+ 最后,新生成的文件格式要是这种形态的:
110
+
111
+ <example_rules>
112
+ ---
113
+ description: [简明描述规则的功能,20字以内]
114
+ globs: [匹配应用此规则的文件路径,如"src/services/*.py"]
115
+ alwaysApply: [是否总是应用,通常为false]
116
+ ---
117
+
118
+ # [规则主标题]
119
+
120
+ ## 简要说明
121
+ [该规则的功能、适用场景和价值,100字以内]
122
+
123
+ ## 典型用法
124
+ ```python
125
+ # 完整的代码示例,包含:
126
+ # 1. 必要的import语句
127
+ # 2. 类/函数定义
128
+ # 3. 参数说明
129
+ # 4. 调用方式
130
+ # 5. 关键注释
131
+ ```
132
+
133
+ ## 依赖说明
134
+ - [必要的依赖库及版本]
135
+ - [环境要求]
136
+ - [初始化流程(如有)]
137
+
138
+ ## 学习来源
139
+ [从哪个提交变更的哪部分代码中提取的该功能点]
140
+ </example_rules>
141
+ """
142
+
143
+ def analyze_commit_changes(
144
+ self, commit_id: str, conversations=None
145
+ ) -> str:
146
+ """ 分析指定commit的代码变更 """
147
+ if conversations is None:
148
+ conversations = []
149
+
150
+ changes, _ = get_commit_changes(self.args.source_dir, commit_id)
151
+
152
+ if not changes:
153
+ printer.print_text("未发现代码变更(Commit)", style="yellow")
154
+ return ""
155
+
156
+ try:
157
+ # 获取prompt内容
158
+ prompt_content = self._analyze_commit_changes.prompt(
159
+ querie_with_urls_and_changes=changes
160
+ )
161
+
162
+ # 准备对话历史
163
+ if conversations:
164
+ new_conversations = conversations[:-1]
165
+ else:
166
+ new_conversations = []
167
+ new_conversations.append({"role": "user", "content": prompt_content})
168
+
169
+ self.llm.setup_default_model_name(self.args.chat_model)
170
+ v = self.llm.chat_ai(new_conversations, self.args.chat_model)
171
+ return v.output
172
+ except Exception as e:
173
+ printer.print_text(f"代码变更分析失败: {e}", style="red")
174
+ return ""
175
+
176
+ def analyze_modules(
177
+ self, sources: SourceCodeList, conversations=None
178
+ ) -> str:
179
+ """ 分析给定的模块文件,根据用户需求生成可复用功能点的总结。 """
180
+
181
+ if conversations is None:
182
+ conversations = []
183
+
184
+ if not sources or not sources.sources:
185
+ printer.print_text("没有提供有效的模块文件进行分析.", style="red")
186
+ return ""
187
+
188
+ try:
189
+ # 准备 Prompt
190
+ prompt_content = self._analyze_modules.prompt(
191
+ sources=sources
192
+ )
193
+
194
+ # 准备对话历史
195
+ # 如果提供了 conversations,我们假设最后一个是用户的原始查询,替换它
196
+ if conversations:
197
+ new_conversations = conversations[:-1]
198
+ else:
199
+ new_conversations = []
200
+ new_conversations.append({"role": "user", "content": prompt_content})
201
+
202
+ self.llm.setup_default_model_name(self.args.chat_model)
203
+ v = self.llm.chat_ai(new_conversations, self.args.chat_model)
204
+ return v.output
205
+ except Exception as e:
206
+ printer.print_text(f"代码模块分析失败: {e}", style="red")
207
+ return ""
208
+
209
+ def _get_index_file_content(self) -> str:
210
+ """获取索引文件内容"""
211
+ index_file_path = os.path.join(os.path.abspath(self.args.source_dir), ".autocoderrules", "index.md")
212
+ index_file_content = ""
213
+
214
+ try:
215
+ if os.path.exists(index_file_path):
216
+ with open(index_file_path, 'r', encoding='utf-8') as f:
217
+ index_file_content = f.read()
218
+ except Exception as e:
219
+ printer.print_text(f"读取索引文件时出错: {str(e)}", style="yellow")
220
+
221
+ return index_file_content
@@ -99,7 +99,7 @@ def init_command_template(source_dir: str):
99
99
  ## 2. 查找0和1中的文件引用的相关文件
100
100
  ## 第一次建议使用0
101
101
  index_filter_level: 0
102
- index_model_max_input_length: 30000
102
+ index_model_max_input_length: 100000
103
103
 
104
104
  ## 过滤文件的线程数量
105
105
  ## 如果您有一个大项目,可以增加这个数字
@@ -0,0 +1,128 @@
1
+ import os
2
+ import json
3
+ import uuid
4
+ from datetime import datetime
5
+
6
+
7
+ # New helper function for cleaning up logs
8
+ def _cleanup_logs(logs_dir: str, max_files: int = 100):
9
+ """
10
+ Cleans up old log files in the specified directory, keeping only the most recent ones.
11
+ Log files are expected to follow the naming convention: <YYYYmmdd_HHMMSS>_<uuid>_<suffix>.md
12
+ """
13
+ # logger.debug(f"开始清理日志目录: {logs_dir},最大保留文件数: {max_files}")
14
+ if not os.path.isdir(logs_dir):
15
+ # logger.debug(f"日志目录 {logs_dir} 不存在,无需清理。")
16
+ return
17
+
18
+ log_files = []
19
+ for filename in os.listdir(logs_dir):
20
+ if filename.endswith(".md"):
21
+ parts = filename.split('_')
22
+ # Expected format: <YYYYmmdd_HHMMSS>_<uuid>_<suffix>.md
23
+ # parts[0] should be the full timestamp string "YYYYmmdd_HHMMSS"
24
+ if len(parts) >= 2: # At least timestamp and uuid part (suffix might be empty or complex)
25
+ timestamp_str = parts[0]
26
+ try:
27
+ # Validate the timestamp format
28
+ datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
29
+ log_files.append((timestamp_str, os.path.join(logs_dir, filename)))
30
+ except ValueError:
31
+ # logger.debug(f"文件名 {filename} 的时间戳部分 ({timestamp_str}) 格式不正确,跳过。")
32
+ continue
33
+ else:
34
+ pass
35
+ # Log the parts for better debugging if needed
36
+ # logger.debug(f"文件名 {filename} (分割后: {parts}) 不符合预期的下划线分割数量 (至少需要 <timestamp>_<uuid>_...),跳过。")
37
+
38
+ # Sort by timestamp (oldest first)
39
+ log_files.sort(key=lambda x: x[0])
40
+
41
+ if len(log_files) > max_files:
42
+ files_to_delete_count = len(log_files) - max_files
43
+ # logger.info(f"日志文件数量 ({len(log_files)}) 超过限制 ({max_files}),将删除 {files_to_delete_count} 个最旧的文件。")
44
+ for i in range(files_to_delete_count):
45
+ file_to_delete_timestamp, file_to_delete_path = log_files[i]
46
+ try:
47
+ os.remove(file_to_delete_path)
48
+ # logger.info(f"已删除旧日志文件: {file_to_delete_path} (时间戳: {file_to_delete_timestamp})")
49
+ except OSError as e:
50
+ pass
51
+ # logger.warning(f"删除日志文件 {file_to_delete_path} 失败: {str(e)}")
52
+ # logger.exception(e) # Log stack trace
53
+ else:
54
+ pass
55
+ # logger.debug(f"日志文件数量 ({len(log_files)}) 未超过限制 ({max_files}),无需删除。")
56
+
57
+
58
+ def save_formatted_log(project_root, json_text, suffix):
59
+ """
60
+ Save a JSON log as a formatted markdown file under project_root/.auto-coder/logs/agentic.
61
+ Filename: <YYYYmmdd_HHMMSS>_<uuid>_<suffix>.md
62
+ Also cleans up old logs in the directory, keeping the latest 100.
63
+ Args:
64
+ project_root (str): The root directory of the project.
65
+ json_text (str): The JSON string to be formatted and saved.
66
+ suffix (str): The suffix for the filename.
67
+ """
68
+ # Prepare directory (logs_dir is needed for cleanup first)
69
+ logs_dir = os.path.join(project_root, ".auto-coder", "logs", "agentic")
70
+
71
+ # Cleanup old logs BEFORE saving the new one
72
+ try:
73
+ _cleanup_logs(logs_dir) # Default to keep 100 files
74
+ except Exception as e:
75
+ pass
76
+ # logger.warning(f"日志清理过程中发生错误: {str(e)}")
77
+ # logger.exception(e)
78
+ # Log cleanup failure should not prevent the main functionality
79
+
80
+ # Parse JSON
81
+ try:
82
+ data = json.loads(json_text)
83
+ except Exception as e:
84
+ # logger.error(f"无效的 JSON 格式: {str(e)}") # Log error before raising
85
+ # logger.exception(e) # Log stack trace
86
+ raise ValueError(f"Invalid JSON provided: {e}")
87
+
88
+ # Format as markdown with recursive depth
89
+ def to_markdown(obj, level=1):
90
+ lines = []
91
+ if isinstance(obj, dict):
92
+ for key, value in obj.items():
93
+ lines.append(f"{'#' * (level + 1)} {key}\n")
94
+ lines.extend(to_markdown(value, level + 1))
95
+ elif isinstance(obj, list):
96
+ for idx, item in enumerate(obj, 1):
97
+ lines.append(f"{'#' * (level + 1)} Item {idx}\n")
98
+ lines.extend(to_markdown(item, level + 1))
99
+ else:
100
+ lines.append(str(obj) + "\n")
101
+ return lines
102
+
103
+ md_lines = ["# Log Entry\n"]
104
+ md_lines.extend(to_markdown(data, 1))
105
+ md_content = "\n".join(md_lines)
106
+
107
+ # Ensure directory exists
108
+ # _cleanup_logs checks if dir exists but does not create it.
109
+ # os.makedirs needs to be called to ensure the directory for the new log file.
110
+ os.makedirs(logs_dir, exist_ok=True)
111
+
112
+ # Prepare filename
113
+ now = datetime.now().strftime("%Y%m%d_%H%M%S")
114
+ unique_id = str(uuid.uuid4())
115
+ filename = f"{now}_{unique_id}_{suffix}.md"
116
+ filepath = os.path.join(logs_dir, filename)
117
+
118
+ # Save file
119
+ try:
120
+ with open(filepath, "w", encoding="utf-8") as f:
121
+ f.write(md_content)
122
+ # logger.info(f"日志已保存至: {filepath}")
123
+ except IOError as e:
124
+ # logger.error(f"保存日志文件 {filepath} 失败: {str(e)}")
125
+ # logger.exception(e) # Log stack trace
126
+ raise # Re-throw the exception so the caller knows saving failed
127
+
128
+ return filepath
@@ -294,7 +294,7 @@ class Printer:
294
294
 
295
295
  def print_key_value(
296
296
  self, items: Dict[str, Any], key_style: str = "bold cyan",
297
- value_style: str = "green", separator: str = ": ", panel: bool = True
297
+ value_style: str = "green", separator: str = ": ", panel: bool = True, title: Optional[str] = None
298
298
  ) -> None:
299
299
  """
300
300
  键值对格式化输出
@@ -303,6 +303,7 @@ class Printer:
303
303
  :param value_style: 值的样式
304
304
  :param separator: 键值分隔符
305
305
  :param panel: 是否用面板包裹
306
+ :param title: 面板标题
306
307
  """
307
308
  content = Group(*[
308
309
  Text.assemble(
@@ -310,7 +311,7 @@ class Printer:
310
311
  (str(v), value_style)
311
312
  ) for k, v in items.items()
312
313
  ])
313
- self._print_with_panel(content, panel)
314
+ self._print_with_panel(content, panel, title)
314
315
 
315
316
  def context_aware_help(
316
317
  self, help_content: Dict[str, str], current_context: str, width: int = 40
@@ -336,10 +337,10 @@ class Printer:
336
337
  width=width
337
338
  )
338
339
 
339
- def _print_with_panel(self, content: Any, use_panel: bool) -> None:
340
+ def _print_with_panel(self, content: Any, use_panel: bool, title: Optional[str] = None) -> None:
340
341
  """内部方法:根据参数决定是否使用面板包装"""
341
342
  if use_panel:
342
- self.print_panel(content)
343
+ self.print_panel(content, title)
343
344
  else:
344
345
  self.console.print(content)
345
346
 
@@ -0,0 +1,85 @@
1
+ import os
2
+ import platform
3
+ import subprocess
4
+ import sys
5
+
6
+ import psutil
7
+
8
+
9
+ def get_windows_parent_process_name():
10
+ """
11
+ 获取当前进程的父进程名(仅在Windows系统下有意义)。
12
+
13
+ 适用场景:
14
+ - 判断命令是否由PowerShell或cmd.exe启动,以便调整命令格式。
15
+ - 在Windows平台上进行父进程分析。
16
+
17
+ 参数:
18
+ - 无
19
+
20
+ 返回:
21
+ - str|None: 父进程名(小写字符串,如"powershell.exe"或"cmd.exe"),如果无法获取则为None。
22
+
23
+ 异常:
24
+ - 捕获所有异常,返回None。
25
+ """
26
+ try:
27
+ current_process = psutil.Process()
28
+ while True:
29
+ parent = current_process.parent()
30
+ if parent is None:
31
+ break
32
+ parent_name = parent.name().lower()
33
+ if parent_name in ["powershell.exe", "cmd.exe"]:
34
+ return parent_name
35
+ current_process = parent
36
+ return None
37
+ except Exception:
38
+ return None
39
+
40
+
41
+ def run_cmd_subprocess(command, verbose=False, cwd=None, encoding=sys.stdout.encoding):
42
+ if verbose:
43
+ print("Using run_cmd_subprocess:", command)
44
+
45
+ try:
46
+ shell = os.environ.get("SHELL", "/bin/sh")
47
+ parent_process = None
48
+
49
+ # Determine the appropriate shell
50
+ if platform.system() == "Windows":
51
+ parent_process = get_windows_parent_process_name()
52
+ if parent_process == "powershell.exe":
53
+ command = f"powershell -Command {command}"
54
+
55
+ if verbose:
56
+ print("Running command:", command)
57
+ print("SHELL:", shell)
58
+ if platform.system() == "Windows":
59
+ print("Parent process:", parent_process)
60
+
61
+ process = subprocess.Popen(
62
+ command,
63
+ stdout=subprocess.PIPE,
64
+ stderr=subprocess.STDOUT,
65
+ text=True,
66
+ shell=True,
67
+ encoding=encoding,
68
+ errors="replace",
69
+ bufsize=0, # Set bufsize to 0 for unbuffered output
70
+ universal_newlines=True,
71
+ cwd=cwd,
72
+ )
73
+
74
+ output = []
75
+ while True:
76
+ chunk = process.stdout.read(1)
77
+ if not chunk:
78
+ break
79
+ print(chunk, end="", flush=True) # Print the chunk in real-time
80
+ output.append(chunk) # Store the chunk for later use
81
+
82
+ process.wait()
83
+ return process.returncode, "".join(output)
84
+ except Exception as e:
85
+ return 1, str(e)
autocoder_nano/version.py CHANGED
@@ -1,3 +1,3 @@
1
- __version__ = "0.1.30"
1
+ __version__ = "0.1.33"
2
2
  __author__ = "moofs"
3
3
  __license__ = "Apache License 2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: autocoder_nano
3
- Version: 0.1.30
3
+ Version: 0.1.33
4
4
  Summary: AutoCoder Nano
5
5
  Author: moofs
6
6
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -24,10 +24,11 @@ Requires-Dist: uvicorn
24
24
  Requires-Dist: pathspec
25
25
  Requires-Dist: fastapi
26
26
  Requires-Dist: tokenizers
27
- Requires-Dist: duckdb==1.2.0
27
+ Requires-Dist: duckdb
28
28
  Requires-Dist: numpy
29
29
  Requires-Dist: requests
30
30
  Requires-Dist: beautifulsoup4
31
+ Requires-Dist: psutil
31
32
  Dynamic: author
32
33
  Dynamic: classifier
33
34
  Dynamic: description