janito 1.8.1__py3-none-any.whl → 1.10.0__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.
- janito/__init__.py +1 -1
- janito/agent/api_exceptions.py +4 -0
- janito/agent/config.py +1 -1
- janito/agent/config_defaults.py +2 -3
- janito/agent/config_utils.py +0 -9
- janito/agent/conversation.py +177 -114
- janito/agent/conversation_api.py +179 -159
- janito/agent/conversation_tool_calls.py +11 -8
- janito/agent/llm_conversation_history.py +70 -0
- janito/agent/openai_client.py +44 -21
- janito/agent/openai_schema_generator.py +164 -128
- janito/agent/platform_discovery.py +134 -77
- janito/agent/profile_manager.py +5 -5
- janito/agent/rich_message_handler.py +80 -31
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +9 -8
- janito/agent/test_openai_schema_generator.py +93 -0
- janito/agent/tool_base.py +7 -2
- janito/agent/tool_executor.py +63 -50
- janito/agent/tool_registry.py +5 -2
- janito/agent/tool_use_tracker.py +42 -5
- janito/agent/tools/__init__.py +13 -12
- janito/agent/tools/create_directory.py +9 -6
- janito/agent/tools/create_file.py +35 -54
- janito/agent/tools/delete_text_in_file.py +97 -0
- janito/agent/tools/fetch_url.py +50 -5
- janito/agent/tools/find_files.py +40 -26
- janito/agent/tools/get_file_outline/__init__.py +1 -0
- janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +14 -18
- janito/agent/tools/get_file_outline/python_outline.py +134 -0
- janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +11 -0
- janito/agent/tools/get_lines.py +21 -12
- janito/agent/tools/move_file.py +13 -12
- janito/agent/tools/present_choices.py +3 -1
- janito/agent/tools/python_command_runner.py +150 -0
- janito/agent/tools/python_file_runner.py +148 -0
- janito/agent/tools/python_stdin_runner.py +154 -0
- janito/agent/tools/remove_directory.py +4 -2
- janito/agent/tools/remove_file.py +15 -13
- janito/agent/tools/replace_file.py +72 -0
- janito/agent/tools/replace_text_in_file.py +7 -5
- janito/agent/tools/run_bash_command.py +29 -72
- janito/agent/tools/run_powershell_command.py +142 -102
- janito/agent/tools/search_text.py +177 -131
- janito/agent/tools/validate_file_syntax/__init__.py +1 -0
- janito/agent/tools/validate_file_syntax/core.py +94 -0
- janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
- janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
- janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
- janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
- janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
- janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
- janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/agent/tools_utils/__init__.py +1 -0
- janito/agent/tools_utils/action_type.py +7 -0
- janito/agent/tools_utils/dir_walk_utils.py +24 -0
- janito/agent/tools_utils/formatting.py +49 -0
- janito/agent/tools_utils/gitignore_utils.py +69 -0
- janito/agent/tools_utils/test_gitignore_utils.py +46 -0
- janito/agent/tools_utils/utils.py +30 -0
- janito/cli/_livereload_log_utils.py +13 -0
- janito/cli/_print_config.py +63 -61
- janito/cli/arg_parser.py +57 -14
- janito/cli/cli_main.py +270 -0
- janito/cli/livereload_starter.py +60 -0
- janito/cli/main.py +166 -99
- janito/cli/one_shot.py +80 -0
- janito/cli/termweb_starter.py +2 -2
- janito/i18n/__init__.py +1 -1
- janito/livereload/app.py +25 -0
- janito/rich_utils.py +41 -25
- janito/{cli_chat_shell → shell}/commands/__init__.py +19 -14
- janito/{cli_chat_shell → shell}/commands/config.py +4 -4
- janito/shell/commands/conversation_restart.py +74 -0
- janito/shell/commands/edit.py +24 -0
- janito/shell/commands/history_view.py +18 -0
- janito/{cli_chat_shell → shell}/commands/lang.py +3 -0
- janito/shell/commands/livelogs.py +42 -0
- janito/{cli_chat_shell → shell}/commands/prompt.py +16 -6
- janito/shell/commands/session.py +35 -0
- janito/{cli_chat_shell → shell}/commands/session_control.py +3 -5
- janito/{cli_chat_shell → shell}/commands/termweb_log.py +18 -10
- janito/shell/commands/tools.py +26 -0
- janito/shell/commands/track.py +36 -0
- janito/shell/commands/utility.py +28 -0
- janito/{cli_chat_shell → shell}/commands/verbose.py +4 -5
- janito/shell/commands.py +40 -0
- janito/shell/input_history.py +62 -0
- janito/shell/main.py +257 -0
- janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
- janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
- janito/shell/session/manager.py +101 -0
- janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -17
- janito/termweb/app.py +3 -3
- janito/termweb/static/editor.css +142 -0
- janito/termweb/static/editor.css.bak +27 -0
- janito/termweb/static/editor.html +15 -213
- janito/termweb/static/editor.html.bak +16 -215
- janito/termweb/static/editor.js +209 -0
- janito/termweb/static/editor.js.bak +227 -0
- janito/termweb/static/index.html +2 -3
- janito/termweb/static/index.html.bak +2 -3
- janito/termweb/static/termweb.css.bak +33 -84
- janito/termweb/static/termweb.js +15 -34
- janito/termweb/static/termweb.js.bak +18 -36
- janito/tests/test_rich_utils.py +44 -0
- janito/web/app.py +0 -75
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/METADATA +62 -42
- janito-1.10.0.dist-info/RECORD +158 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/WHEEL +1 -1
- janito/agent/tools/dir_walk_utils.py +0 -16
- janito/agent/tools/gitignore_utils.py +0 -46
- janito/agent/tools/memory.py +0 -48
- janito/agent/tools/outline_file/formatting.py +0 -20
- janito/agent/tools/outline_file/python_outline.py +0 -71
- janito/agent/tools/present_choices_test.py +0 -18
- janito/agent/tools/rich_live.py +0 -44
- janito/agent/tools/run_python_command.py +0 -163
- janito/agent/tools/tools_utils.py +0 -56
- janito/agent/tools/utils.py +0 -33
- janito/agent/tools/validate_file_syntax.py +0 -163
- janito/cli/runner/cli_main.py +0 -180
- janito/cli_chat_shell/chat_loop.py +0 -163
- janito/cli_chat_shell/chat_state.py +0 -38
- janito/cli_chat_shell/commands/history_start.py +0 -37
- janito/cli_chat_shell/commands/session.py +0 -48
- janito/cli_chat_shell/commands/sum.py +0 -49
- janito/cli_chat_shell/commands/utility.py +0 -32
- janito/cli_chat_shell/session_manager.py +0 -72
- janito-1.8.1.dist-info/RECORD +0 -127
- /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
- /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
- /janito/cli/{runner/config.py → config_runner.py} +0 -0
- /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
- /janito/{cli/runner → shell}/__init__.py +0 -0
- /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
- /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
- /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/entry_points.txt +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/top_level.txt +0 -0
@@ -1,18 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Test for present_choices tool.
|
3
|
-
|
4
|
-
Note: This test is for manual/interactive verification only, as prompt_toolkit dialogs require user interaction.
|
5
|
-
"""
|
6
|
-
|
7
|
-
from present_choices import present_choices
|
8
|
-
|
9
|
-
if __name__ == "__main__":
|
10
|
-
prompt = "Select your favorite fruits:"
|
11
|
-
choices = ["Apple", "Banana", "Cherry", "Date"]
|
12
|
-
print("Single-select test:")
|
13
|
-
selected = present_choices(prompt, choices, multi_select=False)
|
14
|
-
print(f"Selected: {selected}")
|
15
|
-
|
16
|
-
print("\nMulti-select test:")
|
17
|
-
selected_multi = present_choices(prompt, choices, multi_select=True)
|
18
|
-
print(f"Selected: {selected_multi}")
|
janito/agent/tools/rich_live.py
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
from contextlib import contextmanager
|
2
|
-
from rich.live import Live
|
3
|
-
from rich.panel import Panel
|
4
|
-
from rich.console import Console
|
5
|
-
|
6
|
-
console = Console()
|
7
|
-
|
8
|
-
_global_live = None
|
9
|
-
|
10
|
-
|
11
|
-
@contextmanager
|
12
|
-
def global_live_panel(title="Working..."):
|
13
|
-
global _global_live
|
14
|
-
if _global_live is None:
|
15
|
-
_global_live = Live(
|
16
|
-
Panel("", title=title), console=console, refresh_per_second=4
|
17
|
-
)
|
18
|
-
_global_live.start()
|
19
|
-
try:
|
20
|
-
yield _global_live
|
21
|
-
finally:
|
22
|
-
pass # Do not stop here; stopping is handled explicitly
|
23
|
-
|
24
|
-
|
25
|
-
def stop_global_live_panel():
|
26
|
-
global _global_live
|
27
|
-
if _global_live is not None:
|
28
|
-
_global_live.stop()
|
29
|
-
_global_live = None
|
30
|
-
|
31
|
-
|
32
|
-
@contextmanager
|
33
|
-
def live_panel(title="Working..."):
|
34
|
-
global _global_live
|
35
|
-
if _global_live is not None:
|
36
|
-
# Update the global panel content instead of creating a nested panel
|
37
|
-
_global_live.update(Panel("", title=title))
|
38
|
-
yield _global_live
|
39
|
-
else:
|
40
|
-
# Fallback: create a temporary panel if no global panel is running
|
41
|
-
with Live(
|
42
|
-
Panel("", title=title), console=console, refresh_per_second=4
|
43
|
-
) as live:
|
44
|
-
yield live
|
@@ -1,163 +0,0 @@
|
|
1
|
-
import subprocess
|
2
|
-
import tempfile
|
3
|
-
import sys
|
4
|
-
import os
|
5
|
-
from janito.agent.tool_base import ToolBase
|
6
|
-
from janito.agent.tool_registry import register_tool
|
7
|
-
from janito.i18n import tr
|
8
|
-
|
9
|
-
|
10
|
-
@register_tool(name="run_python_command")
|
11
|
-
class RunPythonCommandTool(ToolBase):
|
12
|
-
"""
|
13
|
-
Tool to execute Python code in a subprocess and capture output.
|
14
|
-
Args:
|
15
|
-
code (str): The Python code to execute.
|
16
|
-
timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
|
17
|
-
require_confirmation (bool, optional): If True, require user confirmation before running. Defaults to False.
|
18
|
-
interactive (bool, optional): If True, warns that the command may require user interaction. Defaults to False.
|
19
|
-
Returns:
|
20
|
-
str: File paths and line counts for stdout and stderr, or direct output if small enough.
|
21
|
-
"""
|
22
|
-
|
23
|
-
def run(
|
24
|
-
self,
|
25
|
-
code: str,
|
26
|
-
timeout: int = 60,
|
27
|
-
require_confirmation: bool = False,
|
28
|
-
interactive: bool = False,
|
29
|
-
) -> str:
|
30
|
-
if not code.strip():
|
31
|
-
self.report_warning(
|
32
|
-
tr("⚠️ Warning: Empty code provided. Operation skipped.")
|
33
|
-
)
|
34
|
-
return tr("Warning: Empty code provided. Operation skipped.")
|
35
|
-
self.report_info(tr("🐍 Running Python code: ...\n{code}\n", code=code))
|
36
|
-
if interactive:
|
37
|
-
self.report_info(
|
38
|
-
tr(
|
39
|
-
"⚠️ Warning: This code might be interactive, require user input, and might hang."
|
40
|
-
)
|
41
|
-
)
|
42
|
-
sys.stdout.flush()
|
43
|
-
if require_confirmation:
|
44
|
-
confirmed = self.confirm_action(
|
45
|
-
tr("Do you want to execute this Python code?")
|
46
|
-
)
|
47
|
-
if not confirmed:
|
48
|
-
self.report_warning(tr("Execution cancelled by user."))
|
49
|
-
return tr("Execution cancelled by user.")
|
50
|
-
try:
|
51
|
-
with (
|
52
|
-
tempfile.NamedTemporaryFile(
|
53
|
-
mode="w+",
|
54
|
-
suffix=".py",
|
55
|
-
prefix="run_python_",
|
56
|
-
delete=False,
|
57
|
-
encoding="utf-8",
|
58
|
-
) as code_file,
|
59
|
-
tempfile.NamedTemporaryFile(
|
60
|
-
mode="w+",
|
61
|
-
prefix="run_python_stdout_",
|
62
|
-
delete=False,
|
63
|
-
encoding="utf-8",
|
64
|
-
) as stdout_file,
|
65
|
-
tempfile.NamedTemporaryFile(
|
66
|
-
mode="w+",
|
67
|
-
prefix="run_python_stderr_",
|
68
|
-
delete=False,
|
69
|
-
encoding="utf-8",
|
70
|
-
) as stderr_file,
|
71
|
-
):
|
72
|
-
code_file.write(code)
|
73
|
-
code_file.flush()
|
74
|
-
env = os.environ.copy()
|
75
|
-
env["PYTHONIOENCODING"] = "utf-8"
|
76
|
-
process = subprocess.Popen(
|
77
|
-
[sys.executable, code_file.name],
|
78
|
-
stdout=stdout_file,
|
79
|
-
stderr=stderr_file,
|
80
|
-
text=True,
|
81
|
-
env=env,
|
82
|
-
)
|
83
|
-
try:
|
84
|
-
return_code = process.wait(timeout=timeout)
|
85
|
-
except subprocess.TimeoutExpired:
|
86
|
-
process.kill()
|
87
|
-
self.report_error(
|
88
|
-
tr(" ❌ Timed out after {timeout} seconds.", timeout=timeout)
|
89
|
-
)
|
90
|
-
return tr(
|
91
|
-
"Code timed out after {timeout} seconds.", timeout=timeout
|
92
|
-
)
|
93
|
-
stdout_file.flush()
|
94
|
-
stderr_file.flush()
|
95
|
-
with open(
|
96
|
-
stdout_file.name, "r", encoding="utf-8", errors="replace"
|
97
|
-
) as out_f:
|
98
|
-
out_f.seek(0)
|
99
|
-
for line in out_f:
|
100
|
-
self.report_stdout(line)
|
101
|
-
with open(
|
102
|
-
stderr_file.name, "r", encoding="utf-8", errors="replace"
|
103
|
-
) as err_f:
|
104
|
-
err_f.seek(0)
|
105
|
-
for line in err_f:
|
106
|
-
self.report_stderr(line)
|
107
|
-
with open(
|
108
|
-
stdout_file.name, "r", encoding="utf-8", errors="replace"
|
109
|
-
) as out_f:
|
110
|
-
stdout_lines = sum(1 for _ in out_f)
|
111
|
-
with open(
|
112
|
-
stderr_file.name, "r", encoding="utf-8", errors="replace"
|
113
|
-
) as err_f:
|
114
|
-
stderr_lines = sum(1 for _ in err_f)
|
115
|
-
self.report_success(
|
116
|
-
tr(" ✅ return code {return_code}", return_code=return_code)
|
117
|
-
)
|
118
|
-
warning_msg = ""
|
119
|
-
if interactive:
|
120
|
-
warning_msg = tr(
|
121
|
-
"⚠️ Warning: This code might be interactive, require user input, and might hang.\n"
|
122
|
-
)
|
123
|
-
with open(
|
124
|
-
stdout_file.name, "r", encoding="utf-8", errors="replace"
|
125
|
-
) as out_f:
|
126
|
-
stdout_content = out_f.read()
|
127
|
-
with open(
|
128
|
-
stderr_file.name, "r", encoding="utf-8", errors="replace"
|
129
|
-
) as err_f:
|
130
|
-
stderr_content = err_f.read()
|
131
|
-
max_lines = 100
|
132
|
-
if stdout_lines <= max_lines and stderr_lines <= max_lines:
|
133
|
-
result = warning_msg + tr(
|
134
|
-
"Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
|
135
|
-
return_code=return_code,
|
136
|
-
stdout_content=stdout_content,
|
137
|
-
)
|
138
|
-
if stderr_content.strip():
|
139
|
-
result += tr(
|
140
|
-
"\n--- STDERR ---\n{stderr_content}",
|
141
|
-
stderr_content=stderr_content,
|
142
|
-
)
|
143
|
-
return result
|
144
|
-
else:
|
145
|
-
result = warning_msg + tr(
|
146
|
-
"stdout_file: {stdout_file} (lines: {stdout_lines})\n",
|
147
|
-
stdout_file=stdout_file.name,
|
148
|
-
stdout_lines=stdout_lines,
|
149
|
-
)
|
150
|
-
if stderr_lines > 0 and stderr_content.strip():
|
151
|
-
result += tr(
|
152
|
-
"stderr_file: {stderr_file} (lines: {stderr_lines})\n",
|
153
|
-
stderr_file=stderr_file.name,
|
154
|
-
stderr_lines=stderr_lines,
|
155
|
-
)
|
156
|
-
result += tr(
|
157
|
-
"returncode: {return_code}\nUse the get_lines tool to inspect the contents of these files when needed.",
|
158
|
-
return_code=return_code,
|
159
|
-
)
|
160
|
-
return result
|
161
|
-
except Exception as e:
|
162
|
-
self.report_error(tr(" ❌ Error: {error}", error=e))
|
163
|
-
return tr("Error running code: {error}", error=e)
|
@@ -1,56 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import urllib.parse
|
3
|
-
from janito.agent.tools.gitignore_utils import filter_ignored
|
4
|
-
from janito.agent.runtime_config import runtime_config
|
5
|
-
|
6
|
-
|
7
|
-
def display_path(path):
|
8
|
-
"""
|
9
|
-
Returns a display-friendly path. If runtime_config['termweb_port'] is set, injects an ANSI hyperlink to the local web file viewer.
|
10
|
-
Args:
|
11
|
-
path (str): Path to display.
|
12
|
-
Returns:
|
13
|
-
str: Display path, optionally as an ANSI hyperlink.
|
14
|
-
"""
|
15
|
-
if os.path.isabs(path):
|
16
|
-
disp = path
|
17
|
-
else:
|
18
|
-
disp = os.path.relpath(path)
|
19
|
-
port = runtime_config.get("termweb_port")
|
20
|
-
if port:
|
21
|
-
url = f"http://localhost:{port}/?path={urllib.parse.quote(path)}"
|
22
|
-
# Use Rich markup for hyperlinks
|
23
|
-
return f"[link={url}]{disp}[/link]"
|
24
|
-
return disp
|
25
|
-
|
26
|
-
|
27
|
-
def pluralize(word: str, count: int) -> str:
|
28
|
-
"""Return the pluralized form of word if count != 1, unless word already ends with 's'."""
|
29
|
-
if count == 1 or word.endswith("s"):
|
30
|
-
return word
|
31
|
-
return word + "s"
|
32
|
-
|
33
|
-
|
34
|
-
def find_files_with_extensions(directories, extensions, max_depth=0):
|
35
|
-
"""
|
36
|
-
Find files in given directories with specified extensions, respecting .gitignore.
|
37
|
-
|
38
|
-
Args:
|
39
|
-
directories (list[str]): Directories to search.
|
40
|
-
extensions (list[str]): File extensions to include (e.g., ['.py', '.md']).
|
41
|
-
max_depth (int, optional): Maximum directory depth to search. If 0, unlimited.
|
42
|
-
Returns:
|
43
|
-
list[str]: List of matching file paths.
|
44
|
-
"""
|
45
|
-
output = []
|
46
|
-
for directory in directories:
|
47
|
-
for root, dirs, files in os.walk(directory):
|
48
|
-
rel_path = os.path.relpath(root, directory)
|
49
|
-
depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
|
50
|
-
if max_depth > 0 and depth > max_depth:
|
51
|
-
continue
|
52
|
-
dirs, files = filter_ignored(root, dirs, files)
|
53
|
-
for filename in files:
|
54
|
-
if any(filename.lower().endswith(ext) for ext in extensions):
|
55
|
-
output.append(os.path.join(root, filename))
|
56
|
-
return output
|
janito/agent/tools/utils.py
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
|
3
|
-
|
4
|
-
def expand_path(path: str) -> str:
|
5
|
-
"""
|
6
|
-
If ~ is present in the path, expands it to the user's home directory.
|
7
|
-
Otherwise, returns the path unchanged.
|
8
|
-
"""
|
9
|
-
if "~" in path:
|
10
|
-
return os.path.expanduser(path)
|
11
|
-
return path
|
12
|
-
|
13
|
-
|
14
|
-
def display_path(path: str) -> str:
|
15
|
-
"""
|
16
|
-
Returns a user-friendly path for display:
|
17
|
-
- If the path is relative, return it as-is.
|
18
|
-
- If the path starts with ~, keep it as ~.
|
19
|
-
- If the path is under the home directory, replace the home dir with ~.
|
20
|
-
- Else, show the absolute path.
|
21
|
-
"""
|
22
|
-
if not (
|
23
|
-
path.startswith("/")
|
24
|
-
or path.startswith("~")
|
25
|
-
or (os.name == "nt" and len(path) > 1 and path[1] == ":")
|
26
|
-
):
|
27
|
-
return path
|
28
|
-
home = os.path.expanduser("~")
|
29
|
-
if path.startswith("~"):
|
30
|
-
return path
|
31
|
-
if path.startswith(home):
|
32
|
-
return "~" + path[len(home) :]
|
33
|
-
return path
|
@@ -1,163 +0,0 @@
|
|
1
|
-
from janito.agent.tool_base import ToolBase
|
2
|
-
from janito.agent.tool_registry import register_tool
|
3
|
-
from janito.i18n import tr
|
4
|
-
import os
|
5
|
-
import json
|
6
|
-
import yaml
|
7
|
-
from janito.agent.tools.utils import display_path
|
8
|
-
|
9
|
-
|
10
|
-
@register_tool(name="validate_file_syntax")
|
11
|
-
class ValidateFileSyntaxTool(ToolBase):
|
12
|
-
"""
|
13
|
-
Validate a file for syntax issues.
|
14
|
-
|
15
|
-
Supported types:
|
16
|
-
- Python (.py, .pyw)
|
17
|
-
- JSON (.json)
|
18
|
-
- YAML (.yml, .yaml)
|
19
|
-
- PowerShell (.ps1)
|
20
|
-
- XML (.xml)
|
21
|
-
- HTML (.html, .htm) [lxml]
|
22
|
-
|
23
|
-
Args:
|
24
|
-
file_path (str): Path to the file to validate.
|
25
|
-
Returns:
|
26
|
-
str: Validation status message. Example:
|
27
|
-
- "✅ Syntax OK"
|
28
|
-
- "⚠️ Warning: Syntax error: <error message>"
|
29
|
-
- "⚠️ Warning: Unsupported file extension: <ext>"
|
30
|
-
"""
|
31
|
-
|
32
|
-
def run(self, file_path: str) -> str:
|
33
|
-
disp_path = display_path(file_path)
|
34
|
-
self.report_info(
|
35
|
-
tr("🔎 Validating syntax for: {disp_path} ...", disp_path=disp_path)
|
36
|
-
)
|
37
|
-
ext = os.path.splitext(file_path)[1].lower()
|
38
|
-
try:
|
39
|
-
if ext in [".py", ".pyw"]:
|
40
|
-
import py_compile
|
41
|
-
|
42
|
-
py_compile.compile(file_path, doraise=True)
|
43
|
-
elif ext == ".json":
|
44
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
45
|
-
json.load(f)
|
46
|
-
elif ext in [".yml", ".yaml"]:
|
47
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
48
|
-
yaml.safe_load(f)
|
49
|
-
elif ext == ".ps1":
|
50
|
-
from janito.agent.tools.run_powershell_command import (
|
51
|
-
RunPowerShellCommandTool,
|
52
|
-
)
|
53
|
-
|
54
|
-
ps_tool = RunPowerShellCommandTool()
|
55
|
-
check_cmd = "if (Get-Command Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue) { Write-Output 'PSScriptAnalyzerAvailable' } else { Write-Output 'PSScriptAnalyzerMissing' }"
|
56
|
-
check_result = ps_tool.run(command=check_cmd, timeout=15)
|
57
|
-
if "PSScriptAnalyzerMissing" in check_result:
|
58
|
-
msg = tr(
|
59
|
-
"⚠️ Warning: PSScriptAnalyzer is not installed. For best PowerShell syntax validation, install it with:\n Install-Module -Name PSScriptAnalyzer -Scope CurrentUser\n"
|
60
|
-
)
|
61
|
-
self.report_warning(msg)
|
62
|
-
return msg
|
63
|
-
analyze_cmd = f"Invoke-ScriptAnalyzer -Path '{file_path}' -Severity Error | ConvertTo-Json"
|
64
|
-
analyze_result = ps_tool.run(command=analyze_cmd, timeout=30)
|
65
|
-
if "[]" in analyze_result or analyze_result.strip() == "":
|
66
|
-
self.report_success(tr("✅ Syntax OK"))
|
67
|
-
return tr("✅ Syntax valid")
|
68
|
-
else:
|
69
|
-
msg = tr(
|
70
|
-
"⚠️ Warning: PowerShell syntax issues found:\n{analyze_result}",
|
71
|
-
analyze_result=analyze_result,
|
72
|
-
)
|
73
|
-
self.report_warning(msg)
|
74
|
-
return msg
|
75
|
-
elif ext == ".xml":
|
76
|
-
try:
|
77
|
-
from lxml import etree
|
78
|
-
except ImportError:
|
79
|
-
msg = tr("⚠️ lxml not installed. Cannot validate XML.")
|
80
|
-
self.report_warning(msg)
|
81
|
-
return msg
|
82
|
-
with open(file_path, "rb") as f:
|
83
|
-
etree.parse(f)
|
84
|
-
elif ext in (".html", ".htm"):
|
85
|
-
try:
|
86
|
-
from lxml import html
|
87
|
-
except ImportError:
|
88
|
-
msg = tr("⚠️ lxml not installed. Cannot validate HTML.")
|
89
|
-
self.report_warning(msg)
|
90
|
-
return msg
|
91
|
-
with open(file_path, "rb") as f:
|
92
|
-
html.parse(f)
|
93
|
-
from lxml import etree
|
94
|
-
|
95
|
-
parser = etree.HTMLParser(recover=False)
|
96
|
-
with open(file_path, "rb") as f:
|
97
|
-
etree.parse(f, parser=parser)
|
98
|
-
if parser.error_log:
|
99
|
-
errors = "\n".join(str(e) for e in parser.error_log)
|
100
|
-
raise ValueError(
|
101
|
-
tr("HTML syntax errors found:\n{errors}", errors=errors)
|
102
|
-
)
|
103
|
-
elif ext == ".md":
|
104
|
-
import re
|
105
|
-
|
106
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
107
|
-
content = f.read()
|
108
|
-
errors = []
|
109
|
-
# Rule: Headers must start with # followed by a space
|
110
|
-
for i, line in enumerate(content.splitlines(), 1):
|
111
|
-
if re.match(r"^#+[^ #]", line):
|
112
|
-
errors.append(
|
113
|
-
f"Line {i}: Header missing space after # | {line.strip()}"
|
114
|
-
)
|
115
|
-
# Rule: Unclosed code blocks
|
116
|
-
if content.count("```") % 2 != 0:
|
117
|
-
errors.append("Unclosed code block (```) detected")
|
118
|
-
# Rule: Unclosed links/images (flag only if line contains [text]( but not ))
|
119
|
-
for i, line in enumerate(content.splitlines(), 1):
|
120
|
-
if re.search(r"\[[^\]]*\]\([^)]+$", line):
|
121
|
-
errors.append(
|
122
|
-
f"Line {i}: Unclosed link or image (missing closing parenthesis) | {line.strip()}"
|
123
|
-
)
|
124
|
-
# Rule: List items must start with -, *, or + followed by space
|
125
|
-
for i, line in enumerate(content.splitlines(), 1):
|
126
|
-
# Skip horizontal rules like --- or ***
|
127
|
-
if re.match(r"^([-*+])\1{1,}", line):
|
128
|
-
continue
|
129
|
-
# Skip table rows (lines starting with |)
|
130
|
-
if line.lstrip().startswith("|"):
|
131
|
-
continue
|
132
|
-
# Only flag as list item if there is text after the bullet (not just emphasis)
|
133
|
-
if re.match(r"^[-*+][^ \n]", line):
|
134
|
-
stripped = line.strip()
|
135
|
-
# If the line is surrounded by * and ends with *, it's likely emphasis, not a list
|
136
|
-
if not (
|
137
|
-
stripped.startswith("*")
|
138
|
-
and stripped.endswith("*")
|
139
|
-
and len(stripped) > 2
|
140
|
-
):
|
141
|
-
errors.append(
|
142
|
-
f"Line {i}: List item missing space after bullet | {line.strip()}"
|
143
|
-
)
|
144
|
-
# Rule: Inline code must have even number of backticks
|
145
|
-
if content.count("`") % 2 != 0:
|
146
|
-
errors.append("Unclosed inline code (`) detected")
|
147
|
-
if errors:
|
148
|
-
msg = tr(
|
149
|
-
"⚠️ Warning: Markdown syntax issues found:\n{errors}",
|
150
|
-
errors="\n".join(errors),
|
151
|
-
)
|
152
|
-
self.report_warning(msg)
|
153
|
-
return msg
|
154
|
-
else:
|
155
|
-
msg = tr("⚠️ Warning: Unsupported file extension: {ext}", ext=ext)
|
156
|
-
self.report_warning(msg)
|
157
|
-
return msg
|
158
|
-
self.report_success(tr("✅ Syntax OK"))
|
159
|
-
return tr("✅ Syntax valid")
|
160
|
-
except Exception as e:
|
161
|
-
msg = tr("⚠️ Warning: Syntax error: {error}", error=e)
|
162
|
-
self.report_warning(msg)
|
163
|
-
return msg
|
janito/cli/runner/cli_main.py
DELETED
@@ -1,180 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
import socket
|
3
|
-
from janito.agent.profile_manager import AgentProfileManager
|
4
|
-
from janito.agent.runtime_config import unified_config, runtime_config
|
5
|
-
from janito.agent.config import get_api_key
|
6
|
-
from janito import __version__
|
7
|
-
from janito.agent.conversation_exceptions import (
|
8
|
-
MaxRoundsExceededError,
|
9
|
-
EmptyResponseError,
|
10
|
-
ProviderError,
|
11
|
-
)
|
12
|
-
|
13
|
-
|
14
|
-
def is_port_free(port):
|
15
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
16
|
-
return s.connect_ex(("localhost", port)) != 0
|
17
|
-
|
18
|
-
|
19
|
-
def run_cli(args):
|
20
|
-
if args.version:
|
21
|
-
print(f"janito version {__version__}")
|
22
|
-
sys.exit(0)
|
23
|
-
|
24
|
-
# Set vanilla mode if -V/--vanilla is passed
|
25
|
-
if getattr(args, "vanilla", False):
|
26
|
-
runtime_config.set("vanilla_mode", True)
|
27
|
-
|
28
|
-
# Normalize all verbose flags into runtime_config
|
29
|
-
for flag in [
|
30
|
-
"verbose_http",
|
31
|
-
"verbose_http_raw",
|
32
|
-
"verbose_response",
|
33
|
-
"verbose_reason",
|
34
|
-
"verbose_tools",
|
35
|
-
"verbose_events",
|
36
|
-
"verbose_stream",
|
37
|
-
]:
|
38
|
-
if hasattr(args, flag):
|
39
|
-
runtime_config.set(flag, getattr(args, flag, False))
|
40
|
-
|
41
|
-
role = args.role or unified_config.get("role", "software engineer")
|
42
|
-
if args.role:
|
43
|
-
runtime_config.set("role", args.role)
|
44
|
-
if getattr(args, "model", None):
|
45
|
-
runtime_config.set("model", args.model)
|
46
|
-
if getattr(args, "max_tools", None) is not None:
|
47
|
-
runtime_config.set("max_tools", args.max_tools)
|
48
|
-
if getattr(args, "trust_tools", False):
|
49
|
-
runtime_config.set("trust_tools", True)
|
50
|
-
if not getattr(args, "prompt", None):
|
51
|
-
interaction_mode = "chat"
|
52
|
-
else:
|
53
|
-
interaction_mode = "prompt"
|
54
|
-
profile = "base"
|
55
|
-
# PATCH: Pass lang from args or runtime_config to AgentProfileManager
|
56
|
-
lang = getattr(args, "lang", None) or runtime_config.get("lang", "en")
|
57
|
-
profile_manager = AgentProfileManager(
|
58
|
-
api_key=get_api_key(),
|
59
|
-
model=unified_config.get("model"),
|
60
|
-
role=role,
|
61
|
-
profile_name=profile,
|
62
|
-
interaction_mode=interaction_mode,
|
63
|
-
verbose_tools=args.verbose_tools,
|
64
|
-
base_url=unified_config.get("base_url", "https://openrouter.ai/api/v1"),
|
65
|
-
azure_openai_api_version=unified_config.get(
|
66
|
-
"azure_openai_api_version", "2023-05-15"
|
67
|
-
),
|
68
|
-
use_azure_openai=unified_config.get("use_azure_openai", False),
|
69
|
-
lang=lang,
|
70
|
-
)
|
71
|
-
profile_manager.refresh_prompt()
|
72
|
-
if getattr(args, "show_system", False):
|
73
|
-
print(profile_manager.render_prompt())
|
74
|
-
sys.exit(0)
|
75
|
-
if args.max_tokens is not None:
|
76
|
-
runtime_config.set("max_tokens", args.max_tokens)
|
77
|
-
if getattr(args, "verbose_reason", False):
|
78
|
-
runtime_config.set("verbose_reason", True)
|
79
|
-
|
80
|
-
# --- termweb integration ---
|
81
|
-
termweb_proc = None
|
82
|
-
selected_port = None
|
83
|
-
if (
|
84
|
-
not getattr(args, "no_termweb", False)
|
85
|
-
and interaction_mode == "chat"
|
86
|
-
and not runtime_config.get("vanilla_mode", False)
|
87
|
-
):
|
88
|
-
default_port = 8088
|
89
|
-
max_port = 8100
|
90
|
-
requested_port = args.termweb_port
|
91
|
-
if requested_port == default_port:
|
92
|
-
for port in range(default_port, max_port + 1):
|
93
|
-
if is_port_free(port):
|
94
|
-
selected_port = port
|
95
|
-
break
|
96
|
-
if selected_port is None:
|
97
|
-
print(
|
98
|
-
f"[red]No free port found for termweb in range {default_port}-{max_port}.[/red]"
|
99
|
-
)
|
100
|
-
sys.exit(1)
|
101
|
-
else:
|
102
|
-
if not is_port_free(requested_port):
|
103
|
-
print(f"[red]Port {requested_port} is not available for termweb.[/red]")
|
104
|
-
sys.exit(1)
|
105
|
-
selected_port = requested_port
|
106
|
-
runtime_config.set("termweb_port", selected_port)
|
107
|
-
from janito.cli.termweb_starter import start_termweb
|
108
|
-
|
109
|
-
termweb_proc, started, termweb_stdout_path, termweb_stderr_path = start_termweb(
|
110
|
-
selected_port
|
111
|
-
)
|
112
|
-
# Store last running port in .janito/config.json if started
|
113
|
-
if started:
|
114
|
-
from janito.agent.config import local_config
|
115
|
-
|
116
|
-
local_config.set("termweb_last_running_port", selected_port)
|
117
|
-
local_config.save()
|
118
|
-
|
119
|
-
# --- End termweb integration ---
|
120
|
-
try:
|
121
|
-
if not getattr(args, "prompt", None):
|
122
|
-
from janito.cli_chat_shell.chat_loop import start_chat_shell
|
123
|
-
|
124
|
-
start_chat_shell(
|
125
|
-
profile_manager,
|
126
|
-
continue_session=getattr(args, "continue_session", False),
|
127
|
-
termweb_stdout_path=(
|
128
|
-
termweb_stdout_path if "termweb_stdout_path" in locals() else None
|
129
|
-
),
|
130
|
-
termweb_stderr_path=(
|
131
|
-
termweb_stderr_path if "termweb_stderr_path" in locals() else None
|
132
|
-
),
|
133
|
-
)
|
134
|
-
sys.exit(0)
|
135
|
-
# --- Prompt mode ---
|
136
|
-
prompt = args.prompt
|
137
|
-
from rich.console import Console
|
138
|
-
from janito.agent.rich_message_handler import RichMessageHandler
|
139
|
-
|
140
|
-
console = Console()
|
141
|
-
message_handler = RichMessageHandler()
|
142
|
-
messages = []
|
143
|
-
system_prompt_override = runtime_config.get("system_prompt_template")
|
144
|
-
if system_prompt_override:
|
145
|
-
# Só adiciona system prompt se NÃO for vanilla, ou se foi explicitamente passado via --system
|
146
|
-
if not runtime_config.get("vanilla_mode", False) or getattr(
|
147
|
-
args, "system", None
|
148
|
-
):
|
149
|
-
messages.append({"role": "system", "content": system_prompt_override})
|
150
|
-
elif profile_manager.system_prompt_template and not runtime_config.get(
|
151
|
-
"vanilla_mode", False
|
152
|
-
):
|
153
|
-
messages.append(
|
154
|
-
{"role": "system", "content": profile_manager.system_prompt_template}
|
155
|
-
)
|
156
|
-
messages.append({"role": "user", "content": prompt})
|
157
|
-
try:
|
158
|
-
max_rounds = 50
|
159
|
-
profile_manager.agent.chat(
|
160
|
-
messages,
|
161
|
-
message_handler=message_handler,
|
162
|
-
spinner=True,
|
163
|
-
max_rounds=max_rounds,
|
164
|
-
stream=getattr(args, "stream", False),
|
165
|
-
)
|
166
|
-
except MaxRoundsExceededError:
|
167
|
-
console.print("[red]Max conversation rounds exceeded.[/red]")
|
168
|
-
except ProviderError as e:
|
169
|
-
console.print(f"[red]Provider error:[/red] {e}")
|
170
|
-
except EmptyResponseError as e:
|
171
|
-
console.print(f"[red]Error:[/red] {e}")
|
172
|
-
except KeyboardInterrupt:
|
173
|
-
from rich.console import Console
|
174
|
-
|
175
|
-
console = Console()
|
176
|
-
console.print("[yellow]Interrupted by user.[/yellow]")
|
177
|
-
finally:
|
178
|
-
if termweb_proc:
|
179
|
-
termweb_proc.terminate()
|
180
|
-
termweb_proc.wait()
|