ara-cli 0.1.10.0__py3-none-any.whl → 0.1.13.3__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 (140) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +270 -103
  3. ara_cli/ara_command_action.py +106 -63
  4. ara_cli/ara_config.py +187 -128
  5. ara_cli/ara_subcommands/__init__.py +0 -0
  6. ara_cli/ara_subcommands/autofix.py +26 -0
  7. ara_cli/ara_subcommands/chat.py +27 -0
  8. ara_cli/ara_subcommands/classifier_directory.py +16 -0
  9. ara_cli/ara_subcommands/common.py +100 -0
  10. ara_cli/ara_subcommands/config.py +221 -0
  11. ara_cli/ara_subcommands/convert.py +43 -0
  12. ara_cli/ara_subcommands/create.py +75 -0
  13. ara_cli/ara_subcommands/delete.py +22 -0
  14. ara_cli/ara_subcommands/extract.py +22 -0
  15. ara_cli/ara_subcommands/fetch.py +41 -0
  16. ara_cli/ara_subcommands/fetch_agents.py +22 -0
  17. ara_cli/ara_subcommands/fetch_scripts.py +19 -0
  18. ara_cli/ara_subcommands/fetch_templates.py +19 -0
  19. ara_cli/ara_subcommands/list.py +139 -0
  20. ara_cli/ara_subcommands/list_tags.py +25 -0
  21. ara_cli/ara_subcommands/load.py +48 -0
  22. ara_cli/ara_subcommands/prompt.py +136 -0
  23. ara_cli/ara_subcommands/read.py +47 -0
  24. ara_cli/ara_subcommands/read_status.py +20 -0
  25. ara_cli/ara_subcommands/read_user.py +20 -0
  26. ara_cli/ara_subcommands/reconnect.py +27 -0
  27. ara_cli/ara_subcommands/rename.py +22 -0
  28. ara_cli/ara_subcommands/scan.py +14 -0
  29. ara_cli/ara_subcommands/set_status.py +22 -0
  30. ara_cli/ara_subcommands/set_user.py +22 -0
  31. ara_cli/ara_subcommands/template.py +16 -0
  32. ara_cli/artefact_autofix.py +154 -63
  33. ara_cli/artefact_converter.py +256 -0
  34. ara_cli/artefact_models/artefact_model.py +106 -25
  35. ara_cli/artefact_models/artefact_templates.py +20 -10
  36. ara_cli/artefact_models/epic_artefact_model.py +11 -2
  37. ara_cli/artefact_models/feature_artefact_model.py +31 -1
  38. ara_cli/artefact_models/userstory_artefact_model.py +15 -3
  39. ara_cli/artefact_scan.py +2 -2
  40. ara_cli/chat.py +283 -80
  41. ara_cli/chat_agent/__init__.py +0 -0
  42. ara_cli/chat_agent/agent_process_manager.py +155 -0
  43. ara_cli/chat_script_runner/__init__.py +0 -0
  44. ara_cli/chat_script_runner/script_completer.py +23 -0
  45. ara_cli/chat_script_runner/script_finder.py +41 -0
  46. ara_cli/chat_script_runner/script_lister.py +36 -0
  47. ara_cli/chat_script_runner/script_runner.py +36 -0
  48. ara_cli/chat_web_search/__init__.py +0 -0
  49. ara_cli/chat_web_search/web_search.py +263 -0
  50. ara_cli/commands/agent_run_command.py +98 -0
  51. ara_cli/commands/fetch_agents_command.py +106 -0
  52. ara_cli/commands/fetch_scripts_command.py +43 -0
  53. ara_cli/commands/fetch_templates_command.py +39 -0
  54. ara_cli/commands/fetch_templates_commands.py +39 -0
  55. ara_cli/commands/list_agents_command.py +39 -0
  56. ara_cli/commands/read_command.py +17 -4
  57. ara_cli/completers.py +180 -0
  58. ara_cli/constants.py +2 -0
  59. ara_cli/directory_navigator.py +37 -4
  60. ara_cli/file_loaders/text_file_loader.py +2 -2
  61. ara_cli/global_file_lister.py +5 -15
  62. ara_cli/llm_utils.py +58 -0
  63. ara_cli/prompt_chat.py +20 -4
  64. ara_cli/prompt_extractor.py +199 -76
  65. ara_cli/prompt_handler.py +160 -59
  66. ara_cli/tag_extractor.py +38 -18
  67. ara_cli/template_loader.py +3 -2
  68. ara_cli/template_manager.py +52 -21
  69. ara_cli/templates/global-scripts/hello_global.py +1 -0
  70. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  71. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  72. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  73. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  74. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  75. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  76. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  77. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  78. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  79. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  80. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  81. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  82. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  83. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  84. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  85. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  86. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  87. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  88. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  89. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  90. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  91. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  92. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  93. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  94. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  95. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  96. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  97. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  98. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  99. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  100. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  101. ara_cli/version.py +1 -1
  102. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +34 -1
  103. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +123 -54
  104. tests/test_ara_command_action.py +31 -19
  105. tests/test_ara_config.py +177 -90
  106. tests/test_artefact_autofix.py +170 -97
  107. tests/test_artefact_autofix_integration.py +495 -0
  108. tests/test_artefact_converter.py +357 -0
  109. tests/test_artefact_extraction.py +564 -0
  110. tests/test_artefact_scan.py +1 -1
  111. tests/test_chat.py +162 -126
  112. tests/test_chat_givens_images.py +603 -0
  113. tests/test_chat_script_runner.py +454 -0
  114. tests/test_global_file_lister.py +1 -1
  115. tests/test_llm_utils.py +164 -0
  116. tests/test_prompt_chat.py +343 -0
  117. tests/test_prompt_extractor.py +683 -0
  118. tests/test_prompt_handler.py +12 -4
  119. tests/test_tag_extractor.py +19 -13
  120. tests/test_web_search.py +467 -0
  121. ara_cli/ara_command_parser.py +0 -605
  122. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  123. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  124. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  125. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  126. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  127. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  128. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  129. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  130. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  131. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  132. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  133. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  134. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  135. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  136. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  137. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  138. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
  139. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
  140. {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
ara_cli/chat.py CHANGED
@@ -1,36 +1,36 @@
1
1
  import os
2
- import errno
2
+ import sys
3
3
  import argparse
4
4
  import cmd2
5
5
 
6
6
  from ara_cli.prompt_handler import send_prompt
7
7
 
8
+ from . import (
9
+ CATEGORY_CHAT_CONTROL,
10
+ CATEGORY_LLM_CONTROL,
11
+ CATEGORY_SCRIPT_CONTROL,
12
+ CATEGORY_AGENT_CONTROL,
13
+ )
14
+ from . import ROLE_PROMPT, ROLE_RESPONSE, INTRO
15
+ from . import BINARY_TYPE_MAPPING, DOCUMENT_TYPE_EXTENSIONS
16
+
8
17
  from . import error_handler
9
18
  from ara_cli.error_handler import AraError, AraConfigurationError
10
19
 
11
20
  from ara_cli.file_loaders.document_file_loader import DocumentFileLoader
12
21
  from ara_cli.file_loaders.binary_file_loader import BinaryFileLoader
13
22
  from ara_cli.file_loaders.text_file_loader import TextFileLoader
14
-
15
-
16
- extract_parser = argparse.ArgumentParser()
17
- extract_parser.add_argument(
18
- "-f", "--force", action="store_true", help="Force extraction"
19
- )
20
- extract_parser.add_argument(
21
- "-w",
22
- "--write",
23
- action="store_true",
24
- help="Overwrite existing files without using LLM for merging.",
23
+ from ara_cli.chat_agent.agent_process_manager import AgentProcessManager
24
+
25
+ from ara_cli.chat_script_runner.script_runner import ScriptRunner
26
+ from ara_cli.chat_script_runner.script_completer import ScriptCompleter
27
+ from ara_cli.chat_script_runner.script_lister import ScriptLister
28
+ from ara_cli.chat_web_search.web_search import (
29
+ perform_web_search_completion,
30
+ is_web_search_supported,
31
+ get_supported_models_message,
25
32
  )
26
33
 
27
- load_parser = argparse.ArgumentParser()
28
- load_parser.add_argument("file_name", nargs="?", default="", help="File to load")
29
- load_parser.add_argument(
30
- "--load-images",
31
- action="store_true",
32
- help="Extract and describe images from documents",
33
- )
34
34
 
35
35
  extract_parser = argparse.ArgumentParser()
36
36
  extract_parser.add_argument(
@@ -53,39 +53,6 @@ load_parser.add_argument(
53
53
 
54
54
 
55
55
  class Chat(cmd2.Cmd):
56
- CATEGORY_CHAT_CONTROL = "Chat control commands"
57
- CATEGORY_LLM_CONTROL = "Language model controls"
58
-
59
- INTRO = """/***************************************/
60
- araarar
61
- aa ara
62
- aa aa aara
63
- a araarar
64
- a ar ar
65
- aa ara
66
- a a
67
- a aa
68
- a a
69
- ar aa aa
70
- (c) ara chat by talsen team
71
- aa aa
72
- aa a
73
- a aa
74
- aa
75
- /***************************************/
76
- Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat mode):"""
77
-
78
- ROLE_PROMPT = "ara prompt"
79
- ROLE_RESPONSE = "ara response"
80
-
81
- BINARY_TYPE_MAPPING = {
82
- ".png": "image/png",
83
- ".jpg": "image/jpeg",
84
- ".jpeg": "image/jpeg",
85
- }
86
-
87
- DOCUMENT_TYPE_EXTENSIONS = [".docx", ".doc", ".odt", ".pdf"]
88
-
89
56
  def __init__(
90
57
  self,
91
58
  chat_name: str,
@@ -93,6 +60,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
93
60
  enable_commands: list[str] | None = None,
94
61
  ):
95
62
  from ara_cli.template_loader import TemplateLoader
63
+
96
64
  shortcuts = dict(cmd2.DEFAULT_SHORTCUTS)
97
65
  if enable_commands:
98
66
  enable_commands.append("quit") # always allow quitting
@@ -116,15 +84,21 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
116
84
  self.disable_commands(commands_to_disable)
117
85
 
118
86
  self.prompt = "ara> "
119
- self.intro = Chat.INTRO
87
+ self.intro = INTRO
120
88
 
121
- self.default_chat_content = f"# {Chat.ROLE_PROMPT}:\n"
89
+ self.default_chat_content = f"# {ROLE_PROMPT}:\n"
122
90
  self.chat_name = self.setup_chat(chat_name, reset)
123
91
  self.chat_name = os.path.abspath(self.chat_name)
124
92
  self.chat_history = []
125
93
  self.message_buffer = []
126
94
  self.config = self._retrieve_ara_config()
127
95
  self.template_loader = TemplateLoader(chat_instance=self)
96
+ self.script_runner = ScriptRunner(chat_instance=self)
97
+ self.script_lister = ScriptLister()
98
+ self.script_completer = ScriptCompleter()
99
+
100
+ # Initialize agent process manager
101
+ self.agent_manager = AgentProcessManager(self)
128
102
 
129
103
  def disable_commands(self, commands: list[str]):
130
104
  for command in commands:
@@ -146,6 +120,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
146
120
  self.aliases["h"] = "help"
147
121
  self.aliases["n"] = "NEW"
148
122
  self.aliases["e"] = "EXTRACT"
123
+ self.aliases["SEARCH"] = "search"
149
124
  self.aliases["l"] = "LOAD"
150
125
  self.aliases["lr"] = "LOAD_RULES"
151
126
  self.aliases["li"] = "LOAD_INTENTION"
@@ -153,6 +128,11 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
153
128
  self.aliases["lg"] = "LOAD_GIVENS"
154
129
  self.aliases["lb"] = "LOAD_BLUEPRINT"
155
130
  self.aliases["lt"] = "LOAD_TEMPLATE"
131
+ self.aliases["rpy"] = "run_pyscript"
132
+ self.aliases["a"] = "AGENT_RUN"
133
+ self.aliases["al"] = "LIST_AGENTS"
134
+ self.aliases["la"] = "LIST_AGENTS"
135
+ self.aliases["AGENT_LIST"] = "LIST_AGENTS"
156
136
 
157
137
  def setup_chat(self, chat_name, reset: bool = None):
158
138
  if os.path.exists(chat_name):
@@ -167,9 +147,12 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
167
147
  chat_file_short = os.path.split(chat_file)[-1]
168
148
 
169
149
  if reset is None:
170
- user_input = input(
171
- f"{chat_file_short} already exists. Do you want to reset the chat? (y/N): "
150
+ print(
151
+ f"{chat_file_short} already exists. Do you want to reset the chat? (y/N): ",
152
+ end="",
153
+ flush=True,
172
154
  )
155
+ user_input = sys.stdin.readline().strip()
173
156
  if user_input.lower() == "y":
174
157
  self.create_empty_chat_file(chat_file)
175
158
  if reset:
@@ -197,13 +180,14 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
197
180
  print(f"File {file_name} not found.")
198
181
  return False
199
182
  return method(self, file_path, *args, **kwargs)
183
+
200
184
  return wrapper
201
185
 
202
186
  @staticmethod
203
187
  def get_last_role_marker(lines):
204
188
  if not lines:
205
189
  return
206
- role_markers = [f"# {Chat.ROLE_PROMPT}:", f"# {Chat.ROLE_RESPONSE}"]
190
+ role_markers = [f"# {ROLE_PROMPT}:", f"# {ROLE_RESPONSE}"]
207
191
  for line in reversed(lines):
208
192
  stripped_line = line.strip()
209
193
  if stripped_line.startswith(tuple(role_markers)):
@@ -265,8 +249,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
265
249
  import re
266
250
  from ara_cli.prompt_handler import prepend_system_prompt
267
251
 
268
- prompt_marker = f"# {self.ROLE_PROMPT}:"
269
- response_marker = f"# {self.ROLE_RESPONSE}:"
252
+ prompt_marker = f"# {ROLE_PROMPT}:"
253
+ response_marker = f"# {ROLE_RESPONSE}:"
270
254
 
271
255
  split_pattern = re.compile(f"({prompt_marker}|{response_marker})")
272
256
 
@@ -302,7 +286,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
302
286
  def send_message(self):
303
287
  self.chat_history = self.load_chat_history(self.chat_name)
304
288
  prompt_to_send = self.assemble_prompt()
305
- role_marker = f"# {Chat.ROLE_RESPONSE}:"
289
+ role_marker = f"# {ROLE_RESPONSE}:"
306
290
 
307
291
  with open(self.chat_name, "a+", encoding="utf-8") as file:
308
292
  last_line = self.get_last_line(file)
@@ -351,9 +335,9 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
351
335
  def find_last_reply_index(self, lines: list[str]):
352
336
  index_to_remove = None
353
337
  for i, line in enumerate(reversed(lines)):
354
- if line.strip().startswith(f"# {Chat.ROLE_PROMPT}"):
338
+ if line.strip().startswith(f"# {ROLE_PROMPT}"):
355
339
  break
356
- if line.strip().startswith(f"# {Chat.ROLE_RESPONSE}"):
340
+ if line.strip().startswith(f"# {ROLE_RESPONSE}"):
357
341
  index_to_remove = len(lines) - i - 1
358
342
  break
359
343
  return index_to_remove
@@ -378,7 +362,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
378
362
  def add_prompt_tag_if_needed(self, chat_file: str):
379
363
  with open(chat_file, "r", encoding="utf-8") as file:
380
364
  lines = file.readlines()
381
- prompt_tag = f"# {Chat.ROLE_PROMPT}:"
365
+
366
+ prompt_tag = f"# {ROLE_PROMPT}:"
382
367
  if Chat.get_last_role_marker(lines) == prompt_tag:
383
368
  return
384
369
  append = prompt_tag
@@ -388,7 +373,6 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
388
373
  with open(chat_file, "a", encoding="utf-8") as file:
389
374
  file.write(append)
390
375
 
391
- # @file_exists_check
392
376
  def load_text_file(
393
377
  self,
394
378
  file_path,
@@ -406,7 +390,6 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
406
390
  extract_images=extract_images,
407
391
  )
408
392
 
409
- # @file_exists_check
410
393
  def load_binary_file(
411
394
  self, file_path, mime_type: str, prefix: str = "", suffix: str = ""
412
395
  ):
@@ -420,7 +403,6 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
420
403
  reader = MarkdownReader(file_path)
421
404
  return reader.read(extract_images=extract_images)
422
405
 
423
- # @file_exists_check
424
406
  def load_document_file(
425
407
  self,
426
408
  file_path: str,
@@ -446,8 +428,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
446
428
  block_delimiter: str = "",
447
429
  extract_images: bool = False,
448
430
  ):
449
- binary_type_mapping = Chat.BINARY_TYPE_MAPPING
450
- document_type_extensions = Chat.DOCUMENT_TYPE_EXTENSIONS
431
+ binary_type_mapping = BINARY_TYPE_MAPPING
432
+ document_type_extensions = DOCUMENT_TYPE_EXTENSIONS
451
433
 
452
434
  file_type = None
453
435
  file_name_lower = file_name.lower()
@@ -486,7 +468,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
486
468
  files.sort()
487
469
  for i, file in enumerate(files):
488
470
  print(f"{i + 1}: {os.path.basename(file)}")
489
- choice = input("Please choose a file to load (enter number): ")
471
+ print("Please choose a file to load (enter number): ", end="", flush=True)
472
+ choice = sys.stdin.readline().strip()
490
473
  try:
491
474
  choice_index = int(choice) - 1
492
475
  if choice_index < 0 or choice_index >= len(files):
@@ -512,6 +495,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
512
495
 
513
496
  def do_quit(self, _):
514
497
  """Exit ara-cli"""
498
+ self.agent_manager.cleanup_agent_process()
515
499
  print("Chat ended")
516
500
  self.last_result = True
517
501
  return True
@@ -581,7 +565,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
581
565
  import glob
582
566
 
583
567
  if file_name == "":
584
- file_name = input("What file do you want to load? ")
568
+ print("What file do you want to load? ", end="", flush=True)
569
+ file_name = sys.stdin.readline().strip()
585
570
  file_pattern = os.path.join(os.path.dirname(self.chat_name), file_name)
586
571
  matching_files = glob.glob(file_pattern)
587
572
  if not matching_files:
@@ -592,7 +577,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
592
577
  return matching_files
593
578
 
594
579
  def load_image(self, file_name: str, prefix: str = "", suffix: str = ""):
595
- binary_type_mapping = Chat.BINARY_TYPE_MAPPING
580
+ binary_type_mapping = BINARY_TYPE_MAPPING
596
581
 
597
582
  file_type = None
598
583
  file_name_lower = file_name.lower()
@@ -621,6 +606,62 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
621
606
  return False
622
607
  return True
623
608
 
609
+ @cmd2.with_category(CATEGORY_CHAT_CONTROL)
610
+ def do_search(self, query: str):
611
+ """Perform a web search and append the results to the chat.
612
+ Usage: search <query>
613
+ """
614
+ if not query:
615
+ self.poutput("Please provide a search query.")
616
+ return
617
+
618
+ # Check if web search is supported by the current model
619
+ from ara_cli.prompt_handler import LLMSingleton
620
+
621
+ chat_instance = LLMSingleton.get_instance()
622
+ config_parameters = chat_instance.get_config_by_purpose("default")
623
+ default_llm = config_parameters.get("model")
624
+
625
+ is_supported, _ = is_web_search_supported(default_llm)
626
+ if not is_supported:
627
+ self.poutput(get_supported_models_message(default_llm))
628
+ return
629
+
630
+ self.add_prompt_tag_if_needed(self.chat_name)
631
+
632
+ role_marker = f"# Web Search Results for '{query}':"
633
+
634
+ with open(self.chat_name, "a+", encoding="utf-8") as file:
635
+ last_line = self.get_last_line(file)
636
+
637
+ self.poutput(role_marker)
638
+
639
+ if not last_line.startswith(role_marker):
640
+ if last_line:
641
+ file.write("\n")
642
+ file.write(role_marker + "\n")
643
+
644
+ try:
645
+ # perform_web_search_completion now returns a generator or a string
646
+ search_result = perform_web_search_completion(query)
647
+
648
+ if isinstance(search_result, str):
649
+ # If it's a string, it's an error/info message
650
+ self.poutput(search_result)
651
+ file.write(search_result + "\n")
652
+ else:
653
+ # Otherwise, it's a generator, stream the content
654
+ for chunk in search_result:
655
+ chunk_content = chunk.choices[0].delta.content
656
+ if not chunk_content:
657
+ continue
658
+ self.poutput(chunk_content, end="")
659
+ file.write(chunk_content)
660
+ file.flush()
661
+ self.poutput("")
662
+ except Exception as e:
663
+ error_handler.report_error(e)
664
+
624
665
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
625
666
  def do_LOAD_IMAGE(self, file_name):
626
667
  """Load an image file and append it to chat file. Can be given the file name in-line. Will attempt to find the file relative to chat file first, then treat the given path as absolute"""
@@ -638,7 +679,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
638
679
  # Determine mime type
639
680
  file_type = None
640
681
  file_path_lower = file_path.lower()
641
- for extension, mime_type in Chat.BINARY_TYPE_MAPPING.items():
682
+ for extension, mime_type in BINARY_TYPE_MAPPING.items():
642
683
  if file_path_lower.endswith(extension):
643
684
  file_type = mime_type
644
685
  break
@@ -745,7 +786,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
745
786
  def do_NEW(self, chat_name):
746
787
  """Create a new chat. Optionally provide a chat name in-line: NEW new_chat"""
747
788
  if chat_name == "":
748
- chat_name = input("What should be the new chat name? ")
789
+ print("What should be the new chat name? ", end="", flush=True)
790
+ chat_name = sys.stdin.readline().strip()
749
791
  current_directory = os.path.dirname(self.chat_name)
750
792
  chat_file_path = os.path.join(current_directory, chat_name)
751
793
  self.__init__(chat_file_path)
@@ -758,27 +800,35 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
758
800
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
759
801
  def do_CLEAR(self, _):
760
802
  """Clear the chat and the file containing it"""
761
- user_input = input("Are you sure you want to clear the chat? (y/N): ")
803
+ print("Are you sure you want to clear the chat? (y/N): ", end="", flush=True)
804
+ user_input = sys.stdin.readline().strip()
762
805
  if user_input.lower() != "y":
763
806
  return
764
807
  self.create_empty_chat_file(self.chat_name)
765
808
  self.chat_history = self.load_chat_history(self.chat_name)
809
+ self.message_buffer.clear()
766
810
  print(f"Cleared content of {self.chat_name}")
767
811
 
768
812
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
769
813
  def do_LOAD_RULES(self, rules_name):
770
814
  """Load rules from ./prompt.data/*.rules.md or from a specified template directory if an argument is given. Specify global/<rules_template> to access globally defined rules templates"""
771
- self.template_loader.load_template(rules_name, "rules", self.chat_name, "*.rules.md")
815
+ self.template_loader.load_template(
816
+ rules_name, "rules", self.chat_name, "*.rules.md"
817
+ )
772
818
 
773
819
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
774
820
  def do_LOAD_INTENTION(self, intention_name):
775
821
  """Load intention from ./prompt.data/*.intention.md or from a specified template directory if an argument is given. Specify global/<intention_template> to access globally defined intention templates"""
776
- self.template_loader.load_template(intention_name, "intention", self.chat_name, "*.intention.md")
822
+ self.template_loader.load_template(
823
+ intention_name, "intention", self.chat_name, "*.intention.md"
824
+ )
777
825
 
778
826
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
779
827
  def do_LOAD_COMMANDS(self, commands_name):
780
828
  """Load commands from ./prompt.data/*.commands.md or from a specified template directory if an argument is given. Specify global/<commands_template> to access globally defined commands templates"""
781
- self.template_loader.load_template(commands_name, "commands", self.chat_name, "*.commands.md")
829
+ self.template_loader.load_template(
830
+ commands_name, "commands", self.chat_name, "*.commands.md"
831
+ )
782
832
 
783
833
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
784
834
  def do_LOAD_BLUEPRINT(self, blueprint_name):
@@ -952,7 +1002,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
952
1002
  def do_SEND(self, _):
953
1003
  """Send prompt to the LLM"""
954
1004
  message = "\n".join(self.message_buffer)
955
- self.save_message(Chat.ROLE_PROMPT, message)
1005
+ self.save_message(ROLE_PROMPT, message)
956
1006
  self.send_message()
957
1007
 
958
1008
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
@@ -1071,7 +1121,9 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
1071
1121
 
1072
1122
  def _template_completer(self, text: str, template_type: str) -> list[str]:
1073
1123
  """Generic completer for different template types."""
1074
- available_templates = self.template_loader.get_available_templates(template_type, os.path.dirname(self.chat_name))
1124
+ available_templates = self.template_loader.get_available_templates(
1125
+ template_type, os.path.dirname(self.chat_name)
1126
+ )
1075
1127
  if not text:
1076
1128
  return available_templates
1077
1129
  return [t for t in available_templates if t.startswith(text)]
@@ -1090,4 +1142,155 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
1090
1142
 
1091
1143
  def complete_LOAD_BLUEPRINT(self, text, line, begidx, endidx):
1092
1144
  """Completer for the LOAD_BLUEPRINT command."""
1093
- return self._template_completer(text, "blueprint")
1145
+ return self._template_completer(text, "blueprint")
1146
+
1147
+ def _select_script_from_list(
1148
+ self, scripts: list[str], not_found_message: str, prompt: str
1149
+ ) -> str | None:
1150
+ """Displays a list of scripts and prompts the user to select one."""
1151
+ if not scripts:
1152
+ self.poutput(not_found_message)
1153
+ return None
1154
+
1155
+ # Sort the scripts alphabetically by their basename for consistent display
1156
+ # Create a list of (basename, full_script_name) tuples for sorting and later retrieval
1157
+ scripts_with_basenames = [(os.path.basename(s), s) for s in scripts]
1158
+ scripts_with_basenames.sort(
1159
+ key=lambda x: x[0].lower()
1160
+ ) # Sort by lowercase basename
1161
+
1162
+ for i, (basename, full_script_name) in enumerate(scripts_with_basenames):
1163
+ self.poutput(f"{i + 1}: {basename}")
1164
+
1165
+ try:
1166
+ choice = input(prompt)
1167
+ choice_index = int(choice) - 1
1168
+ if 0 <= choice_index < len(scripts_with_basenames):
1169
+ # Return the full script name from the sorted list
1170
+ return scripts_with_basenames[choice_index][1]
1171
+ else:
1172
+ self.poutput("Invalid choice. Aborting.")
1173
+ return None
1174
+ except (ValueError, EOFError):
1175
+ self.poutput("Invalid input. Aborting.")
1176
+ return None
1177
+
1178
+ @cmd2.with_category(CATEGORY_SCRIPT_CONTROL)
1179
+ def do_run_pyscript(self, args):
1180
+ """Run a python script from the chat.
1181
+ Usage: run_pyscript <script_name> [args...]
1182
+ """
1183
+ script_name, script_args = self._parse_run_pyscript_args(args)
1184
+
1185
+ # If no script name provided, list available scripts grouped by type
1186
+ if not script_name:
1187
+ self._list_available_scripts()
1188
+ return
1189
+
1190
+ script_to_run = self._resolve_script_to_run(script_name, script_args)
1191
+
1192
+ if not script_to_run:
1193
+ return
1194
+
1195
+ # Pass arguments to script runner
1196
+ output = self.script_runner.run_script(script_to_run, script_args)
1197
+ if output:
1198
+ self.poutput(output.strip())
1199
+
1200
+ def _list_available_scripts(self):
1201
+ """Lists available scripts grouped by type (global and custom)."""
1202
+ global_scripts = self.script_lister.get_global_scripts()
1203
+ custom_scripts = self.script_lister.get_custom_scripts()
1204
+
1205
+ if not global_scripts and not custom_scripts:
1206
+ self.poutput("No scripts found.")
1207
+ return
1208
+
1209
+ self.poutput("Available scripts:")
1210
+ self.poutput("")
1211
+
1212
+ if custom_scripts:
1213
+ self.poutput("Custom scripts:")
1214
+ for script in sorted(custom_scripts):
1215
+ self.poutput(f" {script}")
1216
+ self.poutput("")
1217
+
1218
+ if global_scripts:
1219
+ self.poutput("Global scripts:")
1220
+ for script in sorted(global_scripts):
1221
+ self.poutput(f" global/{script}")
1222
+
1223
+ def _parse_run_pyscript_args(self, args):
1224
+ """Parses arguments for run_pyscript command."""
1225
+ import shlex
1226
+
1227
+ if not args:
1228
+ return "", []
1229
+
1230
+ # args is a cmd2.Statement (subclass of str), so we can use it directly
1231
+ full_args = str(args)
1232
+ # Use shlex to split arguments, enabling quoted args support
1233
+ split_args = shlex.split(full_args)
1234
+ if not split_args:
1235
+ return "", []
1236
+
1237
+ script_name = split_args[0]
1238
+ script_args = split_args[1:] if len(split_args) > 1 else []
1239
+ return script_name, script_args
1240
+
1241
+ def _resolve_script_to_run(self, script_name, script_args):
1242
+ """Resolves the script name to run."""
1243
+ return script_name
1244
+
1245
+ def complete_run_pyscript(self, text, line, begidx, endidx):
1246
+ """Completer for the run_pyscript command."""
1247
+ # Get all scripts: ['custom.py', 'global/global.py']
1248
+ available_scripts = self.script_lister.get_all_scripts()
1249
+
1250
+ # Add special commands
1251
+ special_commands = [
1252
+ # "global/"
1253
+ # "*", "global/*"
1254
+ ]
1255
+
1256
+ possible_completions = sorted(list(set(available_scripts + special_commands)))
1257
+
1258
+ # Filter based on what the user has typed
1259
+ return [s for s in possible_completions if s.startswith(text)]
1260
+
1261
+ # ===== AGENT CONTROL COMMANDS =====
1262
+
1263
+ @cmd2.with_category(CATEGORY_AGENT_CONTROL)
1264
+ def do_LIST_AGENTS(self, _):
1265
+ """Lists all available executable binary agents."""
1266
+ from ara_cli.commands.list_agents_command import ListAgentsCommand
1267
+
1268
+ command = ListAgentsCommand(chat_instance=self)
1269
+ command.execute()
1270
+
1271
+ @cmd2.with_category(CATEGORY_AGENT_CONTROL)
1272
+ def do_AGENT_RUN(self, args):
1273
+ """Run a binary agent interactively from the 'ara/.araconfig/agents' directory.
1274
+ Usage: AGENT_RUN <agent_name> [arg1] [arg2] ...
1275
+ Example:
1276
+ AGENT_RUN feature-creation -b .
1277
+ """
1278
+
1279
+ from ara_cli.commands.agent_run_command import AgentRunCommand
1280
+
1281
+ command = AgentRunCommand(self, args)
1282
+ command.execute()
1283
+
1284
+ def complete_AGENT_RUN(self, text, line, begidx, endidx):
1285
+ """Completer for AGENT_RUN command."""
1286
+ from ara_cli.commands.list_agents_command import list_available_binary_agents
1287
+
1288
+ parts = line.split()
1289
+ # This completer runs when the user is typing the first argument (the agent name)
1290
+ if len(parts) < 2 or (len(parts) == 2 and not line.endswith(" ")):
1291
+ available_agents = list_available_binary_agents(self)
1292
+ if not text:
1293
+ return available_agents
1294
+ return [a for a in available_agents if a.startswith(text)]
1295
+ # For subsequent arguments, we can offer file/directory completion
1296
+ return self.path_complete(text, line, begidx, endidx)
File without changes