ara-cli 0.1.10.5__py3-none-any.whl → 0.1.14.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 (151) hide show
  1. ara_cli/__init__.py +51 -6
  2. ara_cli/__main__.py +87 -75
  3. ara_cli/ara_command_action.py +189 -101
  4. ara_cli/ara_config.py +187 -128
  5. ara_cli/ara_subcommands/common.py +2 -2
  6. ara_cli/ara_subcommands/config.py +221 -0
  7. ara_cli/ara_subcommands/convert.py +107 -0
  8. ara_cli/ara_subcommands/fetch.py +41 -0
  9. ara_cli/ara_subcommands/fetch_agents.py +22 -0
  10. ara_cli/ara_subcommands/fetch_scripts.py +19 -0
  11. ara_cli/ara_subcommands/fetch_templates.py +15 -10
  12. ara_cli/ara_subcommands/list.py +97 -23
  13. ara_cli/ara_subcommands/prompt.py +266 -106
  14. ara_cli/artefact_autofix.py +117 -64
  15. ara_cli/artefact_converter.py +355 -0
  16. ara_cli/artefact_creator.py +41 -17
  17. ara_cli/artefact_lister.py +3 -3
  18. ara_cli/artefact_models/artefact_model.py +1 -1
  19. ara_cli/artefact_models/artefact_templates.py +0 -9
  20. ara_cli/artefact_models/feature_artefact_model.py +8 -8
  21. ara_cli/artefact_reader.py +62 -43
  22. ara_cli/artefact_scan.py +39 -17
  23. ara_cli/chat.py +300 -71
  24. ara_cli/chat_agent/__init__.py +0 -0
  25. ara_cli/chat_agent/agent_process_manager.py +155 -0
  26. ara_cli/chat_script_runner/__init__.py +0 -0
  27. ara_cli/chat_script_runner/script_completer.py +23 -0
  28. ara_cli/chat_script_runner/script_finder.py +41 -0
  29. ara_cli/chat_script_runner/script_lister.py +36 -0
  30. ara_cli/chat_script_runner/script_runner.py +36 -0
  31. ara_cli/chat_web_search/__init__.py +0 -0
  32. ara_cli/chat_web_search/web_search.py +263 -0
  33. ara_cli/children_contribution_updater.py +737 -0
  34. ara_cli/classifier.py +34 -0
  35. ara_cli/commands/agent_run_command.py +98 -0
  36. ara_cli/commands/fetch_agents_command.py +106 -0
  37. ara_cli/commands/fetch_scripts_command.py +43 -0
  38. ara_cli/commands/fetch_templates_command.py +39 -0
  39. ara_cli/commands/fetch_templates_commands.py +39 -0
  40. ara_cli/commands/list_agents_command.py +39 -0
  41. ara_cli/commands/load_command.py +4 -3
  42. ara_cli/commands/load_image_command.py +1 -1
  43. ara_cli/commands/read_command.py +23 -27
  44. ara_cli/completers.py +95 -35
  45. ara_cli/constants.py +2 -0
  46. ara_cli/directory_navigator.py +37 -4
  47. ara_cli/error_handler.py +26 -11
  48. ara_cli/file_loaders/document_reader.py +0 -178
  49. ara_cli/file_loaders/factories/__init__.py +0 -0
  50. ara_cli/file_loaders/factories/document_reader_factory.py +32 -0
  51. ara_cli/file_loaders/factories/file_loader_factory.py +27 -0
  52. ara_cli/file_loaders/file_loader.py +1 -30
  53. ara_cli/file_loaders/loaders/__init__.py +0 -0
  54. ara_cli/file_loaders/{document_file_loader.py → loaders/document_file_loader.py} +1 -1
  55. ara_cli/file_loaders/loaders/text_file_loader.py +47 -0
  56. ara_cli/file_loaders/readers/__init__.py +0 -0
  57. ara_cli/file_loaders/readers/docx_reader.py +49 -0
  58. ara_cli/file_loaders/readers/excel_reader.py +27 -0
  59. ara_cli/file_loaders/{markdown_reader.py → readers/markdown_reader.py} +1 -1
  60. ara_cli/file_loaders/readers/odt_reader.py +59 -0
  61. ara_cli/file_loaders/readers/pdf_reader.py +54 -0
  62. ara_cli/file_loaders/readers/pptx_reader.py +104 -0
  63. ara_cli/file_loaders/tools/__init__.py +0 -0
  64. ara_cli/llm_utils.py +58 -0
  65. ara_cli/output_suppressor.py +53 -0
  66. ara_cli/prompt_chat.py +20 -4
  67. ara_cli/prompt_extractor.py +47 -32
  68. ara_cli/prompt_handler.py +123 -17
  69. ara_cli/tag_extractor.py +8 -7
  70. ara_cli/template_loader.py +2 -1
  71. ara_cli/template_manager.py +52 -21
  72. ara_cli/templates/global-scripts/hello_global.py +1 -0
  73. ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
  74. ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
  75. ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
  76. ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
  77. ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
  78. ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
  79. ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
  80. ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
  81. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  82. ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
  83. ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
  84. ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
  85. ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
  86. ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
  87. ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
  88. ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
  89. ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
  90. ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
  91. ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
  92. ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
  93. ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
  94. ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
  95. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  96. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  97. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  98. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  99. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  100. ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
  101. ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
  102. ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
  103. ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
  104. ara_cli/version.py +1 -1
  105. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/METADATA +49 -11
  106. ara_cli-0.1.14.0.dist-info/RECORD +253 -0
  107. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/WHEEL +1 -1
  108. tests/test_ara_command_action.py +31 -19
  109. tests/test_ara_config.py +177 -90
  110. tests/test_artefact_autofix.py +170 -97
  111. tests/test_artefact_autofix_integration.py +495 -0
  112. tests/test_artefact_converter.py +312 -0
  113. tests/test_artefact_extraction.py +564 -0
  114. tests/test_artefact_lister.py +11 -8
  115. tests/test_chat.py +166 -130
  116. tests/test_chat_givens_images.py +603 -0
  117. tests/test_chat_script_runner.py +454 -0
  118. tests/test_children_contribution_updater.py +98 -0
  119. tests/test_document_loader_office.py +267 -0
  120. tests/test_llm_utils.py +164 -0
  121. tests/test_prompt_chat.py +343 -0
  122. tests/test_prompt_extractor.py +683 -0
  123. tests/test_prompt_handler.py +416 -214
  124. tests/test_setup_default_chat_prompt_mode.py +198 -0
  125. tests/test_tag_extractor.py +95 -49
  126. tests/test_web_search.py +467 -0
  127. ara_cli/file_loaders/document_readers.py +0 -233
  128. ara_cli/file_loaders/file_loaders.py +0 -123
  129. ara_cli/file_loaders/text_file_loader.py +0 -187
  130. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  131. ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
  132. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  133. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  134. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  135. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  136. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  137. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  138. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  139. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  140. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  141. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  142. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  143. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  144. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  145. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  146. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  147. ara_cli-0.1.10.5.dist-info/RECORD +0 -194
  148. /ara_cli/file_loaders/{binary_file_loader.py → loaders/binary_file_loader.py} +0 -0
  149. /ara_cli/file_loaders/{image_processor.py → tools/image_processor.py} +0 -0
  150. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/entry_points.txt +0 -0
  151. {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.14.0.dist-info}/top_level.txt +0 -0
ara_cli/chat.py CHANGED
@@ -1,16 +1,25 @@
1
1
  import os
2
- import errno
2
+ import sys
3
3
  import argparse
4
4
  import cmd2
5
5
 
6
- from ara_cli.prompt_handler import send_prompt
6
+ from . import (
7
+ CATEGORY_CHAT_CONTROL,
8
+ CATEGORY_LLM_CONTROL,
9
+ CATEGORY_SCRIPT_CONTROL,
10
+ CATEGORY_AGENT_CONTROL,
11
+ )
12
+ from . import ROLE_PROMPT, ROLE_RESPONSE, INTRO
13
+ from . import BINARY_TYPE_MAPPING, DOCUMENT_TYPE_EXTENSIONS
7
14
 
8
15
  from . import error_handler
9
16
  from ara_cli.error_handler import AraError, AraConfigurationError
10
17
 
11
- from ara_cli.file_loaders.document_file_loader import DocumentFileLoader
12
- from ara_cli.file_loaders.binary_file_loader import BinaryFileLoader
13
- from ara_cli.file_loaders.text_file_loader import TextFileLoader
18
+ from ara_cli.chat_agent.agent_process_manager import AgentProcessManager
19
+
20
+ from ara_cli.chat_script_runner.script_runner import ScriptRunner
21
+ from ara_cli.chat_script_runner.script_completer import ScriptCompleter
22
+ from ara_cli.chat_script_runner.script_lister import ScriptLister
14
23
 
15
24
 
16
25
  extract_parser = argparse.ArgumentParser()
@@ -34,39 +43,6 @@ load_parser.add_argument(
34
43
 
35
44
 
36
45
  class Chat(cmd2.Cmd):
37
- CATEGORY_CHAT_CONTROL = "Chat control commands"
38
- CATEGORY_LLM_CONTROL = "Language model controls"
39
-
40
- INTRO = """/***************************************/
41
- araarar
42
- aa ara
43
- aa aa aara
44
- a araarar
45
- a ar ar
46
- aa ara
47
- a a
48
- a aa
49
- a a
50
- ar aa aa
51
- (c) ara chat by talsen team
52
- aa aa
53
- aa a
54
- a aa
55
- aa
56
- /***************************************/
57
- Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat mode):"""
58
-
59
- ROLE_PROMPT = "ara prompt"
60
- ROLE_RESPONSE = "ara response"
61
-
62
- BINARY_TYPE_MAPPING = {
63
- ".png": "image/png",
64
- ".jpg": "image/jpeg",
65
- ".jpeg": "image/jpeg",
66
- }
67
-
68
- DOCUMENT_TYPE_EXTENSIONS = [".docx", ".doc", ".odt", ".pdf"]
69
-
70
46
  def __init__(
71
47
  self,
72
48
  chat_name: str,
@@ -74,6 +50,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
74
50
  enable_commands: list[str] | None = None,
75
51
  ):
76
52
  from ara_cli.template_loader import TemplateLoader
53
+
77
54
  shortcuts = dict(cmd2.DEFAULT_SHORTCUTS)
78
55
  if enable_commands:
79
56
  enable_commands.append("quit") # always allow quitting
@@ -97,15 +74,21 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
97
74
  self.disable_commands(commands_to_disable)
98
75
 
99
76
  self.prompt = "ara> "
100
- self.intro = Chat.INTRO
77
+ self.intro = INTRO
101
78
 
102
- self.default_chat_content = f"# {Chat.ROLE_PROMPT}:\n"
79
+ self.default_chat_content = f"# {ROLE_PROMPT}:\n"
103
80
  self.chat_name = self.setup_chat(chat_name, reset)
104
81
  self.chat_name = os.path.abspath(self.chat_name)
105
82
  self.chat_history = []
106
83
  self.message_buffer = []
107
84
  self.config = self._retrieve_ara_config()
108
85
  self.template_loader = TemplateLoader(chat_instance=self)
86
+ self.script_runner = ScriptRunner(chat_instance=self)
87
+ self.script_lister = ScriptLister()
88
+ self.script_completer = ScriptCompleter()
89
+
90
+ # Initialize agent process manager
91
+ self.agent_manager = AgentProcessManager(self)
109
92
 
110
93
  def disable_commands(self, commands: list[str]):
111
94
  for command in commands:
@@ -127,6 +110,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
127
110
  self.aliases["h"] = "help"
128
111
  self.aliases["n"] = "NEW"
129
112
  self.aliases["e"] = "EXTRACT"
113
+ self.aliases["SEARCH"] = "search"
130
114
  self.aliases["l"] = "LOAD"
131
115
  self.aliases["lr"] = "LOAD_RULES"
132
116
  self.aliases["li"] = "LOAD_INTENTION"
@@ -134,6 +118,11 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
134
118
  self.aliases["lg"] = "LOAD_GIVENS"
135
119
  self.aliases["lb"] = "LOAD_BLUEPRINT"
136
120
  self.aliases["lt"] = "LOAD_TEMPLATE"
121
+ self.aliases["rpy"] = "run_pyscript"
122
+ self.aliases["a"] = "AGENT_RUN"
123
+ self.aliases["al"] = "LIST_AGENTS"
124
+ self.aliases["la"] = "LIST_AGENTS"
125
+ self.aliases["AGENT_LIST"] = "LIST_AGENTS"
137
126
 
138
127
  def setup_chat(self, chat_name, reset: bool = None):
139
128
  if os.path.exists(chat_name):
@@ -148,9 +137,12 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
148
137
  chat_file_short = os.path.split(chat_file)[-1]
149
138
 
150
139
  if reset is None:
151
- user_input = input(
152
- f"{chat_file_short} already exists. Do you want to reset the chat? (y/N): "
140
+ print(
141
+ f"{chat_file_short} already exists. Do you want to reset the chat? (y/N): ",
142
+ end="",
143
+ flush=True,
153
144
  )
145
+ user_input = sys.stdin.readline().strip()
154
146
  if user_input.lower() == "y":
155
147
  self.create_empty_chat_file(chat_file)
156
148
  if reset:
@@ -178,13 +170,14 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
178
170
  print(f"File {file_name} not found.")
179
171
  return False
180
172
  return method(self, file_path, *args, **kwargs)
173
+
181
174
  return wrapper
182
175
 
183
176
  @staticmethod
184
177
  def get_last_role_marker(lines):
185
178
  if not lines:
186
179
  return
187
- role_markers = [f"# {Chat.ROLE_PROMPT}:", f"# {Chat.ROLE_RESPONSE}"]
180
+ role_markers = [f"# {ROLE_PROMPT}:", f"# {ROLE_RESPONSE}"]
188
181
  for line in reversed(lines):
189
182
  stripped_line = line.strip()
190
183
  if stripped_line.startswith(tuple(role_markers)):
@@ -246,8 +239,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
246
239
  import re
247
240
  from ara_cli.prompt_handler import prepend_system_prompt
248
241
 
249
- prompt_marker = f"# {self.ROLE_PROMPT}:"
250
- response_marker = f"# {self.ROLE_RESPONSE}:"
242
+ prompt_marker = f"# {ROLE_PROMPT}:"
243
+ response_marker = f"# {ROLE_RESPONSE}:"
251
244
 
252
245
  split_pattern = re.compile(f"({prompt_marker}|{response_marker})")
253
246
 
@@ -283,26 +276,29 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
283
276
  def send_message(self):
284
277
  self.chat_history = self.load_chat_history(self.chat_name)
285
278
  prompt_to_send = self.assemble_prompt()
286
- role_marker = f"# {Chat.ROLE_RESPONSE}:"
279
+ role_marker = f"# {ROLE_RESPONSE}:"
287
280
 
288
281
  with open(self.chat_name, "a+", encoding="utf-8") as file:
289
282
  last_line = self.get_last_line(file)
290
283
 
291
- print(role_marker)
284
+ self.poutput(role_marker)
292
285
 
293
286
  if not last_line.startswith(role_marker):
294
287
  if last_line:
295
288
  file.write("\n")
296
289
  file.write(role_marker + "\n")
290
+ file.flush()
291
+
292
+ from ara_cli.prompt_handler import send_prompt
297
293
 
298
294
  for chunk in send_prompt(prompt_to_send):
299
295
  chunk_content = chunk.choices[0].delta.content
300
296
  if not chunk_content:
301
297
  continue
302
- print(chunk_content, end="", flush=True)
298
+ self.poutput(chunk_content, end="")
303
299
  file.write(chunk_content)
304
300
  file.flush()
305
- print()
301
+ self.poutput("")
306
302
 
307
303
  self.message_buffer.clear()
308
304
 
@@ -332,9 +328,9 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
332
328
  def find_last_reply_index(self, lines: list[str]):
333
329
  index_to_remove = None
334
330
  for i, line in enumerate(reversed(lines)):
335
- if line.strip().startswith(f"# {Chat.ROLE_PROMPT}"):
331
+ if line.strip().startswith(f"# {ROLE_PROMPT}"):
336
332
  break
337
- if line.strip().startswith(f"# {Chat.ROLE_RESPONSE}"):
333
+ if line.strip().startswith(f"# {ROLE_RESPONSE}"):
338
334
  index_to_remove = len(lines) - i - 1
339
335
  break
340
336
  return index_to_remove
@@ -359,7 +355,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
359
355
  def add_prompt_tag_if_needed(self, chat_file: str):
360
356
  with open(chat_file, "r", encoding="utf-8") as file:
361
357
  lines = file.readlines()
362
- prompt_tag = f"# {Chat.ROLE_PROMPT}:"
358
+
359
+ prompt_tag = f"# {ROLE_PROMPT}:"
363
360
  if Chat.get_last_role_marker(lines) == prompt_tag:
364
361
  return
365
362
  append = prompt_tag
@@ -369,7 +366,6 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
369
366
  with open(chat_file, "a", encoding="utf-8") as file:
370
367
  file.write(append)
371
368
 
372
- # @file_exists_check
373
369
  def load_text_file(
374
370
  self,
375
371
  file_path,
@@ -378,6 +374,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
378
374
  block_delimiter: str = "",
379
375
  extract_images: bool = False,
380
376
  ):
377
+ from ara_cli.file_loaders.loaders.text_file_loader import TextFileLoader
378
+
381
379
  loader = TextFileLoader(self)
382
380
  return loader.load(
383
381
  file_path,
@@ -387,21 +385,21 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
387
385
  extract_images=extract_images,
388
386
  )
389
387
 
390
- # @file_exists_check
391
388
  def load_binary_file(
392
389
  self, file_path, mime_type: str, prefix: str = "", suffix: str = ""
393
390
  ):
391
+ from ara_cli.file_loaders.loaders.binary_file_loader import BinaryFileLoader
392
+
394
393
  loader = BinaryFileLoader(self)
395
394
  return loader.load(file_path, mime_type=mime_type, prefix=prefix, suffix=suffix)
396
395
 
397
396
  def read_markdown(self, file_path: str, extract_images: bool = False) -> str:
398
397
  """Read markdown file and optionally extract/describe images"""
399
- from ara_cli.file_loaders.text_file_loader import MarkdownReader
398
+ from ara_cli.file_loaders.readers.markdown_reader import MarkdownReader
400
399
 
401
400
  reader = MarkdownReader(file_path)
402
401
  return reader.read(extract_images=extract_images)
403
402
 
404
- # @file_exists_check
405
403
  def load_document_file(
406
404
  self,
407
405
  file_path: str,
@@ -410,6 +408,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
410
408
  block_delimiter: str = "```",
411
409
  extract_images: bool = False,
412
410
  ):
411
+ from ara_cli.file_loaders.loaders.document_file_loader import DocumentFileLoader
412
+
413
413
  loader = DocumentFileLoader(self)
414
414
  return loader.load(
415
415
  file_path,
@@ -427,8 +427,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
427
427
  block_delimiter: str = "",
428
428
  extract_images: bool = False,
429
429
  ):
430
- binary_type_mapping = Chat.BINARY_TYPE_MAPPING
431
- document_type_extensions = Chat.DOCUMENT_TYPE_EXTENSIONS
430
+ binary_type_mapping = BINARY_TYPE_MAPPING
431
+ document_type_extensions = DOCUMENT_TYPE_EXTENSIONS
432
432
 
433
433
  file_type = None
434
434
  file_name_lower = file_name.lower()
@@ -467,7 +467,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
467
467
  files.sort()
468
468
  for i, file in enumerate(files):
469
469
  print(f"{i + 1}: {os.path.basename(file)}")
470
- choice = input("Please choose a file to load (enter number): ")
470
+ print("Please choose a file to load (enter number): ", end="", flush=True)
471
+ choice = sys.stdin.readline().strip()
471
472
  try:
472
473
  choice_index = int(choice) - 1
473
474
  if choice_index < 0 or choice_index >= len(files):
@@ -493,6 +494,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
493
494
 
494
495
  def do_quit(self, _):
495
496
  """Exit ara-cli"""
497
+ self.agent_manager.cleanup_agent_process()
496
498
  print("Chat ended")
497
499
  self.last_result = True
498
500
  return True
@@ -546,7 +548,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
546
548
  def complete_LOAD(self, text, line, begidx, endidx):
547
549
  import glob
548
550
 
549
- return [x for x in glob.glob(text + "*")]
551
+ return [x for x in glob.glob(glob.escape(text) + "*")]
550
552
 
551
553
  def _retrieve_ara_config(self):
552
554
  from ara_cli.prompt_handler import ConfigManager
@@ -562,8 +564,13 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
562
564
  import glob
563
565
 
564
566
  if file_name == "":
565
- file_name = input("What file do you want to load? ")
567
+ print("What file do you want to load? ", end="", flush=True)
568
+ file_name = sys.stdin.readline().strip()
566
569
  file_pattern = os.path.join(os.path.dirname(self.chat_name), file_name)
570
+
571
+ if os.path.exists(file_pattern):
572
+ return [file_pattern]
573
+
567
574
  matching_files = glob.glob(file_pattern)
568
575
  if not matching_files:
569
576
  error_handler.report_error(
@@ -573,7 +580,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
573
580
  return matching_files
574
581
 
575
582
  def load_image(self, file_name: str, prefix: str = "", suffix: str = ""):
576
- binary_type_mapping = Chat.BINARY_TYPE_MAPPING
583
+ binary_type_mapping = BINARY_TYPE_MAPPING
577
584
 
578
585
  file_type = None
579
586
  file_name_lower = file_name.lower()
@@ -602,6 +609,67 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
602
609
  return False
603
610
  return True
604
611
 
612
+ @cmd2.with_category(CATEGORY_CHAT_CONTROL)
613
+ def do_search(self, query: str):
614
+ """Perform a web search and append the results to the chat.
615
+ Usage: search <query>
616
+ """
617
+ if not query:
618
+ self.poutput("Please provide a search query.")
619
+ return
620
+
621
+ # Check if web search is supported by the current model
622
+ from ara_cli.prompt_handler import LLMSingleton
623
+ from ara_cli.chat_web_search.web_search import (
624
+ perform_web_search_completion,
625
+ is_web_search_supported,
626
+ get_supported_models_message,
627
+ )
628
+
629
+ chat_instance = LLMSingleton.get_instance()
630
+ config_parameters = chat_instance.get_config_by_purpose("default")
631
+ default_llm = config_parameters.get("model")
632
+
633
+ is_supported, _ = is_web_search_supported(default_llm)
634
+ if not is_supported:
635
+ self.poutput(get_supported_models_message(default_llm))
636
+ return
637
+
638
+ self.add_prompt_tag_if_needed(self.chat_name)
639
+
640
+ role_marker = f"# Web Search Results for '{query}':"
641
+
642
+ with open(self.chat_name, "a+", encoding="utf-8") as file:
643
+ last_line = self.get_last_line(file)
644
+
645
+ self.poutput(role_marker)
646
+
647
+ if not last_line.startswith(role_marker):
648
+ if last_line:
649
+ file.write("\n")
650
+ file.write(role_marker + "\n")
651
+
652
+ try:
653
+ # perform_web_search_completion now returns a generator or a string
654
+ search_result = perform_web_search_completion(query)
655
+
656
+ if isinstance(search_result, str):
657
+ # If it's a string, it's an error/info message
658
+ self.poutput(search_result)
659
+ file.write(search_result + "\n")
660
+ else:
661
+ # Otherwise, it's a generator, stream the content
662
+ for chunk in search_result:
663
+ chunk_content = chunk.choices[0].delta.content
664
+ if not chunk_content:
665
+ continue
666
+ self.poutput(chunk_content, end="")
667
+ file.write(chunk_content)
668
+ file.flush()
669
+ self.poutput("")
670
+ except Exception as e:
671
+ error_handler.report_error(e)
672
+
605
673
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
606
674
  def do_LOAD_IMAGE(self, file_name):
607
675
  """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"""
@@ -619,7 +687,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
619
687
  # Determine mime type
620
688
  file_type = None
621
689
  file_path_lower = file_path.lower()
622
- for extension, mime_type in Chat.BINARY_TYPE_MAPPING.items():
690
+ for extension, mime_type in BINARY_TYPE_MAPPING.items():
623
691
  if file_path_lower.endswith(extension):
624
692
  file_type = mime_type
625
693
  break
@@ -726,7 +794,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
726
794
  def do_NEW(self, chat_name):
727
795
  """Create a new chat. Optionally provide a chat name in-line: NEW new_chat"""
728
796
  if chat_name == "":
729
- chat_name = input("What should be the new chat name? ")
797
+ print("What should be the new chat name? ", end="", flush=True)
798
+ chat_name = sys.stdin.readline().strip()
730
799
  current_directory = os.path.dirname(self.chat_name)
731
800
  chat_file_path = os.path.join(current_directory, chat_name)
732
801
  self.__init__(chat_file_path)
@@ -739,7 +808,8 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
739
808
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
740
809
  def do_CLEAR(self, _):
741
810
  """Clear the chat and the file containing it"""
742
- user_input = input("Are you sure you want to clear the chat? (y/N): ")
811
+ print("Are you sure you want to clear the chat? (y/N): ", end="", flush=True)
812
+ user_input = sys.stdin.readline().strip()
743
813
  if user_input.lower() != "y":
744
814
  return
745
815
  self.create_empty_chat_file(self.chat_name)
@@ -750,17 +820,23 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
750
820
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
751
821
  def do_LOAD_RULES(self, rules_name):
752
822
  """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"""
753
- self.template_loader.load_template(rules_name, "rules", self.chat_name, "*.rules.md")
823
+ self.template_loader.load_template(
824
+ rules_name, "rules", self.chat_name, "*.rules.md"
825
+ )
754
826
 
755
827
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
756
828
  def do_LOAD_INTENTION(self, intention_name):
757
829
  """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"""
758
- self.template_loader.load_template(intention_name, "intention", self.chat_name, "*.intention.md")
830
+ self.template_loader.load_template(
831
+ intention_name, "intention", self.chat_name, "*.intention.md"
832
+ )
759
833
 
760
834
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
761
835
  def do_LOAD_COMMANDS(self, commands_name):
762
836
  """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"""
763
- self.template_loader.load_template(commands_name, "commands", self.chat_name, "*.commands.md")
837
+ self.template_loader.load_template(
838
+ commands_name, "commands", self.chat_name, "*.commands.md"
839
+ )
764
840
 
765
841
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
766
842
  def do_LOAD_BLUEPRINT(self, blueprint_name):
@@ -934,7 +1010,7 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
934
1010
  def do_SEND(self, _):
935
1011
  """Send prompt to the LLM"""
936
1012
  message = "\n".join(self.message_buffer)
937
- self.save_message(Chat.ROLE_PROMPT, message)
1013
+ self.save_message(ROLE_PROMPT, message)
938
1014
  self.send_message()
939
1015
 
940
1016
  @cmd2.with_category(CATEGORY_CHAT_CONTROL)
@@ -1053,7 +1129,9 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
1053
1129
 
1054
1130
  def _template_completer(self, text: str, template_type: str) -> list[str]:
1055
1131
  """Generic completer for different template types."""
1056
- available_templates = self.template_loader.get_available_templates(template_type, os.path.dirname(self.chat_name))
1132
+ available_templates = self.template_loader.get_available_templates(
1133
+ template_type, os.path.dirname(self.chat_name)
1134
+ )
1057
1135
  if not text:
1058
1136
  return available_templates
1059
1137
  return [t for t in available_templates if t.startswith(text)]
@@ -1072,4 +1150,155 @@ Start chatting (type 'HELP'/'h' for available commands, 'QUIT'/'q' to exit chat
1072
1150
 
1073
1151
  def complete_LOAD_BLUEPRINT(self, text, line, begidx, endidx):
1074
1152
  """Completer for the LOAD_BLUEPRINT command."""
1075
- return self._template_completer(text, "blueprint")
1153
+ return self._template_completer(text, "blueprint")
1154
+
1155
+ def _select_script_from_list(
1156
+ self, scripts: list[str], not_found_message: str, prompt: str
1157
+ ) -> str | None:
1158
+ """Displays a list of scripts and prompts the user to select one."""
1159
+ if not scripts:
1160
+ self.poutput(not_found_message)
1161
+ return None
1162
+
1163
+ # Sort the scripts alphabetically by their basename for consistent display
1164
+ # Create a list of (basename, full_script_name) tuples for sorting and later retrieval
1165
+ scripts_with_basenames = [(os.path.basename(s), s) for s in scripts]
1166
+ scripts_with_basenames.sort(
1167
+ key=lambda x: x[0].lower()
1168
+ ) # Sort by lowercase basename
1169
+
1170
+ for i, (basename, full_script_name) in enumerate(scripts_with_basenames):
1171
+ self.poutput(f"{i + 1}: {basename}")
1172
+
1173
+ try:
1174
+ choice = input(prompt)
1175
+ choice_index = int(choice) - 1
1176
+ if 0 <= choice_index < len(scripts_with_basenames):
1177
+ # Return the full script name from the sorted list
1178
+ return scripts_with_basenames[choice_index][1]
1179
+ else:
1180
+ self.poutput("Invalid choice. Aborting.")
1181
+ return None
1182
+ except (ValueError, EOFError):
1183
+ self.poutput("Invalid input. Aborting.")
1184
+ return None
1185
+
1186
+ @cmd2.with_category(CATEGORY_SCRIPT_CONTROL)
1187
+ def do_run_pyscript(self, args):
1188
+ """Run a python script from the chat.
1189
+ Usage: run_pyscript <script_name> [args...]
1190
+ """
1191
+ script_name, script_args = self._parse_run_pyscript_args(args)
1192
+
1193
+ # If no script name provided, list available scripts grouped by type
1194
+ if not script_name:
1195
+ self._list_available_scripts()
1196
+ return
1197
+
1198
+ script_to_run = self._resolve_script_to_run(script_name, script_args)
1199
+
1200
+ if not script_to_run:
1201
+ return
1202
+
1203
+ # Pass arguments to script runner
1204
+ output = self.script_runner.run_script(script_to_run, script_args)
1205
+ if output:
1206
+ self.poutput(output.strip())
1207
+
1208
+ def _list_available_scripts(self):
1209
+ """Lists available scripts grouped by type (global and custom)."""
1210
+ global_scripts = self.script_lister.get_global_scripts()
1211
+ custom_scripts = self.script_lister.get_custom_scripts()
1212
+
1213
+ if not global_scripts and not custom_scripts:
1214
+ self.poutput("No scripts found.")
1215
+ return
1216
+
1217
+ self.poutput("Available scripts:")
1218
+ self.poutput("")
1219
+
1220
+ if custom_scripts:
1221
+ self.poutput("Custom scripts:")
1222
+ for script in sorted(custom_scripts):
1223
+ self.poutput(f" {script}")
1224
+ self.poutput("")
1225
+
1226
+ if global_scripts:
1227
+ self.poutput("Global scripts:")
1228
+ for script in sorted(global_scripts):
1229
+ self.poutput(f" global/{script}")
1230
+
1231
+ def _parse_run_pyscript_args(self, args):
1232
+ """Parses arguments for run_pyscript command."""
1233
+ import shlex
1234
+
1235
+ if not args:
1236
+ return "", []
1237
+
1238
+ # args is a cmd2.Statement (subclass of str), so we can use it directly
1239
+ full_args = str(args)
1240
+ # Use shlex to split arguments, enabling quoted args support
1241
+ split_args = shlex.split(full_args)
1242
+ if not split_args:
1243
+ return "", []
1244
+
1245
+ script_name = split_args[0]
1246
+ script_args = split_args[1:] if len(split_args) > 1 else []
1247
+ return script_name, script_args
1248
+
1249
+ def _resolve_script_to_run(self, script_name, script_args):
1250
+ """Resolves the script name to run."""
1251
+ return script_name
1252
+
1253
+ def complete_run_pyscript(self, text, line, begidx, endidx):
1254
+ """Completer for the run_pyscript command."""
1255
+ # Get all scripts: ['custom.py', 'global/global.py']
1256
+ available_scripts = self.script_lister.get_all_scripts()
1257
+
1258
+ # Add special commands
1259
+ special_commands = [
1260
+ # "global/"
1261
+ # "*", "global/*"
1262
+ ]
1263
+
1264
+ possible_completions = sorted(list(set(available_scripts + special_commands)))
1265
+
1266
+ # Filter based on what the user has typed
1267
+ return [s for s in possible_completions if s.startswith(text)]
1268
+
1269
+ # ===== AGENT CONTROL COMMANDS =====
1270
+
1271
+ @cmd2.with_category(CATEGORY_AGENT_CONTROL)
1272
+ def do_LIST_AGENTS(self, _):
1273
+ """Lists all available executable binary agents."""
1274
+ from ara_cli.commands.list_agents_command import ListAgentsCommand
1275
+
1276
+ command = ListAgentsCommand(chat_instance=self)
1277
+ command.execute()
1278
+
1279
+ @cmd2.with_category(CATEGORY_AGENT_CONTROL)
1280
+ def do_AGENT_RUN(self, args):
1281
+ """Run a binary agent interactively from the 'ara/.araconfig/agents' directory.
1282
+ Usage: AGENT_RUN <agent_name> [arg1] [arg2] ...
1283
+ Example:
1284
+ AGENT_RUN feature-creation -b .
1285
+ """
1286
+
1287
+ from ara_cli.commands.agent_run_command import AgentRunCommand
1288
+
1289
+ command = AgentRunCommand(self, args)
1290
+ command.execute()
1291
+
1292
+ def complete_AGENT_RUN(self, text, line, begidx, endidx):
1293
+ """Completer for AGENT_RUN command."""
1294
+ from ara_cli.commands.list_agents_command import list_available_binary_agents
1295
+
1296
+ parts = line.split()
1297
+ # This completer runs when the user is typing the first argument (the agent name)
1298
+ if len(parts) < 2 or (len(parts) == 2 and not line.endswith(" ")):
1299
+ available_agents = list_available_binary_agents(self)
1300
+ if not text:
1301
+ return available_agents
1302
+ return [a for a in available_agents if a.startswith(text)]
1303
+ # For subsequent arguments, we can offer file/directory completion
1304
+ return self.path_complete(text, line, begidx, endidx)
File without changes