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.
- agent_config.yaml +34 -0
- siada/__init__.py +13 -0
- siada/agent_hub/__init__.py +10 -0
- siada/agent_hub/coder/bug_fix_agent.py +398 -0
- siada/agent_hub/coder/code_gen_agent.py +235 -0
- siada/agent_hub/coder/fe_gen_agent.py +37 -0
- siada/agent_hub/coder/issue_review_agent.py +68 -0
- siada/agent_hub/coder/prompt/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/__init__.py +0 -0
- siada/agent_hub/coder/prompt/base/capabilities.py +18 -0
- siada/agent_hub/coder/prompt/base/non_interactive.py +14 -0
- siada/agent_hub/coder/prompt/base/prompt_builder.py +33 -0
- siada/agent_hub/coder/prompt/base/rules.py +40 -0
- siada/agent_hub/coder/prompt/base/tool_use.py +14 -0
- siada/agent_hub/coder/prompt/bug_prompt/bug_fix_prompt.py +51 -0
- siada/agent_hub/coder/prompt/bug_prompt/rules.py +53 -0
- siada/agent_hub/coder/prompt/code_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/fe_gen_prompt.py +45 -0
- siada/agent_hub/coder/prompt/issue_review_prompt.py +121 -0
- siada/agent_hub/coder/tracing/__init__.py +22 -0
- siada/agent_hub/coder/tracing/logger_tracing_processor.py +458 -0
- siada/agent_hub/hooks/__init__.py +0 -0
- siada/agent_hub/hooks/io_hooks.py +30 -0
- siada/agent_hub/siada_agent.py +214 -0
- siada/config/__init__.py +0 -0
- siada/config/config_loader.py +67 -0
- siada/entrypoint/__init__.py +3 -0
- siada/entrypoint/args_parser/__init__.py +0 -0
- siada/entrypoint/args_parser/args.py +195 -0
- siada/entrypoint/interaction/__init__.py +0 -0
- siada/entrypoint/interaction/base_turn.py +107 -0
- siada/entrypoint/interaction/config.py +30 -0
- siada/entrypoint/interaction/controller.py +122 -0
- siada/entrypoint/interaction/nointeractive_controller.py +27 -0
- siada/entrypoint/interaction/turn/interface.py +116 -0
- siada/entrypoint/interaction/turn/models.py +34 -0
- siada/entrypoint/interaction/turn/run_turn.py +641 -0
- siada/entrypoint/interaction/turn_factory.py +35 -0
- siada/entrypoint/interaction/turn_models.py +28 -0
- siada/entrypoint/siadahub.py +413 -0
- siada/foundation/__init__.py +0 -0
- siada/foundation/code_agent_context.py +48 -0
- siada/foundation/config.py +59 -0
- siada/foundation/constants.py +2 -0
- siada/foundation/context.py +76 -0
- siada/foundation/logging.py +341 -0
- siada/foundation/tools/__init__.py +0 -0
- siada/foundation/tools/get_git_diff.py +105 -0
- siada/io/__init__.py +0 -0
- siada/io/banner.py +148 -0
- siada/io/color_settings.py +207 -0
- siada/io/color_utils.py +16 -0
- siada/io/components/__init__.py +0 -0
- siada/io/components/mdstream.py +204 -0
- siada/io/console_printer.py +68 -0
- siada/io/io.py +661 -0
- siada/io/key_bindings.py +129 -0
- siada/io/notification_command.py +40 -0
- siada/io/test_banner.py +64 -0
- siada/models/__init__.py +0 -0
- siada/models/agent.py +47 -0
- siada/models/coder/__init__.py +0 -0
- siada/models/converter.py +27 -0
- siada/models/model_base_config.py +121 -0
- siada/models/model_run_config.py +153 -0
- siada/provider/__init__.py +0 -0
- siada/provider/client_factory.py +112 -0
- siada/provider/llm_client.py +11 -0
- siada/provider/openrouter/__init__.py +0 -0
- siada/provider/openrouter/coverter.py +17 -0
- siada/provider/openrouter/openroute_provider.py +33 -0
- siada/provider/provider_factory.py +53 -0
- siada/queries/tree-sitter-language-pack/README.md +7 -0
- siada/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- siada/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- siada/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- siada/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- siada/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- siada/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- siada/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- siada/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- siada/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- siada/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- siada/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- siada/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- siada/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- siada/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- siada/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- siada/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- siada/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- siada/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- siada/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- siada/queries/tree-sitter-languages/README.md +23 -0
- siada/queries/tree-sitter-languages/c-tags.scm +9 -0
- siada/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- siada/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- siada/queries/tree-sitter-languages/dart-tags.scm +91 -0
- siada/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- siada/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- siada/queries/tree-sitter-languages/elm-tags.scm +19 -0
- siada/queries/tree-sitter-languages/go-tags.scm +30 -0
- siada/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- siada/queries/tree-sitter-languages/java-tags.scm +20 -0
- siada/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- siada/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- siada/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- siada/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- siada/queries/tree-sitter-languages/php-tags.scm +26 -0
- siada/queries/tree-sitter-languages/python-tags.scm +12 -0
- siada/queries/tree-sitter-languages/ql-tags.scm +26 -0
- siada/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- siada/queries/tree-sitter-languages/rust-tags.scm +60 -0
- siada/queries/tree-sitter-languages/scala-tags.scm +65 -0
- siada/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- siada/services/__init__.py +30 -0
- siada/services/at_file_recommendation_design_document.md +1213 -0
- siada/services/code_context_manager.py +136 -0
- siada/services/enhanced_fix_result_check.py +546 -0
- siada/services/execution_trace_collector.py +207 -0
- siada/services/file_recommendation/__init__.py +136 -0
- siada/services/file_recommendation/core/__init__.py +17 -0
- siada/services/file_recommendation/core/completion_engine.py +351 -0
- siada/services/file_recommendation/core/config.py +62 -0
- siada/services/file_recommendation/core/file_discovery.py +353 -0
- siada/services/file_recommendation/core/suggestion.py +110 -0
- siada/services/file_recommendation/utils/__init__.py +22 -0
- siada/services/file_recommendation/utils/path_utils.py +138 -0
- siada/services/file_recommendation/utils/text_utils.py +253 -0
- siada/services/fix_result_check.py +377 -0
- siada/services/handle_at_command/README.md +247 -0
- siada/services/handle_at_command/__init__.py +115 -0
- siada/services/handle_at_command/exceptions.py +46 -0
- siada/services/handle_at_command/models.py +92 -0
- siada/services/handle_at_command/parser.py +189 -0
- siada/services/handle_at_command/processor.py +321 -0
- siada/services/handle_at_command/resolver.py +318 -0
- siada/services/handle_at_command/utils.py +345 -0
- siada/services/model_info_service.py +103 -0
- siada/services/siada_runner.py +170 -0
- siada/session/__init__.py +29 -0
- siada/session/session_manager.py +66 -0
- siada/session/session_models.py +43 -0
- siada/support/__init__.py +0 -0
- siada/support/completer.py +204 -0
- siada/support/editor.py +146 -0
- siada/support/envprocessor.py +54 -0
- siada/support/repo.py +51 -0
- siada/support/slash_commands.py +410 -0
- siada/support/spinner.py +320 -0
- siada/tools/__init__.py +0 -0
- siada/tools/ast/README.md +345 -0
- siada/tools/ast/ast_tool.py +341 -0
- siada/tools/ast/models.py +11 -0
- siada/tools/coder/__init__.py +0 -0
- siada/tools/coder/ask_followup_question.py +70 -0
- siada/tools/coder/cmd_runner.py +132 -0
- siada/tools/coder/file_operator.py +284 -0
- siada/tools/coder/file_search/README.md +223 -0
- siada/tools/coder/file_search/__init__.py +10 -0
- siada/tools/coder/file_search/bin/rg-linux-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-linux-x64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-arm64 +0 -0
- siada/tools/coder/file_search/bin/rg-macos-x64 +0 -0
- siada/tools/coder/file_search/bin/rg.exe +0 -0
- siada/tools/coder/file_search/search.py +437 -0
- siada/tools/coder/file_search/setup_package.py +150 -0
- siada/tools/coder/file_search/test_search.py +142 -0
- siada/tools/coder/files.py +147 -0
- siada/tools/coder/fix_attempt_completion.py +58 -0
- siada/tools/coder/issue_review_completion.py +29 -0
- siada/tools/coder/observation/__init__.py +0 -0
- siada/tools/coder/observation/error.py +22 -0
- siada/tools/coder/observation/file_observation.py +216 -0
- siada/tools/coder/observation/observation.py +71 -0
- siada/tools/coder/repo_map/__init__.py +157 -0
- siada/tools/coder/repo_map/dump.py +29 -0
- siada/tools/coder/repo_map/io.py +226 -0
- siada/tools/coder/repo_map/repo_map.py +881 -0
- siada/tools/coder/repo_map/special.py +203 -0
- siada/tools/coder/repo_map/token_counter.py +154 -0
- siada/tools/coder/repo_map/waiting.py +221 -0
- siada/tools/coder/reproduce_completion.py +39 -0
- siada/tools/coder/run_cmd.py +60 -0
- siada/tools/coder/test_completion.py +25 -0
- siada/tools/coder/tool_docs.py +44 -0
- siada/tools/compression_tool.py +200 -0
- siada/tools/read_many_files/README.md +220 -0
- siada/tools/read_many_files/__init__.py +25 -0
- siada/tools/read_many_files/file_processor.py +486 -0
- siada/tools/read_many_files/filters.py +326 -0
- siada/tools/read_many_files/formatters.py +204 -0
- siada/tools/read_many_files/models.py +189 -0
- siada/tools/read_many_files_tool.py +233 -0
- siada/tools/tool_call_format/__init__.py +55 -0
- siada/tools/tool_call_format/file_to_language.py +231 -0
- siada/tools/tool_call_format/formatter_factory.py +80 -0
- siada/tools/tool_call_format/formatters.py +282 -0
- siada/tools/tool_call_format/tool_call_formatter.py +54 -0
- siada/tools/web/__init__.py +0 -0
- siada/tools/web/web_crawl.py +208 -0
- siada/utils.py +194 -0
- siada_cli-0.0.1.dist-info/LICENSE +201 -0
- siada_cli-0.0.1.dist-info/METADATA +457 -0
- siada_cli-0.0.1.dist-info/RECORD +215 -0
- siada_cli-0.0.1.dist-info/WHEEL +4 -0
- 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()
|