siada-cli 0.0.1__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 (215) hide show
  1. agent_config.yaml +34 -0
  2. siada/__init__.py +13 -0
  3. siada/agent_hub/__init__.py +10 -0
  4. siada/agent_hub/coder/bug_fix_agent.py +398 -0
  5. siada/agent_hub/coder/code_gen_agent.py +235 -0
  6. siada/agent_hub/coder/fe_gen_agent.py +37 -0
  7. siada/agent_hub/coder/issue_review_agent.py +68 -0
  8. siada/agent_hub/coder/prompt/__init__.py +0 -0
  9. siada/agent_hub/coder/prompt/base/__init__.py +0 -0
  10. siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
  11. siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
  12. siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
  13. siada/agent_hub/coder/prompt/base/rules.py +40 -0
  14. siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
  15. siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
  16. siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
  17. siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
  18. siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
  19. siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
  20. siada/agent_hub/coder/tracing/__init__.py +22 -0
  21. siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
  22. siada/agent_hub/hooks/__init__.py +0 -0
  23. siada/agent_hub/hooks/io_hooks.py +30 -0
  24. siada/agent_hub/siada_agent.py +214 -0
  25. siada/config/__init__.py +0 -0
  26. siada/config/config_loader.py +67 -0
  27. siada/entrypoint/__init__.py +3 -0
  28. siada/entrypoint/args_parser/__init__.py +0 -0
  29. siada/entrypoint/args_parser/args.py +195 -0
  30. siada/entrypoint/interaction/__init__.py +0 -0
  31. siada/entrypoint/interaction/base_turn.py +107 -0
  32. siada/entrypoint/interaction/config.py +30 -0
  33. siada/entrypoint/interaction/controller.py +122 -0
  34. siada/entrypoint/interaction/nointeractive_controller.py +27 -0
  35. siada/entrypoint/interaction/turn/interface.py +116 -0
  36. siada/entrypoint/interaction/turn/models.py +34 -0
  37. siada/entrypoint/interaction/turn/run_turn.py +641 -0
  38. siada/entrypoint/interaction/turn_factory.py +35 -0
  39. siada/entrypoint/interaction/turn_models.py +28 -0
  40. siada/entrypoint/siadahub.py +413 -0
  41. siada/foundation/__init__.py +0 -0
  42. siada/foundation/code_agent_context.py +48 -0
  43. siada/foundation/config.py +59 -0
  44. siada/foundation/constants.py +2 -0
  45. siada/foundation/context.py +76 -0
  46. siada/foundation/logging.py +341 -0
  47. siada/foundation/tools/__init__.py +0 -0
  48. siada/foundation/tools/get_git_diff.py +105 -0
  49. siada/io/__init__.py +0 -0
  50. siada/io/banner.py +148 -0
  51. siada/io/color_settings.py +207 -0
  52. siada/io/color_utils.py +16 -0
  53. siada/io/components/__init__.py +0 -0
  54. siada/io/components/mdstream.py +204 -0
  55. siada/io/console_printer.py +68 -0
  56. siada/io/io.py +661 -0
  57. siada/io/key_bindings.py +129 -0
  58. siada/io/notification_command.py +40 -0
  59. siada/io/test_banner.py +64 -0
  60. siada/models/__init__.py +0 -0
  61. siada/models/agent.py +47 -0
  62. siada/models/coder/__init__.py +0 -0
  63. siada/models/converter.py +27 -0
  64. siada/models/model_base_config.py +121 -0
  65. siada/models/model_run_config.py +153 -0
  66. siada/provider/__init__.py +0 -0
  67. siada/provider/client_factory.py +112 -0
  68. siada/provider/llm_client.py +11 -0
  69. siada/provider/openrouter/__init__.py +0 -0
  70. siada/provider/openrouter/coverter.py +17 -0
  71. siada/provider/openrouter/openroute_provider.py +33 -0
  72. siada/provider/provider_factory.py +53 -0
  73. siada/queries/tree-sitter-language-pack/README.md +7 -0
  74. siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  75. siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  76. siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  77. siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  78. siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  79. siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  80. siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  81. siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  82. siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  83. siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  84. siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  85. siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  86. siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  87. siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  88. siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  89. siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  90. siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  91. siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  92. siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  93. siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  94. siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  95. siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  96. siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  97. siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  98. siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  99. siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  100. siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  101. siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  102. siada/queries/tree-sitter-languages/README.md +23 -0
  103. siada/queries/tree-sitter-languages/c-tags.scm +9 -0
  104. siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  105. siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  106. siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
  107. siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  108. siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  109. siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
  110. siada/queries/tree-sitter-languages/go-tags.scm +30 -0
  111. siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  112. siada/queries/tree-sitter-languages/java-tags.scm +20 -0
  113. siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  114. siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  115. siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  116. siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  117. siada/queries/tree-sitter-languages/php-tags.scm +26 -0
  118. siada/queries/tree-sitter-languages/python-tags.scm +12 -0
  119. siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
  120. siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  121. siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
  122. siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
  123. siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  124. siada/services/__init__.py +30 -0
  125. siada/services/at_file_recommendation_design_document.md +1213 -0
  126. siada/services/code_context_manager.py +136 -0
  127. siada/services/enhanced_fix_result_check.py +546 -0
  128. siada/services/execution_trace_collector.py +207 -0
  129. siada/services/file_recommendation/__init__.py +136 -0
  130. siada/services/file_recommendation/core/__init__.py +17 -0
  131. siada/services/file_recommendation/core/completion_engine.py +351 -0
  132. siada/services/file_recommendation/core/config.py +62 -0
  133. siada/services/file_recommendation/core/file_discovery.py +353 -0
  134. siada/services/file_recommendation/core/suggestion.py +110 -0
  135. siada/services/file_recommendation/utils/__init__.py +22 -0
  136. siada/services/file_recommendation/utils/path_utils.py +138 -0
  137. siada/services/file_recommendation/utils/text_utils.py +253 -0
  138. siada/services/fix_result_check.py +377 -0
  139. siada/services/handle_at_command/README.md +247 -0
  140. siada/services/handle_at_command/__init__.py +115 -0
  141. siada/services/handle_at_command/exceptions.py +46 -0
  142. siada/services/handle_at_command/models.py +92 -0
  143. siada/services/handle_at_command/parser.py +189 -0
  144. siada/services/handle_at_command/processor.py +321 -0
  145. siada/services/handle_at_command/resolver.py +318 -0
  146. siada/services/handle_at_command/utils.py +345 -0
  147. siada/services/model_info_service.py +103 -0
  148. siada/services/siada_runner.py +170 -0
  149. siada/session/__init__.py +29 -0
  150. siada/session/session_manager.py +66 -0
  151. siada/session/session_models.py +43 -0
  152. siada/support/__init__.py +0 -0
  153. siada/support/completer.py +204 -0
  154. siada/support/editor.py +146 -0
  155. siada/support/envprocessor.py +54 -0
  156. siada/support/repo.py +51 -0
  157. siada/support/slash_commands.py +410 -0
  158. siada/support/spinner.py +320 -0
  159. siada/tools/__init__.py +0 -0
  160. siada/tools/ast/README.md +345 -0
  161. siada/tools/ast/ast_tool.py +341 -0
  162. siada/tools/ast/models.py +11 -0
  163. siada/tools/coder/__init__.py +0 -0
  164. siada/tools/coder/ask_followup_question.py +70 -0
  165. siada/tools/coder/cmd_runner.py +132 -0
  166. siada/tools/coder/file_operator.py +284 -0
  167. siada/tools/coder/file_search/README.md +223 -0
  168. siada/tools/coder/file_search/__init__.py +10 -0
  169. siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
  170. siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
  171. siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
  172. siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
  173. siada/tools/coder/file_search/bin/rg.exe +0 -0
  174. siada/tools/coder/file_search/search.py +437 -0
  175. siada/tools/coder/file_search/setup_package.py +150 -0
  176. siada/tools/coder/file_search/test_search.py +142 -0
  177. siada/tools/coder/files.py +147 -0
  178. siada/tools/coder/fix_attempt_completion.py +58 -0
  179. siada/tools/coder/issue_review_completion.py +29 -0
  180. siada/tools/coder/observation/__init__.py +0 -0
  181. siada/tools/coder/observation/error.py +22 -0
  182. siada/tools/coder/observation/file_observation.py +216 -0
  183. siada/tools/coder/observation/observation.py +71 -0
  184. siada/tools/coder/repo_map/__init__.py +157 -0
  185. siada/tools/coder/repo_map/dump.py +29 -0
  186. siada/tools/coder/repo_map/io.py +226 -0
  187. siada/tools/coder/repo_map/repo_map.py +881 -0
  188. siada/tools/coder/repo_map/special.py +203 -0
  189. siada/tools/coder/repo_map/token_counter.py +154 -0
  190. siada/tools/coder/repo_map/waiting.py +221 -0
  191. siada/tools/coder/reproduce_completion.py +39 -0
  192. siada/tools/coder/run_cmd.py +60 -0
  193. siada/tools/coder/test_completion.py +25 -0
  194. siada/tools/coder/tool_docs.py +44 -0
  195. siada/tools/compression_tool.py +200 -0
  196. siada/tools/read_many_files/README.md +220 -0
  197. siada/tools/read_many_files/__init__.py +25 -0
  198. siada/tools/read_many_files/file_processor.py +486 -0
  199. siada/tools/read_many_files/filters.py +326 -0
  200. siada/tools/read_many_files/formatters.py +204 -0
  201. siada/tools/read_many_files/models.py +189 -0
  202. siada/tools/read_many_files_tool.py +233 -0
  203. siada/tools/tool_call_format/__init__.py +55 -0
  204. siada/tools/tool_call_format/file_to_language.py +231 -0
  205. siada/tools/tool_call_format/formatter_factory.py +80 -0
  206. siada/tools/tool_call_format/formatters.py +282 -0
  207. siada/tools/tool_call_format/tool_call_formatter.py +54 -0
  208. siada/tools/web/__init__.py +0 -0
  209. siada/tools/web/web_crawl.py +208 -0
  210. siada/utils.py +194 -0
  211. siada_cli-0.0.1.dist-info/LICENSE +201 -0
  212. siada_cli-0.0.1.dist-info/METADATA +457 -0
  213. siada_cli-0.0.1.dist-info/RECORD +215 -0
  214. siada_cli-0.0.1.dist-info/WHEEL +4 -0
  215. siada_cli-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,341 @@
1
+ import os
2
+ import logging
3
+ import tempfile
4
+ from pathlib import Path
5
+ from logging.handlers import TimedRotatingFileHandler
6
+ from typing import Literal, Mapping, Optional
7
+ from termcolor import colored
8
+
9
+
10
+ def get_log_directory():
11
+ """
12
+ Get log directory with the following priority:
13
+ - Development mode: Project root ./logs (detected by pyproject.toml)
14
+ - 1. Environment variable SIADAHUB_LOG_DIR
15
+ - 2. User home directory ~/.siada-cli/logs
16
+ - 3. XDG cache directory ~/.cache/siada-cli/logs
17
+ - 4. System temp directory /tmp/siada-cli/logs
18
+ - 5. Current working directory ./logs (fallback)
19
+ """
20
+ # Check if in development mode first
21
+ if _is_development_mode():
22
+ dev_log_dir = _get_development_log_dir()
23
+ if dev_log_dir and _ensure_log_dir(Path(dev_log_dir)):
24
+ return dev_log_dir
25
+
26
+ # 1. Check environment variable
27
+ if env_log_dir := os.getenv('SIADAHUB_LOG_DIR'):
28
+ log_dir = Path(env_log_dir)
29
+ if _ensure_log_dir(log_dir):
30
+ return str(log_dir)
31
+
32
+ # 2. User home directory ~/.siada-cli/logs
33
+ home_log_dir = Path.home() / '.siada-cli' / 'logs'
34
+ if _ensure_log_dir(home_log_dir):
35
+ return str(home_log_dir)
36
+
37
+ # 3. XDG cache directory ~/.cache/siada-cli/logs
38
+ cache_dir = os.environ.get('XDG_CACHE_HOME', str(Path.home() / '.cache'))
39
+ xdg_log_dir = Path(cache_dir) / 'siada-cli' / 'logs'
40
+ if _ensure_log_dir(xdg_log_dir):
41
+ return str(xdg_log_dir)
42
+
43
+ # 4. System temp directory
44
+ temp_log_dir = Path(tempfile.gettempdir()) / 'siada-cli' / 'logs'
45
+ if _ensure_log_dir(temp_log_dir):
46
+ return str(temp_log_dir)
47
+
48
+ # 5. Fallback: current directory
49
+ fallback_log_dir = Path('./logs')
50
+ _ensure_log_dir(fallback_log_dir)
51
+ return str(fallback_log_dir)
52
+
53
+
54
+ def _is_development_mode():
55
+ """
56
+ Detect if running in development mode by checking environment variables:
57
+ - SIADAHUB_ENV=development
58
+ - SIADA_ENV=dev/development
59
+ - DEVELOPMENT=true/1/yes
60
+ """
61
+ # Check specific environment variables
62
+ env_vars = [
63
+ ('SIADAHUB_ENV', ['development', 'dev']),
64
+ ('SIADA_ENV', ['development', 'dev']),
65
+ ('DEVELOPMENT', ['true', '1', 'yes']),
66
+ ]
67
+
68
+ for env_var, valid_values in env_vars:
69
+ env_value = os.getenv(env_var, '').lower()
70
+ if env_value in valid_values:
71
+ return True
72
+
73
+ return False
74
+
75
+
76
+ def _get_development_log_dir():
77
+ """Get development mode log directory - try current working directory first"""
78
+ try:
79
+ # Try current working directory first (for project root)
80
+ current_dir = Path.cwd()
81
+ if (current_dir / 'pyproject.toml').exists():
82
+ return str(current_dir / 'logs')
83
+
84
+ # Fallback: use module location to find project root
85
+ module_file = Path(__file__).resolve()
86
+ project_root = module_file.parent.parent.parent
87
+ if (project_root / 'pyproject.toml').exists():
88
+ return str(project_root / 'logs')
89
+
90
+ except Exception:
91
+ pass
92
+
93
+ return None
94
+
95
+
96
+ def _ensure_log_dir(log_dir: Path) -> bool:
97
+ """Ensure log directory exists and is writable, return success status"""
98
+ try:
99
+ log_dir.mkdir(parents=True, exist_ok=True)
100
+ # Test write permissions
101
+ test_file = log_dir / '.write_test'
102
+ test_file.touch()
103
+ test_file.unlink()
104
+ return True
105
+ except (PermissionError, OSError):
106
+ return False
107
+
108
+
109
+ # Get log directory and file path
110
+ log_dir = get_log_directory()
111
+ log_file = os.path.join(log_dir, 'siada_api.log')
112
+
113
+
114
+ DEBUG = os.getenv('DEBUG', 'False').lower() in ['true', '1', 'yes']
115
+ if DEBUG:
116
+ LOG_LEVEL = 'DEBUG'
117
+
118
+ ColorType = Literal[
119
+ 'red',
120
+ 'green',
121
+ 'yellow',
122
+ 'blue',
123
+ 'magenta',
124
+ 'cyan',
125
+ 'light_grey',
126
+ 'dark_grey',
127
+ 'light_red',
128
+ 'light_green',
129
+ 'light_yellow',
130
+ 'light_blue',
131
+ 'light_magenta',
132
+ 'light_cyan',
133
+ 'white',
134
+ ]
135
+
136
+ LOG_COLORS: Mapping[str, ColorType] = {
137
+ 'ACTION': 'green',
138
+ 'USER_ACTION': 'light_yellow',
139
+ 'OBSERVATION': 'yellow',
140
+ 'USER_OBSERVATION': 'light_green',
141
+ 'DETAIL': 'cyan',
142
+ 'ERROR': 'red',
143
+ 'PLAN': 'light_magenta',
144
+ 'OUTPUT': 'light_blue',
145
+ 'MESSAGE': 'green',
146
+ }
147
+
148
+
149
+ def format_log_line(time_str, msg_type, msg, use_color=False):
150
+ separator = "*************" * 2
151
+ if use_color:
152
+ separator = colored(separator, 'blue', force_color=True)
153
+ return f"\n{separator}\n{time_str} - {msg_type}\n{msg}"
154
+
155
+
156
+ class ColoredFormatter(logging.Formatter):
157
+ def format(self, record):
158
+ msg_type = record.__dict__.get('msg_type')
159
+ event_source = record.__dict__.get('event_source')
160
+ if event_source:
161
+ new_msg_type = f'{event_source.upper()}_{msg_type}'
162
+ if new_msg_type in LOG_COLORS:
163
+ msg_type = new_msg_type
164
+ if msg_type in LOG_COLORS:
165
+ msg_type_color = colored(msg_type, LOG_COLORS[msg_type], force_color=True)
166
+ msg = colored(record.msg, LOG_COLORS[msg_type], force_color=True)
167
+ time_str = colored(
168
+ self.formatTime(record, self.datefmt), LOG_COLORS[msg_type], force_color=True
169
+ )
170
+ name_str = colored(record.name, LOG_COLORS[msg_type], force_color=True)
171
+ level_str = colored(record.levelname, LOG_COLORS[msg_type], force_color=True)
172
+ if msg_type in ['ERROR'] or DEBUG:
173
+ return f'{time_str} - {name_str}:{level_str}: {record.filename}:{record.lineno}\n{msg_type_color}\n{msg}'
174
+ return format_log_line(time_str, msg_type_color, msg, use_color=True)
175
+ elif msg_type == 'STEP':
176
+ msg = '\n\n==============\n' + record.msg + '\n'
177
+ return f'{msg}'
178
+ return super().format(record)
179
+
180
+
181
+ class FileFormatter(logging.Formatter):
182
+ def format(self, record):
183
+ msg_type = record.__dict__.get('msg_type')
184
+ event_source = record.__dict__.get('event_source')
185
+
186
+ # Handle event source prefix
187
+ if event_source and msg_type:
188
+ msg_type = f'{event_source.upper()}_{msg_type}'
189
+
190
+ if msg_type:
191
+ msg = record.msg
192
+ time_str = self.formatTime(record, self.datefmt)
193
+ name_str = record.name
194
+ level_str = record.levelname
195
+
196
+ # Handle error or debug info with more details
197
+ if msg_type == 'ERROR' or DEBUG:
198
+ return f'{time_str} - {name_str}:{level_str}: {record.filename}:{record.lineno}\n{msg_type}\n{msg}'
199
+
200
+ # Normal message
201
+ return format_log_line(time_str, msg_type, msg)
202
+
203
+ elif msg_type == 'STEP':
204
+ msg = '\n\n==============\n' + record.msg + '\n'
205
+ return f'{msg}'
206
+
207
+ return super().format(record)
208
+
209
+
210
+ file_formatter = logging.Formatter(
211
+ '%(asctime)s - %(name)s:%(levelname)s: %(filename)s:%(lineno)s - %(message)s',
212
+ datefmt='%H:%M:%S',
213
+ )
214
+ llm_formatter = logging.Formatter('%(message)s')
215
+
216
+
217
+ def get_file_handler():
218
+ file_handler = TimedRotatingFileHandler(
219
+ log_file,
220
+ when='midnight',
221
+ interval=1,
222
+ backupCount=30,
223
+ encoding='utf-8'
224
+ )
225
+ file_handler.setLevel(logging.INFO)
226
+ formatter_str = '%(asctime)s - %(name)s:%(levelname)s: %(filename)s:%(lineno)s - %(message)s'
227
+ file_handler.setFormatter(FileFormatter(formatter_str, datefmt='%H:%M:%S'))
228
+ return file_handler
229
+
230
+
231
+ def get_console_handler(log_level=logging.INFO, extra_info: Optional[str] = None):
232
+ """Returns a console handler for logging."""
233
+ console_handler = logging.StreamHandler()
234
+ console_handler.setLevel(log_level)
235
+ formatter_str = '\033[92m%(asctime)s - %(name)s:%(levelname)s\033[0m: %(filename)s:%(lineno)s - %(message)s'
236
+ if extra_info:
237
+ formatter_str = f'{extra_info} - ' + formatter_str
238
+ console_handler.setFormatter(ColoredFormatter(formatter_str, datefmt='%H:%M:%S'))
239
+ return console_handler
240
+
241
+
242
+ def configure_third_party_loggers():
243
+ """
244
+ Configure third-party library log levels to reduce verbose log output
245
+ """
246
+ # Set httpx log level to ERROR to avoid excessive network request logs
247
+ logging.getLogger('httpx').setLevel(logging.ERROR)
248
+
249
+ # Add other third-party library log configurations as needed
250
+ # logging.getLogger('urllib3').setLevel(logging.WARNING)
251
+ # logging.getLogger('requests').setLevel(logging.WARNING)
252
+
253
+
254
+ def setup_logger():
255
+ """
256
+ Setup and return siada.api logger
257
+ """
258
+ # Create logger
259
+ setup_logger = logging.getLogger('siada.api')
260
+ setup_logger.setLevel(logging.INFO)
261
+
262
+ # If logger already has handlers, don't add duplicates
263
+ if setup_logger.handlers:
264
+ return setup_logger
265
+
266
+ # Create console handler
267
+ console_handler = get_console_handler()
268
+ # Create file handler - rotate daily, keep 30 days of logs
269
+ file_handler = get_file_handler()
270
+
271
+ # Add handlers to logger
272
+ setup_logger.addHandler(console_handler)
273
+ setup_logger.addHandler(file_handler)
274
+ setup_logger.propagate = False
275
+
276
+ # Configure third-party library log levels
277
+ configure_third_party_loggers()
278
+
279
+ return setup_logger
280
+
281
+
282
+
283
+ def remove_console_handler(target_logger=None):
284
+ """
285
+ Remove console handler from logger, keeping only file handler
286
+
287
+ Args:
288
+ target_logger: Logger instance to modify. If None, uses the global logger.
289
+ """
290
+ if target_logger is None:
291
+ target_logger = logging.getLogger('siada.api')
292
+
293
+ # Find and remove console handlers
294
+ handlers_to_remove = []
295
+ for handler in target_logger.handlers:
296
+ if isinstance(handler, logging.StreamHandler) and not isinstance(handler, TimedRotatingFileHandler):
297
+ handlers_to_remove.append(handler)
298
+
299
+ for handler in handlers_to_remove:
300
+ target_logger.removeHandler(handler)
301
+ handler.close()
302
+
303
+
304
+ def add_console_handler(target_logger=None, log_level=logging.INFO):
305
+ """
306
+ Add console handler back to logger
307
+
308
+ Args:
309
+ target_logger: Logger instance to modify. If None, uses the global logger.
310
+ log_level: Log level for console handler
311
+ """
312
+ if target_logger is None:
313
+ target_logger = logging.getLogger('siada.api')
314
+
315
+ # Check if console handler already exists
316
+ has_console_handler = any(
317
+ isinstance(handler, logging.StreamHandler) and not isinstance(handler, TimedRotatingFileHandler)
318
+ for handler in target_logger.handlers
319
+ )
320
+
321
+ if not has_console_handler:
322
+ console_handler = get_console_handler(log_level)
323
+ target_logger.addHandler(console_handler)
324
+
325
+
326
+ def toggle_console_output(enable: bool = True, target_logger=None):
327
+ """
328
+ Toggle console output on/off
329
+
330
+ Args:
331
+ enable: True to enable console output, False to disable
332
+ target_logger: Logger instance to modify. If None, uses the global logger.
333
+ """
334
+ if enable:
335
+ add_console_handler(target_logger)
336
+ else:
337
+ remove_console_handler(target_logger)
338
+
339
+
340
+ # Global accessible logger instance
341
+ logger = setup_logger()
File without changes
@@ -0,0 +1,105 @@
1
+ import subprocess
2
+ import re
3
+ from typing import List, Optional
4
+ from pathlib import Path
5
+ import os
6
+
7
+ class GitDiffUtil:
8
+ """工具类,用于获取 Git 仓库的 diff 信息,排除测试文件。"""
9
+
10
+ @staticmethod
11
+ def get_git_diff(repo_path: str = ".") -> str:
12
+ """
13
+ 获取当前 Git 仓库的完整 diff。
14
+
15
+ Args:
16
+ repo_path: Git 仓库的路径,默认为当前目录。
17
+
18
+ Returns:
19
+ str: 返回完整的 diff 信息。
20
+ """
21
+ if repo_path == ".":
22
+ repo_path = Path.cwd()
23
+ else:
24
+ repo_path = Path(repo_path).resolve()
25
+
26
+ if not (repo_path / ".git").exists():
27
+ raise ValueError(f"Git diff failed: Path {repo_path} is not a valid Git repository.")
28
+
29
+ cmd = ['git', 'diff']
30
+ try:
31
+ result = subprocess.run(
32
+ cmd,
33
+ cwd=repo_path,
34
+ capture_output=True,
35
+ text=True,
36
+ check=True
37
+ )
38
+ return result.stdout
39
+ except subprocess.CalledProcessError as e:
40
+ raise Exception(f"Git diff failed: {e.stderr}")
41
+
42
+ @staticmethod
43
+ def get_git_diff_exclude_test_files(repo_path: str = ".") -> str:
44
+ """
45
+ 获取当前 Git 仓库的完整 diff,排除测试文件。
46
+
47
+ Args:
48
+ repo_path: Git 仓库的路径,默认为当前目录。
49
+
50
+ Returns:
51
+ str: 返回排除测试文件后的 diff 信息。
52
+ """
53
+ if repo_path == ".":
54
+ repo_path = Path.cwd()
55
+ else:
56
+ repo_path = Path(repo_path).resolve()
57
+ print(f"try to get git diff from {repo_path}\n")
58
+ if not (repo_path / ".git").exists():
59
+ git_dir = GitDiffUtil._find_git_root(repo_path)
60
+ if git_dir:
61
+ repo_path = git_dir
62
+ else:
63
+ raise ValueError(f"Git diff failed: Path {repo_path} is not a valid Git repository.")
64
+
65
+ cmd = ['git', 'diff', '--', ':(exclude)test*/', ':(exclude)**/*test*']
66
+ try:
67
+ result = subprocess.run(
68
+ cmd,
69
+ cwd=repo_path,
70
+ capture_output=True,
71
+ text=True,
72
+ check=True
73
+ )
74
+ return result.stdout
75
+ except subprocess.CalledProcessError as e:
76
+ raise Exception(f"Git diff failed: {e.stderr}")
77
+
78
+
79
+ @staticmethod
80
+ def _find_git_root(path: Path) -> Optional[Path]:
81
+ """
82
+ 向上查找 Git 仓库根目录
83
+ Args:
84
+ path: 起始查找路径
85
+ Returns:
86
+ Git 仓库根目录路径,如果未找到则返回 None
87
+ """
88
+ current = path.resolve()
89
+
90
+ while current != current.parent:
91
+ if (current / '.git').exists():
92
+ return current
93
+ current = current.parent
94
+
95
+ return None
96
+
97
+ if __name__ == "__main__":
98
+ # from siada.foundation.tools.get_git_diff import GitDiffUtil
99
+ # 示例用法
100
+ try:
101
+ diff = GitDiffUtil.get_git_diff_exclude_test_files(repo_path="/Users/caoxin/Projects/AgentHub/siada-agenthub")
102
+ print("Git diff (excluding test files):")
103
+ print(diff)
104
+ except Exception as e:
105
+ print(f"Error: {e}")
siada/io/__init__.py ADDED
File without changes
siada/io/banner.py ADDED
@@ -0,0 +1,148 @@
1
+ """
2
+ ASCII Art Banner Display Module for Siada CLI
3
+
4
+ Provides colorful banner display with gradient effects and fallback for non-pretty terminals.
5
+ """
6
+
7
+ from rich.text import Text
8
+ from rich.console import Console
9
+
10
+
11
+ class BannerDisplay:
12
+ """Handle ASCII art banner display with color gradients."""
13
+
14
+ # ASCII art for SIADA CLI
15
+ BANNER_LINES = [
16
+ " ███████╗██╗ █████╗ ██████╗ █████╗ ██████╗██╗ ██╗",
17
+ " ██╔════╝██║██╔══██╗██╔══██╗██╔══██╗ ██╔════╝██║ ██║",
18
+ " ███████╗██║███████║██║ ██║███████║ ██║ ██║ ██║",
19
+ " ╚════██║██║██╔══██║██║ ██║██╔══██║ ██║ ██║ ██║",
20
+ " ███████║██║██║ ██║██████╔╝██║ ██║ ╚██████╗███████╗██║",
21
+ " ╚══════╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝"
22
+ ]
23
+
24
+ # Color gradient from left to right
25
+ GRADIENT_COLORS = [
26
+ "bold blue",
27
+ "bold bright_blue",
28
+ "bold cyan",
29
+ "bold bright_cyan",
30
+ "bold green",
31
+ "bold bright_green",
32
+ "bold yellow",
33
+ "bold bright_yellow"
34
+ ]
35
+
36
+ @classmethod
37
+ def show_banner(cls, pretty: bool = True, console: Console = None):
38
+ """
39
+ Display the SIADA CLI banner.
40
+
41
+ Args:
42
+ pretty: Whether to use colorful output
43
+ console: Rich console instance (optional)
44
+ """
45
+ if pretty:
46
+ try:
47
+ cls._show_pretty_banner(console)
48
+ except Exception:
49
+ # Fallback to plain banner if rich output fails
50
+ cls._show_plain_banner()
51
+ else:
52
+ cls._show_plain_banner()
53
+
54
+ @classmethod
55
+ def _show_pretty_banner(cls, console: Console = None):
56
+ """Show colorful banner with left-to-right gradient."""
57
+ if console is None:
58
+ console = Console()
59
+
60
+ banner = Text()
61
+
62
+ for line in cls.BANNER_LINES:
63
+ # Calculate smooth gradient across the line
64
+ line_length = len(line.rstrip()) # Remove trailing spaces for better gradient
65
+ if line_length == 0:
66
+ banner.append(line + "\n")
67
+ continue
68
+
69
+ # Create smoother gradient distribution
70
+ for i, char in enumerate(line):
71
+ if char.isspace() and i >= line_length:
72
+ # Keep trailing spaces uncolored
73
+ banner.append(char)
74
+ else:
75
+ # Calculate gradient position (0.0 to 1.0)
76
+ gradient_pos = i / max(1, line_length - 1) if line_length > 1 else 0
77
+ # Map to color index with smooth transition
78
+ color_index = min(int(gradient_pos * len(cls.GRADIENT_COLORS)),
79
+ len(cls.GRADIENT_COLORS) - 1)
80
+ color = cls.GRADIENT_COLORS[color_index]
81
+ banner.append(char, style=color)
82
+
83
+ banner.append("\n")
84
+
85
+ console.print(banner)
86
+
87
+ @classmethod
88
+ def _show_plain_banner(cls):
89
+ """Show plain text banner for non-pretty terminals."""
90
+ print()
91
+ try:
92
+ for line in cls.BANNER_LINES:
93
+ print(line)
94
+ except UnicodeEncodeError:
95
+ # Fallback to ASCII-only banner if Unicode fails
96
+ cls._show_ascii_fallback_banner()
97
+ print()
98
+
99
+ @classmethod
100
+ def _show_ascii_fallback_banner(cls):
101
+ """Show ASCII-only fallback banner."""
102
+ ascii_banner = [
103
+ " ===== SIADA CLI =====",
104
+ " S I A D A C L I",
105
+ " ====================="
106
+ ]
107
+ for line in ascii_banner:
108
+ print(line)
109
+
110
+ @classmethod
111
+ def get_simple_banner(cls) -> str:
112
+ """
113
+ Get a simple text version of the banner.
114
+
115
+ Returns:
116
+ Simple ASCII text banner
117
+ """
118
+ return "\n".join([
119
+ "",
120
+ " ███████╗██╗ █████╗ ██████╗ █████╗ ██████╗██╗ ██╗",
121
+ " ██╔════╝██║██╔══██╗██╔══██╗██╔══██╗ ██╔════╝██║ ██║",
122
+ " ███████╗██║███████║██║ ██║███████║ ██║ ██║ ██║",
123
+ " ╚════██║██║██╔══██║██║ ██║██╔══██║ ██║ ██║ ██║",
124
+ " ███████║██║██║ ██║██████╔╝██║ ██║ ╚██████╗███████╗██║",
125
+ " ╚══════╝╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝",
126
+ ""
127
+ ])
128
+
129
+
130
+ def show_siada_banner(pretty: bool = True, console: Console = None):
131
+ """
132
+ Convenience function to display SIADA CLI banner.
133
+
134
+ Args:
135
+ pretty: Whether to use colorful output
136
+ console: Rich console instance (optional)
137
+ """
138
+ BannerDisplay.show_banner(pretty, console)
139
+
140
+
141
+ def get_banner_text() -> str:
142
+ """
143
+ Get the plain text version of the banner.
144
+
145
+ Returns:
146
+ String containing the ASCII art banner
147
+ """
148
+ return BannerDisplay.get_simple_banner()