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.
Files changed (142) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/api_exceptions.py +4 -0
  3. janito/agent/config.py +1 -1
  4. janito/agent/config_defaults.py +2 -3
  5. janito/agent/config_utils.py +0 -9
  6. janito/agent/conversation.py +177 -114
  7. janito/agent/conversation_api.py +179 -159
  8. janito/agent/conversation_tool_calls.py +11 -8
  9. janito/agent/llm_conversation_history.py +70 -0
  10. janito/agent/openai_client.py +44 -21
  11. janito/agent/openai_schema_generator.py +164 -128
  12. janito/agent/platform_discovery.py +134 -77
  13. janito/agent/profile_manager.py +5 -5
  14. janito/agent/rich_message_handler.py +80 -31
  15. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +9 -8
  16. janito/agent/test_openai_schema_generator.py +93 -0
  17. janito/agent/tool_base.py +7 -2
  18. janito/agent/tool_executor.py +63 -50
  19. janito/agent/tool_registry.py +5 -2
  20. janito/agent/tool_use_tracker.py +42 -5
  21. janito/agent/tools/__init__.py +13 -12
  22. janito/agent/tools/create_directory.py +9 -6
  23. janito/agent/tools/create_file.py +35 -54
  24. janito/agent/tools/delete_text_in_file.py +97 -0
  25. janito/agent/tools/fetch_url.py +50 -5
  26. janito/agent/tools/find_files.py +40 -26
  27. janito/agent/tools/get_file_outline/__init__.py +1 -0
  28. janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +14 -18
  29. janito/agent/tools/get_file_outline/python_outline.py +134 -0
  30. janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +11 -0
  31. janito/agent/tools/get_lines.py +21 -12
  32. janito/agent/tools/move_file.py +13 -12
  33. janito/agent/tools/present_choices.py +3 -1
  34. janito/agent/tools/python_command_runner.py +150 -0
  35. janito/agent/tools/python_file_runner.py +148 -0
  36. janito/agent/tools/python_stdin_runner.py +154 -0
  37. janito/agent/tools/remove_directory.py +4 -2
  38. janito/agent/tools/remove_file.py +15 -13
  39. janito/agent/tools/replace_file.py +72 -0
  40. janito/agent/tools/replace_text_in_file.py +7 -5
  41. janito/agent/tools/run_bash_command.py +29 -72
  42. janito/agent/tools/run_powershell_command.py +142 -102
  43. janito/agent/tools/search_text.py +177 -131
  44. janito/agent/tools/validate_file_syntax/__init__.py +1 -0
  45. janito/agent/tools/validate_file_syntax/core.py +94 -0
  46. janito/agent/tools/validate_file_syntax/css_validator.py +35 -0
  47. janito/agent/tools/validate_file_syntax/html_validator.py +77 -0
  48. janito/agent/tools/validate_file_syntax/js_validator.py +27 -0
  49. janito/agent/tools/validate_file_syntax/json_validator.py +6 -0
  50. janito/agent/tools/validate_file_syntax/markdown_validator.py +66 -0
  51. janito/agent/tools/validate_file_syntax/ps1_validator.py +32 -0
  52. janito/agent/tools/validate_file_syntax/python_validator.py +5 -0
  53. janito/agent/tools/validate_file_syntax/xml_validator.py +11 -0
  54. janito/agent/tools/validate_file_syntax/yaml_validator.py +6 -0
  55. janito/agent/tools_utils/__init__.py +1 -0
  56. janito/agent/tools_utils/action_type.py +7 -0
  57. janito/agent/tools_utils/dir_walk_utils.py +24 -0
  58. janito/agent/tools_utils/formatting.py +49 -0
  59. janito/agent/tools_utils/gitignore_utils.py +69 -0
  60. janito/agent/tools_utils/test_gitignore_utils.py +46 -0
  61. janito/agent/tools_utils/utils.py +30 -0
  62. janito/cli/_livereload_log_utils.py +13 -0
  63. janito/cli/_print_config.py +63 -61
  64. janito/cli/arg_parser.py +57 -14
  65. janito/cli/cli_main.py +270 -0
  66. janito/cli/livereload_starter.py +60 -0
  67. janito/cli/main.py +166 -99
  68. janito/cli/one_shot.py +80 -0
  69. janito/cli/termweb_starter.py +2 -2
  70. janito/i18n/__init__.py +1 -1
  71. janito/livereload/app.py +25 -0
  72. janito/rich_utils.py +41 -25
  73. janito/{cli_chat_shell → shell}/commands/__init__.py +19 -14
  74. janito/{cli_chat_shell → shell}/commands/config.py +4 -4
  75. janito/shell/commands/conversation_restart.py +74 -0
  76. janito/shell/commands/edit.py +24 -0
  77. janito/shell/commands/history_view.py +18 -0
  78. janito/{cli_chat_shell → shell}/commands/lang.py +3 -0
  79. janito/shell/commands/livelogs.py +42 -0
  80. janito/{cli_chat_shell → shell}/commands/prompt.py +16 -6
  81. janito/shell/commands/session.py +35 -0
  82. janito/{cli_chat_shell → shell}/commands/session_control.py +3 -5
  83. janito/{cli_chat_shell → shell}/commands/termweb_log.py +18 -10
  84. janito/shell/commands/tools.py +26 -0
  85. janito/shell/commands/track.py +36 -0
  86. janito/shell/commands/utility.py +28 -0
  87. janito/{cli_chat_shell → shell}/commands/verbose.py +4 -5
  88. janito/shell/commands.py +40 -0
  89. janito/shell/input_history.py +62 -0
  90. janito/shell/main.py +257 -0
  91. janito/{cli_chat_shell/shell_command_completer.py → shell/prompt/completer.py} +1 -1
  92. janito/{cli_chat_shell/chat_ui.py → shell/prompt/session_setup.py} +19 -5
  93. janito/shell/session/manager.py +101 -0
  94. janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -17
  95. janito/termweb/app.py +3 -3
  96. janito/termweb/static/editor.css +142 -0
  97. janito/termweb/static/editor.css.bak +27 -0
  98. janito/termweb/static/editor.html +15 -213
  99. janito/termweb/static/editor.html.bak +16 -215
  100. janito/termweb/static/editor.js +209 -0
  101. janito/termweb/static/editor.js.bak +227 -0
  102. janito/termweb/static/index.html +2 -3
  103. janito/termweb/static/index.html.bak +2 -3
  104. janito/termweb/static/termweb.css.bak +33 -84
  105. janito/termweb/static/termweb.js +15 -34
  106. janito/termweb/static/termweb.js.bak +18 -36
  107. janito/tests/test_rich_utils.py +44 -0
  108. janito/web/app.py +0 -75
  109. {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/METADATA +62 -42
  110. janito-1.10.0.dist-info/RECORD +158 -0
  111. {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/WHEEL +1 -1
  112. janito/agent/tools/dir_walk_utils.py +0 -16
  113. janito/agent/tools/gitignore_utils.py +0 -46
  114. janito/agent/tools/memory.py +0 -48
  115. janito/agent/tools/outline_file/formatting.py +0 -20
  116. janito/agent/tools/outline_file/python_outline.py +0 -71
  117. janito/agent/tools/present_choices_test.py +0 -18
  118. janito/agent/tools/rich_live.py +0 -44
  119. janito/agent/tools/run_python_command.py +0 -163
  120. janito/agent/tools/tools_utils.py +0 -56
  121. janito/agent/tools/utils.py +0 -33
  122. janito/agent/tools/validate_file_syntax.py +0 -163
  123. janito/cli/runner/cli_main.py +0 -180
  124. janito/cli_chat_shell/chat_loop.py +0 -163
  125. janito/cli_chat_shell/chat_state.py +0 -38
  126. janito/cli_chat_shell/commands/history_start.py +0 -37
  127. janito/cli_chat_shell/commands/session.py +0 -48
  128. janito/cli_chat_shell/commands/sum.py +0 -49
  129. janito/cli_chat_shell/commands/utility.py +0 -32
  130. janito/cli_chat_shell/session_manager.py +0 -72
  131. janito-1.8.1.dist-info/RECORD +0 -127
  132. /janito/agent/tools/{outline_file → get_file_outline}/markdown_outline.py +0 -0
  133. /janito/cli/{runner/_termweb_log_utils.py → _termweb_log_utils.py} +0 -0
  134. /janito/cli/{runner/config.py → config_runner.py} +0 -0
  135. /janito/cli/{runner/formatting.py → formatting_runner.py} +0 -0
  136. /janito/{cli/runner → shell}/__init__.py +0 -0
  137. /janito/{cli_chat_shell → shell/prompt}/load_prompt.py +0 -0
  138. /janito/{cli_chat_shell/config_shell.py → shell/session/config.py} +0 -0
  139. /janito/{cli_chat_shell/__init__.py → shell/session/history.py} +0 -0
  140. {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/entry_points.txt +0 -0
  141. {janito-1.8.1.dist-info → janito-1.10.0.dist-info}/licenses/LICENSE +0 -0
  142. {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}")
@@ -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
@@ -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
@@ -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()