janito 1.6.0__py3-none-any.whl → 1.8.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 (117) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/config.py +3 -3
  3. janito/agent/config_defaults.py +3 -2
  4. janito/agent/conversation.py +73 -27
  5. janito/agent/conversation_api.py +104 -4
  6. janito/agent/conversation_exceptions.py +6 -0
  7. janito/agent/conversation_tool_calls.py +17 -3
  8. janito/agent/event.py +24 -0
  9. janito/agent/event_dispatcher.py +24 -0
  10. janito/agent/event_handler_protocol.py +5 -0
  11. janito/agent/event_system.py +15 -0
  12. janito/agent/message_handler.py +4 -1
  13. janito/agent/message_handler_protocol.py +5 -0
  14. janito/agent/openai_client.py +5 -6
  15. janito/agent/openai_schema_generator.py +23 -4
  16. janito/agent/platform_discovery.py +90 -0
  17. janito/agent/profile_manager.py +34 -110
  18. janito/agent/queued_message_handler.py +22 -3
  19. janito/agent/rich_message_handler.py +3 -1
  20. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
  21. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
  22. janito/agent/test_handler_protocols.py +47 -0
  23. janito/agent/tests/__init__.py +1 -0
  24. janito/agent/tool_base.py +1 -1
  25. janito/agent/tool_executor.py +109 -0
  26. janito/agent/tool_registry.py +3 -75
  27. janito/agent/tool_use_tracker.py +46 -0
  28. janito/agent/tools/__init__.py +11 -8
  29. janito/agent/tools/ask_user.py +26 -12
  30. janito/agent/tools/create_directory.py +50 -18
  31. janito/agent/tools/create_file.py +60 -29
  32. janito/agent/tools/dir_walk_utils.py +16 -0
  33. janito/agent/tools/fetch_url.py +10 -11
  34. janito/agent/tools/find_files.py +49 -40
  35. janito/agent/tools/get_lines.py +60 -25
  36. janito/agent/tools/memory.py +48 -0
  37. janito/agent/tools/move_file.py +72 -23
  38. janito/agent/tools/outline_file/__init__.py +85 -0
  39. janito/agent/tools/outline_file/formatting.py +20 -0
  40. janito/agent/tools/outline_file/markdown_outline.py +14 -0
  41. janito/agent/tools/outline_file/python_outline.py +71 -0
  42. janito/agent/tools/present_choices.py +62 -0
  43. janito/agent/tools/present_choices_test.py +18 -0
  44. janito/agent/tools/remove_directory.py +31 -26
  45. janito/agent/tools/remove_file.py +31 -13
  46. janito/agent/tools/replace_text_in_file.py +135 -36
  47. janito/agent/tools/run_bash_command.py +113 -97
  48. janito/agent/tools/run_powershell_command.py +169 -0
  49. janito/agent/tools/run_python_command.py +53 -29
  50. janito/agent/tools/search_outline.py +17 -0
  51. janito/agent/tools/search_text.py +208 -0
  52. janito/agent/tools/tools_utils.py +47 -4
  53. janito/agent/tools/utils.py +14 -15
  54. janito/agent/tools/validate_file_syntax.py +163 -0
  55. janito/cli/_print_config.py +1 -1
  56. janito/cli/arg_parser.py +36 -4
  57. janito/cli/config_commands.py +1 -1
  58. janito/cli/logging_setup.py +7 -2
  59. janito/cli/main.py +97 -3
  60. janito/cli/runner/__init__.py +0 -2
  61. janito/cli/runner/_termweb_log_utils.py +17 -0
  62. janito/cli/runner/cli_main.py +121 -89
  63. janito/cli/runner/config.py +6 -4
  64. janito/cli/termweb_starter.py +73 -0
  65. janito/cli_chat_shell/chat_loop.py +52 -13
  66. janito/cli_chat_shell/chat_state.py +1 -1
  67. janito/cli_chat_shell/chat_ui.py +2 -3
  68. janito/cli_chat_shell/commands/__init__.py +17 -6
  69. janito/cli_chat_shell/commands/{history_reset.py → history_start.py} +13 -5
  70. janito/cli_chat_shell/commands/lang.py +16 -0
  71. janito/cli_chat_shell/commands/prompt.py +42 -0
  72. janito/cli_chat_shell/commands/session_control.py +36 -1
  73. janito/cli_chat_shell/commands/sum.py +49 -0
  74. janito/cli_chat_shell/commands/termweb_log.py +86 -0
  75. janito/cli_chat_shell/commands/utility.py +5 -2
  76. janito/cli_chat_shell/commands/verbose.py +29 -0
  77. janito/cli_chat_shell/load_prompt.py +47 -8
  78. janito/cli_chat_shell/session_manager.py +9 -1
  79. janito/cli_chat_shell/shell_command_completer.py +20 -0
  80. janito/cli_chat_shell/ui.py +110 -93
  81. janito/i18n/__init__.py +35 -0
  82. janito/i18n/messages.py +23 -0
  83. janito/i18n/pt.py +46 -0
  84. janito/rich_utils.py +43 -43
  85. janito/termweb/app.py +95 -0
  86. janito/termweb/static/editor.html +238 -0
  87. janito/termweb/static/editor.html.bak +238 -0
  88. janito/termweb/static/explorer.html.bak +59 -0
  89. janito/termweb/static/favicon.ico +0 -0
  90. janito/termweb/static/favicon.ico.bak +0 -0
  91. janito/termweb/static/index.html +55 -0
  92. janito/termweb/static/index.html.bak +55 -0
  93. janito/termweb/static/index.html.bak.bak +175 -0
  94. janito/termweb/static/landing.html.bak +36 -0
  95. janito/termweb/static/termicon.svg +1 -0
  96. janito/termweb/static/termweb.css +235 -0
  97. janito/termweb/static/termweb.css.bak +286 -0
  98. janito/termweb/static/termweb.js +187 -0
  99. janito/termweb/static/termweb.js.bak +187 -0
  100. janito/termweb/static/termweb.js.bak.bak +157 -0
  101. janito/termweb/static/termweb_quickopen.js +135 -0
  102. janito/termweb/static/termweb_quickopen.js.bak +125 -0
  103. janito/web/app.py +10 -13
  104. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/METADATA +73 -32
  105. janito-1.8.0.dist-info/RECORD +127 -0
  106. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
  107. janito/agent/tool_registry_core.py +0 -2
  108. janito/agent/tools/get_file_outline.py +0 -117
  109. janito/agent/tools/py_compile_file.py +0 -40
  110. janito/agent/tools/replace_file.py +0 -51
  111. janito/agent/tools/search_files.py +0 -71
  112. janito/cli/runner/scan.py +0 -44
  113. janito/cli_chat_shell/commands/system.py +0 -73
  114. janito-1.6.0.dist-info/RECORD +0 -81
  115. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
  116. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
  117. {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
@@ -4,34 +4,68 @@ from .chat_ui import setup_prompt_session, print_welcome_message
4
4
  from .commands import handle_command
5
5
  from janito.agent.conversation_exceptions import EmptyResponseError, ProviderError
6
6
 
7
-
8
- def start_chat_shell(profile_manager, continue_session=False, max_rounds=50):
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
9
23
  agent = profile_manager.agent
10
24
  message_handler = RichMessageHandler()
11
25
  console = message_handler.console
12
26
 
13
27
  # Load state
14
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
15
33
  messages = state["messages"]
16
34
  mem_history = state["mem_history"]
17
- last_usage_info = state["last_usage_info"]
35
+ last_usage_info_ref = {"value": state["last_usage_info"]}
18
36
  last_elapsed = state["last_elapsed"]
19
37
 
20
- # Add system prompt if needed
21
- if profile_manager.system_prompt_template and not any(
22
- m.get("role") == "system" for m in messages
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)
23
48
  ):
24
49
  messages.insert(0, {"role": "system", "content": agent.system_prompt_template})
25
50
 
26
51
  print_welcome_message(console, continued=continue_session)
27
52
 
28
53
  session = setup_prompt_session(
29
- messages, last_usage_info, last_elapsed, mem_history, profile_manager, agent
54
+ messages, last_usage_info_ref, last_elapsed, mem_history, profile_manager, agent
30
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"]
31
61
 
32
62
  while True:
33
63
  try:
34
- if state.get("paste_mode"):
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"):
35
69
  console.print("")
36
70
  user_input = session.prompt("Multiline> ", multiline=True)
37
71
  was_paste_mode = True
@@ -40,7 +74,7 @@ def start_chat_shell(profile_manager, continue_session=False, max_rounds=50):
40
74
  from prompt_toolkit.formatted_text import HTML
41
75
 
42
76
  user_input = session.prompt(
43
- HTML("<prompt>💬 </prompt>"), multiline=False
77
+ HTML("<inputline>💬 </inputline>"), multiline=False
44
78
  )
45
79
  was_paste_mode = False
46
80
  except EOFError:
@@ -51,7 +85,10 @@ def start_chat_shell(profile_manager, continue_session=False, max_rounds=50):
51
85
  try:
52
86
  confirm = (
53
87
  session.prompt(
54
- HTML("<prompt>Do you really want to exit? (y/n): </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
+ )
55
92
  )
56
93
  .strip()
57
94
  .lower()
@@ -95,8 +132,10 @@ def start_chat_shell(profile_manager, continue_session=False, max_rounds=50):
95
132
 
96
133
  start_time = time.time()
97
134
 
135
+ # No need to propagate verbose; ToolExecutor and others fetch from runtime_config
136
+
98
137
  try:
99
- response = profile_manager.agent.handle_conversation(
138
+ response = profile_manager.agent.chat(
100
139
  messages,
101
140
  max_rounds=max_rounds,
102
141
  message_handler=message_handler,
@@ -118,7 +157,7 @@ def start_chat_shell(profile_manager, continue_session=False, max_rounds=50):
118
157
  last_elapsed = time.time() - start_time
119
158
 
120
159
  usage = response.get("usage")
121
- last_usage_info = usage
160
+ last_usage_info_ref["value"] = usage
122
161
 
123
162
  # Save conversation and input history
124
- save_chat_state(messages, mem_history, last_usage_info)
163
+ save_chat_state(messages, mem_history, last_usage_info_ref["value"])
@@ -9,7 +9,7 @@ from prompt_toolkit.history import InMemoryHistory
9
9
 
10
10
  def load_chat_state(continue_session: bool):
11
11
  messages = []
12
- last_usage_info = None
12
+ last_usage_info = {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
13
13
  last_elapsed = None
14
14
  history_list = load_input_history()
15
15
  mem_history = InMemoryHistory()
@@ -5,7 +5,7 @@ from janito.agent.runtime_config import runtime_config
5
5
 
6
6
 
7
7
  def setup_prompt_session(
8
- messages, last_usage_info, last_elapsed, mem_history, profile_manager, agent
8
+ messages, last_usage_info_ref, last_elapsed, mem_history, profile_manager, agent
9
9
  ):
10
10
  model_name = getattr(agent, "model", None)
11
11
 
@@ -13,7 +13,7 @@ def setup_prompt_session(
13
13
  return messages
14
14
 
15
15
  def get_usage():
16
- return last_usage_info
16
+ return last_usage_info_ref["value"]
17
17
 
18
18
  def get_elapsed():
19
19
  return last_elapsed
@@ -32,7 +32,6 @@ def setup_prompt_session(
32
32
  )
33
33
  else (runtime_config.get("role") or effective_config.get("role"))
34
34
  ),
35
- style_ref=lambda: getattr(profile_manager, "interaction_style", "default"),
36
35
  ),
37
36
  mem_history,
38
37
  )
@@ -1,13 +1,19 @@
1
1
  from .session import handle_continue, handle_history
2
- from .system import handle_system, handle_role, handle_style
2
+ from .prompt import handle_prompt, handle_role
3
3
  from .session_control import handle_exit, handle_restart
4
4
  from .utility import handle_help, handle_clear, handle_multi
5
+ from .termweb_log import handle_termweb_log_tail, handle_termweb_status
6
+ from .sum import handle_sum
5
7
  from .config import handle_reload
6
- from .history_reset import handle_reset
8
+ from .history_start import handle_start
7
9
  from ..config_shell import handle_config_shell
10
+ from .verbose import handle_verbose
11
+ from .lang import handle_lang
8
12
  from janito.agent.runtime_config import runtime_config
9
13
 
10
14
  COMMAND_HANDLERS = {
15
+ "/termweb-logs": handle_termweb_log_tail,
16
+ "/termweb-status": handle_termweb_status,
11
17
  "/history": handle_history,
12
18
  "/continue": handle_continue,
13
19
  "/exit": handle_exit,
@@ -15,19 +21,23 @@ COMMAND_HANDLERS = {
15
21
  "/restart": handle_restart,
16
22
  "/help": handle_help,
17
23
  "/multi": handle_multi,
18
- "/system": handle_system,
24
+ "/prompt": handle_prompt,
25
+ "/verbose": handle_verbose,
19
26
  }
20
27
 
21
28
  if not runtime_config.get("vanilla_mode", False):
22
29
  COMMAND_HANDLERS["/role"] = handle_role
23
30
 
31
+
32
+ COMMAND_HANDLERS["/lang"] = handle_lang
33
+
24
34
  COMMAND_HANDLERS.update(
25
35
  {
36
+ "/sum": handle_sum,
26
37
  "/clear": handle_clear,
27
- "/reset": handle_reset,
38
+ "/start": handle_start,
28
39
  "/config": handle_config_shell,
29
40
  "/reload": handle_reload,
30
- "/style": handle_style,
31
41
  }
32
42
  )
33
43
 
@@ -38,7 +48,8 @@ def handle_command(command, console, **kwargs):
38
48
  args = parts[1:]
39
49
  handler = COMMAND_HANDLERS.get(cmd)
40
50
  if handler:
41
- return handler(console, *args, **kwargs)
51
+ # Pass args as a keyword argument for handlers that expect it
52
+ return handler(console, args=args, **kwargs)
42
53
  console.print(
43
54
  f"[bold red]Invalid command: {cmd}. Type /help for a list of commands.[/bold red]"
44
55
  )
@@ -2,14 +2,22 @@ from prompt_toolkit.history import InMemoryHistory
2
2
  import os
3
3
 
4
4
 
5
- def handle_reset(console, state, **kwargs):
5
+ def handle_start(console, state, **kwargs):
6
+
6
7
  save_path = os.path.join(".janito", "last_conversation.json")
7
8
 
9
+ # Clear the terminal screen
10
+ os.system("cls" if os.name == "nt" else "clear")
11
+
8
12
  # Clear in-memory conversation and prompt history
9
13
  state["messages"].clear()
10
14
  state["history_list"].clear()
11
15
  state["mem_history"] = InMemoryHistory()
12
- state["last_usage_info"] = None
16
+ state["last_usage_info"] = {
17
+ "prompt_tokens": 0,
18
+ "completion_tokens": 0,
19
+ "total_tokens": 0,
20
+ }
13
21
  state["last_elapsed"] = None
14
22
 
15
23
  # Delete saved conversation file if exists
@@ -23,7 +31,7 @@ def handle_reset(console, state, **kwargs):
23
31
  console.print(
24
32
  f"[bold red]Failed to delete saved conversation:[/bold red] {e}"
25
33
  )
26
- else:
27
- console.print("[bold yellow]No saved conversation to delete.[/bold yellow]")
28
34
 
29
- console.print("[bold green]Conversation history has been reset.[/bold green]")
35
+ console.print(
36
+ "[bold green]Conversation history has been started (context reset).[/bold green]"
37
+ )
@@ -0,0 +1,16 @@
1
+ from janito.agent.runtime_config import runtime_config
2
+ import janito.i18n as i18n
3
+
4
+
5
+ def handle_lang(console, args=None, **kwargs):
6
+ if not args or len(args) == 0:
7
+ console.print(
8
+ "[bold yellow]Uso: /lang [código_idioma] (ex: pt, en, es)[/bold yellow]"
9
+ )
10
+ return
11
+ lang_code = args[0]
12
+ runtime_config.set("lang", lang_code)
13
+ i18n.set_locale(lang_code)
14
+ console.print(
15
+ f"[bold green]Idioma alterado para:[/bold green] [cyan]{lang_code}[/cyan]"
16
+ )
@@ -0,0 +1,42 @@
1
+ from janito.agent.runtime_config import runtime_config
2
+
3
+
4
+ def handle_prompt(console, **kwargs):
5
+ profile_manager = kwargs.get("profile_manager")
6
+ prompt = profile_manager.system_prompt_template if profile_manager else None
7
+ if not prompt and profile_manager:
8
+ prompt = profile_manager.system_prompt_template
9
+ console.print(f"[bold magenta]System Prompt:[/bold magenta]\n{prompt}")
10
+
11
+
12
+ def handle_role(console, *args, **kwargs):
13
+ state = kwargs.get("state")
14
+ profile_manager = kwargs.get("profile_manager")
15
+ if not args:
16
+ console.print("[bold red]Usage: /role <new role description>[/bold red]")
17
+ return
18
+ new_role = " ".join(args)
19
+ if profile_manager:
20
+ profile_manager.set_role(new_role)
21
+ # Update system message in conversation
22
+ found = False
23
+ for msg in state["messages"]:
24
+ if msg.get("role") == "system":
25
+ msg["content"] = (
26
+ profile_manager.system_prompt_template if profile_manager else new_role
27
+ )
28
+ found = True
29
+ break
30
+ if not found:
31
+ state["messages"].insert(0, {"role": "system", "content": new_role})
32
+ # Also store the raw role string
33
+ if profile_manager:
34
+ setattr(profile_manager, "role_name", new_role)
35
+ runtime_config.set("role", new_role)
36
+ console.print(f"[bold green]System role updated to:[/bold green] {new_role}")
37
+
38
+
39
+ def handle_profile(console, *args, **kwargs):
40
+ """/profile - Show the current and available Agent Profile (only 'base' is supported)"""
41
+ console.print("[bold green]Current profile:[/bold green] base")
42
+ console.print("[bold yellow]Available profiles:[/bold yellow]\n- base")
@@ -1,5 +1,40 @@
1
1
  import os
2
2
  import sys
3
+ import subprocess
4
+
5
+
6
+ def restart_cli():
7
+ # Clean up prompt_toolkit session if active
8
+ try:
9
+ from janito.cli_chat_shell import chat_loop
10
+
11
+ session = getattr(chat_loop, "active_prompt_session", None)
12
+ if session is not None and hasattr(session, "app"):
13
+ session.app.exit()
14
+ except Exception:
15
+ pass # Ignore cleanup errors
16
+
17
+ if os.name == "nt":
18
+ if (
19
+ "powershell" in os.environ.get("SHELL", "").lower()
20
+ or "pwsh" in sys.executable.lower()
21
+ ):
22
+ args = [
23
+ "powershell",
24
+ "-Command",
25
+ "Start-Process",
26
+ sys.executable,
27
+ "-ArgumentList",
28
+ "'-m','janito"
29
+ + ("','" + "','".join(sys.argv[1:]) if sys.argv[1:] else "")
30
+ + "'",
31
+ ]
32
+ subprocess.Popen(args)
33
+ else:
34
+ subprocess.Popen([sys.executable, "-m", "janito"] + sys.argv[1:])
35
+ sys.exit(0)
36
+ else:
37
+ os.execv(sys.executable, [sys.executable, "-m", "janito"] + sys.argv[1:])
3
38
 
4
39
 
5
40
  def handle_exit(console, **kwargs):
@@ -9,4 +44,4 @@ def handle_exit(console, **kwargs):
9
44
 
10
45
  def handle_restart(console, **kwargs):
11
46
  console.print("[bold yellow]Restarting CLI...[/bold yellow]")
12
- os.execv(sys.executable, [sys.executable, "-m", "janito"] + sys.argv[1:])
47
+ restart_cli()
@@ -0,0 +1,49 @@
1
+ def handle_sum(console, state, **kwargs):
2
+ """
3
+ Summarize the current chat history and replace it with a summary message.
4
+ """
5
+ agent = kwargs.get("agent")
6
+ if agent is None:
7
+ console.print("[bold red]Agent not provided to /sum command.[/bold red]")
8
+ return
9
+
10
+ messages = state.get("messages", [])
11
+ if not messages or len(messages) < 2:
12
+ console.print(
13
+ "[bold yellow]Not enough conversation to summarize.[/bold yellow]"
14
+ )
15
+ return
16
+
17
+ # Find the system message if present
18
+ system_msg = next((m for m in messages if m.get("role") == "system"), None)
19
+
20
+ # Prepare summary prompt
21
+ summary_prompt = {
22
+ "role": "user",
23
+ "content": "Summarize the following conversation in a concise paragraph for context. Only output the summary, do not include any tool calls or formatting.",
24
+ }
25
+ # Exclude system messages for the summary context
26
+ convo_for_summary = [m for m in messages if m.get("role") != "system"]
27
+ summary_messages = [summary_prompt] + convo_for_summary
28
+
29
+ try:
30
+ summary_response = agent.chat(summary_messages, spinner=True, max_tokens=256)
31
+ summary_text = (
32
+ summary_response["content"]
33
+ if isinstance(summary_response, dict)
34
+ else str(summary_response)
35
+ )
36
+ except Exception as e:
37
+ console.print(f"[bold red]Error during summarization: {e}[/bold red]")
38
+ return
39
+
40
+ # Rebuild conversation history
41
+ new_history = []
42
+ if system_msg:
43
+ new_history.append(system_msg)
44
+ new_history.append({"role": "assistant", "content": summary_text})
45
+ state["messages"] = new_history
46
+
47
+ console.print(
48
+ "[bold green]Conversation summarized and history replaced with summary.[/bold green]"
49
+ )
@@ -0,0 +1,86 @@
1
+ import http.client
2
+ from rich.console import Console
3
+ from janito.agent.runtime_config import runtime_config
4
+
5
+
6
+ def is_termweb_running(port):
7
+ """Check if termweb is running by making an HTTP request to the root endpoint."""
8
+ try:
9
+ conn = http.client.HTTPConnection("localhost", port, timeout=0.5)
10
+ conn.request("GET", "/")
11
+ resp = conn.getresponse()
12
+ return resp.status == 200
13
+ except Exception:
14
+ return False
15
+
16
+
17
+ def handle_termweb_log_tail(console: Console, *args, state=None, **kwargs):
18
+ lines = 20
19
+ if args and args[0].isdigit():
20
+ lines = int(args[0])
21
+ stdout_path = state.get("termweb_stdout_path") if state else None
22
+ stderr_path = state.get("termweb_stderr_path") if state else None
23
+ if not stdout_path and not stderr_path:
24
+ console.print(
25
+ "[yellow][termweb] No termweb log files found for this session.[/yellow]"
26
+ )
27
+ return
28
+ if stdout_path:
29
+ try:
30
+ with open(stdout_path, encoding="utf-8") as f:
31
+ stdout_lines = f.readlines()[-lines:]
32
+ if stdout_lines:
33
+ console.print(
34
+ f"[yellow][termweb][stdout] Tail of {stdout_path}:\n"
35
+ + "".join(stdout_lines)
36
+ )
37
+ except Exception:
38
+ pass
39
+ if stderr_path:
40
+ try:
41
+ with open(stderr_path, encoding="utf-8") as f:
42
+ stderr_lines = f.readlines()[-lines:]
43
+ if stderr_lines:
44
+ console.print(
45
+ f"[red][termweb][stderr] Tail of {stderr_path}:\n"
46
+ + "".join(stderr_lines)
47
+ )
48
+ except Exception:
49
+ pass
50
+ if (not stdout_path or not stdout_lines) and (not stderr_path or not stderr_lines):
51
+ console.print("[termweb] No output or errors captured in logs.")
52
+
53
+
54
+ def handle_termweb_status(console: Console, *args, state=None, **kwargs):
55
+ if state is None:
56
+ console.print(
57
+ "[red]No shell state available. Cannot determine termweb status.[/red]"
58
+ )
59
+ return
60
+ port = state.get("termweb_port")
61
+ port_source = "state"
62
+ if not port:
63
+ port = runtime_config.get("termweb_port")
64
+ port_source = "runtime_config"
65
+ pid = state.get("termweb_pid")
66
+ stdout_path = state.get("termweb_stdout_path")
67
+ stderr_path = state.get("termweb_stderr_path")
68
+ running = False
69
+ if port:
70
+ running = is_termweb_running(port)
71
+ console.print("[bold cyan]TermWeb Server Status:[/bold cyan]")
72
+ console.print(f" Running: {'[green]Yes[/green]' if running else '[red]No[/red]'}")
73
+ if pid:
74
+ console.print(f" PID: {pid}")
75
+ if port:
76
+ console.print(f" Port: {port} (from {port_source})")
77
+ url = f"http://localhost:{port}/"
78
+ console.print(f" URL: [underline blue]{url}[/underline blue]")
79
+ else:
80
+ console.print(
81
+ " [yellow]No port configured in state or runtime_config.[/yellow]"
82
+ )
83
+ if stdout_path:
84
+ console.print(f" Stdout log: {stdout_path}")
85
+ if stderr_path:
86
+ console.print(f" Stderr log: {stderr_path}")
@@ -6,12 +6,15 @@ def handle_help(console, **kwargs):
6
6
  /restart - Restart the CLI
7
7
  /help - Show this help message
8
8
  /continue - Restore last saved conversation
9
- /reset - Reset conversation history
10
- /system - Show the system prompt
9
+ /start - Reset conversation history
10
+ /prompt - Show the system prompt
11
11
  /role - Change the system role
12
12
  /clear - Clear the terminal screen
13
13
  /multi - Provide multiline input as next message
14
14
  /config - Show or set configuration (see: /config show, /config set local|global key=value)
15
+ /termweb-logs - Show the last lines of the latest termweb logs
16
+ /termweb-status - Show status information about the running termweb server
17
+ /verbose [on|off] - Show or set verbose mode for this session
15
18
  """
16
19
  )
17
20
 
@@ -0,0 +1,29 @@
1
+ from janito.agent.runtime_config import runtime_config
2
+
3
+
4
+ def handle_verbose(console, state, **kwargs):
5
+ """
6
+ /verbose [on|off]
7
+ Shows or sets verbose mode for the current shell session.
8
+ """
9
+ args = kwargs.get("args", [])
10
+ verbose = runtime_config.get("verbose", False)
11
+ if not args:
12
+ status = "ON" if verbose else "OFF"
13
+ console.print(
14
+ f"[bold green]/verbose:[/bold green] Verbose mode is currently [bold]{status}[/bold]."
15
+ )
16
+ return
17
+ arg = args[0].lower()
18
+ if arg == "on":
19
+ runtime_config["verbose"] = True
20
+ console.print(
21
+ "[bold green]/verbose:[/bold green] Verbose mode is now [bold]ON[/bold]."
22
+ )
23
+ elif arg == "off":
24
+ runtime_config["verbose"] = False
25
+ console.print(
26
+ "[bold green]/verbose:[/bold green] Verbose mode is now [bold]OFF[/bold]."
27
+ )
28
+ else:
29
+ console.print("[bold red]Usage:[/bold red] /verbose [on|off]")
@@ -1,18 +1,57 @@
1
1
  import os
2
+ import importlib.resources
2
3
 
3
4
 
4
5
  def load_prompt(filename=None):
5
6
  """
6
7
  Load the system prompt from a file. If filename is None, use the default prompt file.
7
8
  Returns the prompt string.
9
+ Tries source path first, then falls back to importlib.resources for installed packages.
8
10
  """
11
+ default_rel_path = os.path.normpath(
12
+ os.path.join(
13
+ os.path.dirname(__file__),
14
+ "../agent/templates/profiles/system_prompt_template_default.j2",
15
+ )
16
+ )
9
17
  if filename is None:
10
- # Default prompt file path (can be customized)
11
- filename = os.path.join(
12
- os.path.dirname(__file__), "../agent/templates/system_prompt_template.j2"
18
+ filename = default_rel_path
19
+
20
+ # Try loading from source path first
21
+ abs_path = os.path.abspath(filename)
22
+ if os.path.exists(abs_path):
23
+ with open(abs_path, "r", encoding="utf-8") as f:
24
+ return f.read()
25
+
26
+ # If not found, try importlib.resources (for installed package)
27
+ try:
28
+ # Remove leading directories up to 'janito/agent/templates'
29
+ # and get the relative path inside the package
30
+ resource_path = filename
31
+ for marker in ["janito/agent/templates/", "agent/templates/"]:
32
+ idx = filename.replace("\\", "/").find(marker)
33
+ if idx != -1:
34
+ resource_path = filename[idx + len("janito/agent/templates/") :]
35
+ break
36
+
37
+ # Try loading from janito.agent.templates.profiles
38
+ if resource_path.startswith("profiles/"):
39
+ package = "janito.agent.templates.profiles"
40
+ resource = resource_path[len("profiles/") :]
41
+ elif resource_path.startswith("features/"):
42
+ package = "janito.agent.templates.features"
43
+ resource = resource_path[len("features/") :]
44
+ else:
45
+ package = "janito.agent.templates"
46
+ resource = resource_path
47
+
48
+ with (
49
+ importlib.resources.files(package)
50
+ .joinpath(resource)
51
+ .open("r", encoding="utf-8") as f
52
+ ):
53
+ return f.read()
54
+ except Exception as e:
55
+ raise FileNotFoundError(
56
+ f"Prompt file not found at '{abs_path}' or in package resources: {e}"
13
57
  )
14
- filename = os.path.abspath(filename)
15
- if not os.path.exists(filename):
16
- raise FileNotFoundError(f"Prompt file not found: {filename}")
17
- with open(filename, "r", encoding="utf-8") as f:
18
- return f.read()
@@ -27,8 +27,16 @@ def save_conversation(
27
27
  ):
28
28
  os.makedirs(os.path.dirname(path), exist_ok=True)
29
29
  data = {"messages": messages, "prompts": prompts, "last_usage_info": usage_info}
30
+
31
+ def usage_serializer(obj):
32
+ if hasattr(obj, "to_dict"):
33
+ return obj.to_dict()
34
+ if hasattr(obj, "__dict__"):
35
+ return obj.__dict__
36
+ return str(obj)
37
+
30
38
  with open(path, "w", encoding="utf-8") as f:
31
- json.dump(data, f, indent=2)
39
+ json.dump(data, f, indent=2, default=usage_serializer)
32
40
 
33
41
 
34
42
  def load_input_history():
@@ -0,0 +1,20 @@
1
+ from prompt_toolkit.completion import Completer, Completion
2
+
3
+
4
+ class ShellCommandCompleter(Completer):
5
+ def __init__(self):
6
+ # Import here to avoid circular import at module level
7
+ from janito.cli_chat_shell.commands import COMMAND_HANDLERS
8
+
9
+ # Only commands starting with '/'
10
+ self.commands = sorted(
11
+ [cmd for cmd in COMMAND_HANDLERS.keys() if cmd.startswith("/")]
12
+ )
13
+
14
+ def get_completions(self, document, complete_event):
15
+ text = document.text_before_cursor
16
+ if text.startswith("/"):
17
+ prefix = text[1:]
18
+ for cmd in self.commands:
19
+ if cmd[1:].startswith(prefix):
20
+ yield Completion(cmd, start_position=-(len(prefix) + 1))