janito 1.8.0__py3-none-any.whl → 1.9.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/config_defaults.py +23 -0
- janito/agent/config_utils.py +0 -9
- janito/agent/conversation.py +31 -9
- janito/agent/conversation_api.py +32 -2
- janito/agent/conversation_history.py +53 -0
- janito/agent/conversation_tool_calls.py +11 -8
- janito/agent/openai_client.py +11 -3
- janito/agent/openai_schema_generator.py +9 -6
- janito/agent/providers.py +77 -0
- janito/agent/rich_message_handler.py +1 -1
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +8 -8
- janito/agent/tool_executor.py +18 -10
- janito/agent/tool_use_tracker.py +16 -0
- janito/agent/tools/__init__.py +7 -9
- janito/agent/tools/create_directory.py +7 -6
- janito/agent/tools/create_file.py +29 -54
- janito/agent/tools/delete_text_in_file.py +97 -0
- janito/agent/tools/fetch_url.py +11 -3
- janito/agent/tools/find_files.py +37 -25
- janito/agent/tools/get_file_outline/__init__.py +1 -0
- janito/agent/tools/{outline_file/__init__.py → get_file_outline/core.py} +12 -15
- janito/agent/tools/get_file_outline/python_outline.py +134 -0
- janito/agent/tools/{search_outline.py → get_file_outline/search_outline.py} +9 -0
- janito/agent/tools/get_lines.py +15 -11
- janito/agent/tools/move_file.py +10 -11
- janito/agent/tools/remove_directory.py +2 -2
- janito/agent/tools/remove_file.py +11 -13
- janito/agent/tools/replace_file.py +62 -0
- janito/agent/tools/replace_text_in_file.py +3 -3
- janito/agent/tools/run_bash_command.py +3 -7
- janito/agent/tools/run_powershell_command.py +39 -28
- janito/agent/tools/run_python_command.py +3 -5
- janito/agent/tools/search_text.py +10 -14
- janito/agent/tools/validate_file_syntax/__init__.py +1 -0
- janito/agent/tools/validate_file_syntax/core.py +92 -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/dir_walk_utils.py +23 -0
- janito/agent/{tools/outline_file → tools_utils}/formatting.py +5 -2
- janito/agent/{tools → tools_utils}/gitignore_utils.py +0 -3
- janito/agent/tools_utils/utils.py +30 -0
- janito/cli/_livereload_log_utils.py +13 -0
- janito/cli/arg_parser.py +45 -3
- janito/cli/{runner/cli_main.py → cli_main.py} +120 -20
- janito/cli/livereload_starter.py +60 -0
- janito/cli/main.py +110 -21
- janito/cli/one_shot.py +66 -0
- janito/cli/termweb_starter.py +2 -2
- janito/livereload/app.py +25 -0
- janito/rich_utils.py +0 -22
- janito/{cli_chat_shell → shell}/commands/__init__.py +18 -11
- janito/{cli_chat_shell → shell}/commands/config.py +4 -4
- janito/shell/commands/conversation_restart.py +72 -0
- janito/shell/commands/edit.py +21 -0
- janito/shell/commands/history_view.py +18 -0
- janito/shell/commands/livelogs.py +40 -0
- janito/{cli_chat_shell → shell}/commands/prompt.py +10 -6
- janito/shell/commands/session.py +32 -0
- janito/{cli_chat_shell → shell}/commands/session_control.py +2 -7
- janito/{cli_chat_shell → shell}/commands/sum.py +6 -6
- janito/{cli_chat_shell → shell}/commands/termweb_log.py +10 -10
- janito/shell/commands/tools.py +23 -0
- janito/{cli_chat_shell → shell}/commands/utility.py +5 -4
- janito/{cli_chat_shell → shell}/commands/verbose.py +1 -1
- janito/shell/commands.py +40 -0
- janito/shell/main.py +321 -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/{cli_chat_shell/session_manager.py → shell/session/manager.py} +53 -3
- janito/{cli_chat_shell/ui.py → shell/ui/interactive.py} +23 -15
- janito/termweb/app.py +3 -3
- janito/termweb/static/editor.css +146 -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-1.8.0.dist-info → janito-1.9.0.dist-info}/METADATA +6 -3
- janito-1.9.0.dist-info/RECORD +151 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/WHEEL +1 -1
- janito/agent/tools/dir_walk_utils.py +0 -16
- janito/agent/tools/memory.py +0 -48
- 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/tools_utils.py +0 -56
- janito/agent/tools/utils.py +0 -33
- janito/agent/tools/validate_file_syntax.py +0 -163
- 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-1.8.0.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}/commands/lang.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.0.dist-info → janito-1.9.0.dist-info}/entry_points.txt +0 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.8.0.dist-info → janito-1.9.0.dist-info}/top_level.txt +0 -0
janito/agent/tools/memory.py
DELETED
@@ -1,48 +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
|
-
|
5
|
-
|
6
|
-
@register_tool(name="memory")
|
7
|
-
class MemoryTool(ToolBase):
|
8
|
-
"""
|
9
|
-
Simple in-memory key-value store for demonstration purposes.
|
10
|
-
"""
|
11
|
-
|
12
|
-
def __init__(self):
|
13
|
-
super().__init__()
|
14
|
-
self.memory = {}
|
15
|
-
|
16
|
-
def run(self, action: str, key: str, value: str = None) -> str:
|
17
|
-
if action == "set":
|
18
|
-
self.report_info(tr("ℹ️ Storing value for key: '{key}' ...", key=key))
|
19
|
-
self.memory[key] = value
|
20
|
-
msg = tr("Value stored for key: '{key}'.", key=key)
|
21
|
-
self.report_success(msg)
|
22
|
-
return msg
|
23
|
-
elif action == "get":
|
24
|
-
self.report_info(tr("ℹ️ Retrieving value for key: '{key}' ...", key=key))
|
25
|
-
if key in self.memory:
|
26
|
-
msg = tr(
|
27
|
-
"Value for key '{key}': {value}", key=key, value=self.memory[key]
|
28
|
-
)
|
29
|
-
self.report_success(msg)
|
30
|
-
return msg
|
31
|
-
else:
|
32
|
-
msg = tr("Key '{key}' not found.", key=key)
|
33
|
-
self.report_warning(msg)
|
34
|
-
return msg
|
35
|
-
elif action == "delete":
|
36
|
-
if key in self.memory:
|
37
|
-
del self.memory[key]
|
38
|
-
msg = tr("Key '{key}' deleted.", key=key)
|
39
|
-
self.report_success(msg)
|
40
|
-
return msg
|
41
|
-
else:
|
42
|
-
msg = tr("Key '{key}' not found.", key=key)
|
43
|
-
self.report_error(msg)
|
44
|
-
return msg
|
45
|
-
else:
|
46
|
-
msg = tr("Unknown action: {action}", action=action)
|
47
|
-
self.report_error(msg)
|
48
|
-
return msg
|
@@ -1,71 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
from typing import List
|
3
|
-
|
4
|
-
|
5
|
-
def parse_python_outline(lines: List[str]):
|
6
|
-
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
7
|
-
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
8
|
-
assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
|
9
|
-
outline = []
|
10
|
-
stack = [] # (type, name, indent, start, parent)
|
11
|
-
for idx, line in enumerate(lines):
|
12
|
-
class_match = class_pat.match(line)
|
13
|
-
func_match = func_pat.match(line)
|
14
|
-
assign_match = assign_pat.match(line)
|
15
|
-
indent = len(line) - len(line.lstrip())
|
16
|
-
if class_match:
|
17
|
-
name = class_match.group(2)
|
18
|
-
parent = stack[-1][1] if stack and stack[-1][0] == "class" else ""
|
19
|
-
stack.append(("class", name, indent, idx + 1, parent))
|
20
|
-
elif func_match:
|
21
|
-
name = func_match.group(2)
|
22
|
-
parent = (
|
23
|
-
stack[-1][1]
|
24
|
-
if stack
|
25
|
-
and stack[-1][0] in ("class", "function")
|
26
|
-
and indent > stack[-1][2]
|
27
|
-
else ""
|
28
|
-
)
|
29
|
-
stack.append(("function", name, indent, idx + 1, parent))
|
30
|
-
elif assign_match and indent == 0:
|
31
|
-
var_name = assign_match.group(2)
|
32
|
-
var_type = "const" if var_name.isupper() else "var"
|
33
|
-
outline.append(
|
34
|
-
{
|
35
|
-
"type": var_type,
|
36
|
-
"name": var_name,
|
37
|
-
"start": idx + 1,
|
38
|
-
"end": idx + 1,
|
39
|
-
"parent": "",
|
40
|
-
}
|
41
|
-
)
|
42
|
-
while stack and indent < stack[-1][2]:
|
43
|
-
popped = stack.pop()
|
44
|
-
outline.append(
|
45
|
-
{
|
46
|
-
"type": (
|
47
|
-
popped[0]
|
48
|
-
if popped[0] != "function" or popped[3] == 1
|
49
|
-
else ("method" if popped[4] else "function")
|
50
|
-
),
|
51
|
-
"name": popped[1],
|
52
|
-
"start": popped[3],
|
53
|
-
"end": idx,
|
54
|
-
"parent": popped[4],
|
55
|
-
}
|
56
|
-
)
|
57
|
-
for popped in stack:
|
58
|
-
outline.append(
|
59
|
-
{
|
60
|
-
"type": (
|
61
|
-
popped[0]
|
62
|
-
if popped[0] != "function" or popped[3] == 1
|
63
|
-
else ("method" if popped[4] else "function")
|
64
|
-
),
|
65
|
-
"name": popped[1],
|
66
|
-
"start": popped[3],
|
67
|
-
"end": len(lines),
|
68
|
-
"parent": popped[4],
|
69
|
-
}
|
70
|
-
)
|
71
|
-
return outline
|
@@ -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,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
|
@@ -1,163 +0,0 @@
|
|
1
|
-
from janito.agent.rich_message_handler import RichMessageHandler
|
2
|
-
from .chat_state import load_chat_state, save_chat_state
|
3
|
-
from .chat_ui import setup_prompt_session, print_welcome_message
|
4
|
-
from .commands import handle_command
|
5
|
-
from janito.agent.conversation_exceptions import EmptyResponseError, ProviderError
|
6
|
-
|
7
|
-
# Track the active prompt session for cleanup
|
8
|
-
active_prompt_session = None
|
9
|
-
|
10
|
-
|
11
|
-
def start_chat_shell(
|
12
|
-
profile_manager,
|
13
|
-
continue_session=False,
|
14
|
-
max_rounds=50,
|
15
|
-
termweb_stdout_path=None,
|
16
|
-
termweb_stderr_path=None,
|
17
|
-
):
|
18
|
-
import janito.i18n as i18n
|
19
|
-
from janito.agent.runtime_config import runtime_config
|
20
|
-
|
21
|
-
i18n.set_locale(runtime_config.get("lang", "en"))
|
22
|
-
global active_prompt_session
|
23
|
-
agent = profile_manager.agent
|
24
|
-
message_handler = RichMessageHandler()
|
25
|
-
console = message_handler.console
|
26
|
-
|
27
|
-
# Load state
|
28
|
-
state = load_chat_state(continue_session)
|
29
|
-
if termweb_stdout_path:
|
30
|
-
state["termweb_stdout_path"] = termweb_stdout_path
|
31
|
-
if termweb_stderr_path:
|
32
|
-
state["termweb_stderr_path"] = termweb_stderr_path
|
33
|
-
messages = state["messages"]
|
34
|
-
mem_history = state["mem_history"]
|
35
|
-
last_usage_info_ref = {"value": state["last_usage_info"]}
|
36
|
-
last_elapsed = state["last_elapsed"]
|
37
|
-
|
38
|
-
# Add system prompt if needed (skip in vanilla mode)
|
39
|
-
from janito.agent.runtime_config import runtime_config
|
40
|
-
|
41
|
-
if (
|
42
|
-
profile_manager.system_prompt_template
|
43
|
-
and (
|
44
|
-
not runtime_config.get("vanilla_mode", False)
|
45
|
-
or runtime_config.get("system_prompt_template")
|
46
|
-
)
|
47
|
-
and not any(m.get("role") == "system" for m in messages)
|
48
|
-
):
|
49
|
-
messages.insert(0, {"role": "system", "content": agent.system_prompt_template})
|
50
|
-
|
51
|
-
print_welcome_message(console, continued=continue_session)
|
52
|
-
|
53
|
-
session = setup_prompt_session(
|
54
|
-
messages, last_usage_info_ref, last_elapsed, mem_history, profile_manager, agent
|
55
|
-
)
|
56
|
-
active_prompt_session = session
|
57
|
-
|
58
|
-
inject_message = state.get("inject_message")
|
59
|
-
if "inject_message" in state:
|
60
|
-
del state["inject_message"]
|
61
|
-
|
62
|
-
while True:
|
63
|
-
try:
|
64
|
-
if inject_message is not None:
|
65
|
-
user_input = inject_message
|
66
|
-
inject_message = None
|
67
|
-
was_paste_mode = False
|
68
|
-
elif state.get("paste_mode"):
|
69
|
-
console.print("")
|
70
|
-
user_input = session.prompt("Multiline> ", multiline=True)
|
71
|
-
was_paste_mode = True
|
72
|
-
state["paste_mode"] = False
|
73
|
-
else:
|
74
|
-
from prompt_toolkit.formatted_text import HTML
|
75
|
-
|
76
|
-
user_input = session.prompt(
|
77
|
-
HTML("<inputline>💬 </inputline>"), multiline=False
|
78
|
-
)
|
79
|
-
was_paste_mode = False
|
80
|
-
except EOFError:
|
81
|
-
console.print("\n[bold red]Exiting...[/bold red]")
|
82
|
-
break
|
83
|
-
except KeyboardInterrupt:
|
84
|
-
console.print() # Move to next line
|
85
|
-
try:
|
86
|
-
confirm = (
|
87
|
-
session.prompt(
|
88
|
-
# Use <inputline> for full-line blue background, <prompt> for icon only
|
89
|
-
HTML(
|
90
|
-
"<inputline>Do you really want to exit? (y/n): </inputline>"
|
91
|
-
)
|
92
|
-
)
|
93
|
-
.strip()
|
94
|
-
.lower()
|
95
|
-
)
|
96
|
-
except KeyboardInterrupt:
|
97
|
-
message_handler.handle_message(
|
98
|
-
{"type": "error", "message": "Exiting..."}
|
99
|
-
)
|
100
|
-
break
|
101
|
-
if confirm == "y":
|
102
|
-
message_handler.handle_message(
|
103
|
-
{"type": "error", "message": "Exiting..."}
|
104
|
-
)
|
105
|
-
break
|
106
|
-
else:
|
107
|
-
continue
|
108
|
-
|
109
|
-
cmd_input = user_input.strip().lower()
|
110
|
-
if not was_paste_mode and (cmd_input.startswith("/") or cmd_input == "exit"):
|
111
|
-
# Treat both '/exit' and 'exit' as commands
|
112
|
-
result = handle_command(
|
113
|
-
user_input.strip(),
|
114
|
-
console,
|
115
|
-
profile_manager=profile_manager,
|
116
|
-
agent=agent,
|
117
|
-
messages=messages,
|
118
|
-
mem_history=mem_history,
|
119
|
-
state=state,
|
120
|
-
)
|
121
|
-
if result == "exit":
|
122
|
-
break
|
123
|
-
continue
|
124
|
-
|
125
|
-
if not user_input.strip():
|
126
|
-
continue
|
127
|
-
|
128
|
-
mem_history.append_string(user_input)
|
129
|
-
messages.append({"role": "user", "content": user_input})
|
130
|
-
|
131
|
-
import time
|
132
|
-
|
133
|
-
start_time = time.time()
|
134
|
-
|
135
|
-
# No need to propagate verbose; ToolExecutor and others fetch from runtime_config
|
136
|
-
|
137
|
-
try:
|
138
|
-
response = profile_manager.agent.chat(
|
139
|
-
messages,
|
140
|
-
max_rounds=max_rounds,
|
141
|
-
message_handler=message_handler,
|
142
|
-
spinner=True,
|
143
|
-
)
|
144
|
-
except KeyboardInterrupt:
|
145
|
-
message_handler.handle_message(
|
146
|
-
{"type": "info", "message": "Request interrupted. Returning to prompt."}
|
147
|
-
)
|
148
|
-
continue
|
149
|
-
except ProviderError as e:
|
150
|
-
message_handler.handle_message(
|
151
|
-
{"type": "error", "message": f"Provider error: {e}"}
|
152
|
-
)
|
153
|
-
continue
|
154
|
-
except EmptyResponseError as e:
|
155
|
-
message_handler.handle_message({"type": "error", "message": f"Error: {e}"})
|
156
|
-
continue
|
157
|
-
last_elapsed = time.time() - start_time
|
158
|
-
|
159
|
-
usage = response.get("usage")
|
160
|
-
last_usage_info_ref["value"] = usage
|
161
|
-
|
162
|
-
# Save conversation and input history
|
163
|
-
save_chat_state(messages, mem_history, last_usage_info_ref["value"])
|
@@ -1,38 +0,0 @@
|
|
1
|
-
from .session_manager import (
|
2
|
-
load_last_conversation,
|
3
|
-
load_input_history,
|
4
|
-
save_conversation,
|
5
|
-
save_input_history,
|
6
|
-
)
|
7
|
-
from prompt_toolkit.history import InMemoryHistory
|
8
|
-
|
9
|
-
|
10
|
-
def load_chat_state(continue_session: bool):
|
11
|
-
messages = []
|
12
|
-
last_usage_info = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
|
13
|
-
last_elapsed = None
|
14
|
-
history_list = load_input_history()
|
15
|
-
mem_history = InMemoryHistory()
|
16
|
-
for item in history_list:
|
17
|
-
mem_history.append_string(item)
|
18
|
-
if continue_session:
|
19
|
-
msgs, prompts, usage = load_last_conversation()
|
20
|
-
messages = msgs
|
21
|
-
last_usage_info = usage
|
22
|
-
mem_history = InMemoryHistory()
|
23
|
-
for item in prompts:
|
24
|
-
mem_history.append_string(item)
|
25
|
-
state = {
|
26
|
-
"messages": messages,
|
27
|
-
"mem_history": mem_history,
|
28
|
-
"history_list": history_list,
|
29
|
-
"last_usage_info": last_usage_info,
|
30
|
-
"last_elapsed": last_elapsed,
|
31
|
-
}
|
32
|
-
return state
|
33
|
-
|
34
|
-
|
35
|
-
def save_chat_state(messages, mem_history, last_usage_info):
|
36
|
-
prompts = [h for h in mem_history.get_strings()]
|
37
|
-
save_conversation(messages, prompts, last_usage_info)
|
38
|
-
save_input_history(prompts)
|
@@ -1,37 +0,0 @@
|
|
1
|
-
from prompt_toolkit.history import InMemoryHistory
|
2
|
-
import os
|
3
|
-
|
4
|
-
|
5
|
-
def handle_start(console, state, **kwargs):
|
6
|
-
|
7
|
-
save_path = os.path.join(".janito", "last_conversation.json")
|
8
|
-
|
9
|
-
# Clear the terminal screen
|
10
|
-
os.system("cls" if os.name == "nt" else "clear")
|
11
|
-
|
12
|
-
# Clear in-memory conversation and prompt history
|
13
|
-
state["messages"].clear()
|
14
|
-
state["history_list"].clear()
|
15
|
-
state["mem_history"] = InMemoryHistory()
|
16
|
-
state["last_usage_info"] = {
|
17
|
-
"prompt_tokens": 0,
|
18
|
-
"completion_tokens": 0,
|
19
|
-
"total_tokens": 0,
|
20
|
-
}
|
21
|
-
state["last_elapsed"] = None
|
22
|
-
|
23
|
-
# Delete saved conversation file if exists
|
24
|
-
if os.path.exists(save_path):
|
25
|
-
try:
|
26
|
-
os.remove(save_path)
|
27
|
-
console.print(
|
28
|
-
"[bold yellow]Deleted saved conversation history.[/bold yellow]"
|
29
|
-
)
|
30
|
-
except Exception as e:
|
31
|
-
console.print(
|
32
|
-
f"[bold red]Failed to delete saved conversation:[/bold red] {e}"
|
33
|
-
)
|
34
|
-
|
35
|
-
console.print(
|
36
|
-
"[bold green]Conversation history has been started (context reset).[/bold green]"
|
37
|
-
)
|