ara-cli 0.1.9.69__py3-none-any.whl → 0.1.10.8__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.

Potentially problematic release.


This version of ara-cli might be problematic. Click here for more details.

Files changed (150) hide show
  1. ara_cli/__init__.py +18 -2
  2. ara_cli/__main__.py +248 -62
  3. ara_cli/ara_command_action.py +155 -86
  4. ara_cli/ara_config.py +226 -80
  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/create.py +75 -0
  11. ara_cli/ara_subcommands/delete.py +22 -0
  12. ara_cli/ara_subcommands/extract.py +22 -0
  13. ara_cli/ara_subcommands/fetch_templates.py +14 -0
  14. ara_cli/ara_subcommands/list.py +65 -0
  15. ara_cli/ara_subcommands/list_tags.py +25 -0
  16. ara_cli/ara_subcommands/load.py +48 -0
  17. ara_cli/ara_subcommands/prompt.py +136 -0
  18. ara_cli/ara_subcommands/read.py +47 -0
  19. ara_cli/ara_subcommands/read_status.py +20 -0
  20. ara_cli/ara_subcommands/read_user.py +20 -0
  21. ara_cli/ara_subcommands/reconnect.py +27 -0
  22. ara_cli/ara_subcommands/rename.py +22 -0
  23. ara_cli/ara_subcommands/scan.py +14 -0
  24. ara_cli/ara_subcommands/set_status.py +22 -0
  25. ara_cli/ara_subcommands/set_user.py +22 -0
  26. ara_cli/ara_subcommands/template.py +16 -0
  27. ara_cli/artefact_autofix.py +649 -68
  28. ara_cli/artefact_creator.py +8 -11
  29. ara_cli/artefact_deleter.py +2 -4
  30. ara_cli/artefact_fuzzy_search.py +22 -10
  31. ara_cli/artefact_link_updater.py +4 -4
  32. ara_cli/artefact_lister.py +29 -55
  33. ara_cli/artefact_models/artefact_data_retrieval.py +23 -0
  34. ara_cli/artefact_models/artefact_load.py +11 -3
  35. ara_cli/artefact_models/artefact_model.py +146 -39
  36. ara_cli/artefact_models/artefact_templates.py +70 -44
  37. ara_cli/artefact_models/businessgoal_artefact_model.py +23 -25
  38. ara_cli/artefact_models/epic_artefact_model.py +34 -26
  39. ara_cli/artefact_models/feature_artefact_model.py +203 -64
  40. ara_cli/artefact_models/keyfeature_artefact_model.py +21 -24
  41. ara_cli/artefact_models/serialize_helper.py +1 -1
  42. ara_cli/artefact_models/task_artefact_model.py +83 -15
  43. ara_cli/artefact_models/userstory_artefact_model.py +37 -27
  44. ara_cli/artefact_models/vision_artefact_model.py +23 -42
  45. ara_cli/artefact_reader.py +92 -91
  46. ara_cli/artefact_renamer.py +8 -4
  47. ara_cli/artefact_scan.py +66 -3
  48. ara_cli/chat.py +622 -162
  49. ara_cli/chat_agent/__init__.py +0 -0
  50. ara_cli/chat_agent/agent_communicator.py +62 -0
  51. ara_cli/chat_agent/agent_process_manager.py +211 -0
  52. ara_cli/chat_agent/agent_status_manager.py +73 -0
  53. ara_cli/chat_agent/agent_workspace_manager.py +76 -0
  54. ara_cli/commands/__init__.py +0 -0
  55. ara_cli/commands/command.py +7 -0
  56. ara_cli/commands/extract_command.py +15 -0
  57. ara_cli/commands/load_command.py +65 -0
  58. ara_cli/commands/load_image_command.py +34 -0
  59. ara_cli/commands/read_command.py +117 -0
  60. ara_cli/completers.py +144 -0
  61. ara_cli/directory_navigator.py +37 -4
  62. ara_cli/error_handler.py +134 -0
  63. ara_cli/file_classifier.py +6 -5
  64. ara_cli/file_lister.py +1 -1
  65. ara_cli/file_loaders/__init__.py +0 -0
  66. ara_cli/file_loaders/binary_file_loader.py +33 -0
  67. ara_cli/file_loaders/document_file_loader.py +34 -0
  68. ara_cli/file_loaders/document_reader.py +245 -0
  69. ara_cli/file_loaders/document_readers.py +233 -0
  70. ara_cli/file_loaders/file_loader.py +50 -0
  71. ara_cli/file_loaders/file_loaders.py +123 -0
  72. ara_cli/file_loaders/image_processor.py +89 -0
  73. ara_cli/file_loaders/markdown_reader.py +75 -0
  74. ara_cli/file_loaders/text_file_loader.py +187 -0
  75. ara_cli/global_file_lister.py +51 -0
  76. ara_cli/list_filter.py +1 -1
  77. ara_cli/output_suppressor.py +1 -1
  78. ara_cli/prompt_extractor.py +215 -88
  79. ara_cli/prompt_handler.py +521 -134
  80. ara_cli/prompt_rag.py +2 -2
  81. ara_cli/tag_extractor.py +83 -38
  82. ara_cli/template_loader.py +245 -0
  83. ara_cli/template_manager.py +18 -13
  84. ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
  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/prompt_template_tech_stack_transformer.commands.md +95 -0
  91. ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
  92. ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
  93. ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
  94. ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
  95. ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
  96. ara_cli/update_config_prompt.py +9 -3
  97. ara_cli/version.py +1 -1
  98. ara_cli-0.1.10.8.dist-info/METADATA +241 -0
  99. ara_cli-0.1.10.8.dist-info/RECORD +193 -0
  100. tests/test_ara_command_action.py +73 -59
  101. tests/test_ara_config.py +341 -36
  102. tests/test_artefact_autofix.py +1060 -0
  103. tests/test_artefact_link_updater.py +3 -3
  104. tests/test_artefact_lister.py +52 -132
  105. tests/test_artefact_renamer.py +2 -2
  106. tests/test_artefact_scan.py +327 -33
  107. tests/test_chat.py +2063 -498
  108. tests/test_file_classifier.py +24 -1
  109. tests/test_file_creator.py +3 -5
  110. tests/test_file_lister.py +1 -1
  111. tests/test_global_file_lister.py +131 -0
  112. tests/test_list_filter.py +2 -2
  113. tests/test_prompt_handler.py +746 -0
  114. tests/test_tag_extractor.py +19 -13
  115. tests/test_template_loader.py +192 -0
  116. tests/test_template_manager.py +5 -4
  117. tests/test_update_config_prompt.py +2 -2
  118. ara_cli/ara_command_parser.py +0 -327
  119. ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
  120. ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
  121. ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
  122. ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
  123. ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
  124. ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
  125. ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
  126. ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
  127. ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
  128. ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
  129. ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
  130. ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
  131. ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
  132. ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
  133. ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
  134. ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
  135. ara_cli/templates/template.businessgoal +0 -10
  136. ara_cli/templates/template.capability +0 -10
  137. ara_cli/templates/template.epic +0 -15
  138. ara_cli/templates/template.example +0 -6
  139. ara_cli/templates/template.feature +0 -26
  140. ara_cli/templates/template.issue +0 -14
  141. ara_cli/templates/template.keyfeature +0 -15
  142. ara_cli/templates/template.task +0 -6
  143. ara_cli/templates/template.userstory +0 -17
  144. ara_cli/templates/template.vision +0 -14
  145. ara_cli-0.1.9.69.dist-info/METADATA +0 -16
  146. ara_cli-0.1.9.69.dist-info/RECORD +0 -158
  147. tests/test_ara_autofix.py +0 -219
  148. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/WHEEL +0 -0
  149. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/entry_points.txt +0 -0
  150. {ara_cli-0.1.9.69.dist-info → ara_cli-0.1.10.8.dist-info}/top_level.txt +0 -0
File without changes
@@ -0,0 +1,62 @@
1
+ import re
2
+ import queue
3
+
4
+
5
+ class AgentCommunicator:
6
+ def __init__(self, agent_process_manager):
7
+ self.agent_process_manager = agent_process_manager
8
+ self.agent_process = agent_process_manager.agent_process
9
+ self.agent_output_queue = agent_process_manager.agent_output_queue
10
+ self.chat_instance = agent_process_manager.chat_instance
11
+ self.status_manager = agent_process_manager.status_manager
12
+
13
+ def read_agent_output(self):
14
+ try:
15
+ for line in iter(self.agent_process.stdout.readline, ""):
16
+ if not line:
17
+ break
18
+ self.agent_output_queue.put(line)
19
+ except Exception as e:
20
+ self.agent_output_queue.put(f"Error reading agent output: {e}\n")
21
+
22
+ def _handle_agent_answer(self, lines, answer_index):
23
+ for j in range(answer_index + 1, len(lines) - 1):
24
+ print(lines[j], flush=True)
25
+ print(flush=True)
26
+
27
+ self.agent_process_manager.agent_mode = True
28
+ self.chat_instance.prompt = "ara-agent> "
29
+ self.status_manager.update_status_file(mode="waiting for input")
30
+
31
+ def _process_lines(self, lines):
32
+ for i, line_text in enumerate(lines[:-1]):
33
+ print(line_text, flush=True)
34
+ if re.match(r'^\s*Answer:', line_text):
35
+ self._handle_agent_answer(lines, i)
36
+ return True, lines[-1]
37
+ return False, lines[-1]
38
+
39
+ def process_agent_output(self):
40
+ buffer = ""
41
+ while True:
42
+ try:
43
+ line = self.agent_output_queue.get(timeout=0.1)
44
+ buffer += line
45
+
46
+ if "\n" in buffer:
47
+ lines = buffer.split("\n")
48
+ found_answer, buffer = self._process_lines(lines)
49
+ if found_answer:
50
+ return
51
+
52
+ except queue.Empty:
53
+ if self.agent_process and self.agent_process.poll() is not None:
54
+ if buffer:
55
+ print(buffer, flush=True)
56
+ self.agent_process_manager.cleanup_agent_process()
57
+ return
58
+ continue
59
+ except Exception as e:
60
+ print(f"Error processing agent output: {e}", flush=True)
61
+ self.agent_process_manager.cleanup_agent_process()
62
+ return
@@ -0,0 +1,211 @@
1
+ from ara_cli.chat_agent.agent_workspace_manager import AgentWorkspaceManager
2
+ from ara_cli.error_handler import AraError, ErrorLevel
3
+ from ara_cli.chat_agent.agent_status_manager import AgentStatusManager
4
+ from ara_cli.chat_agent.agent_communicator import AgentCommunicator
5
+ import os
6
+ import subprocess
7
+ import threading
8
+ import psutil
9
+ import queue
10
+
11
+
12
+ class AgentProcessManager:
13
+ _instance = None
14
+
15
+ def __new__(cls, chat_instance=None):
16
+ if cls._instance is None:
17
+ cls._instance = super(AgentProcessManager, cls).__new__(cls)
18
+ cls._instance._initialized = False
19
+ return cls._instance
20
+
21
+ def __init__(self, chat_instance=None):
22
+ if hasattr(self, "_initialized") and self._initialized:
23
+ if chat_instance:
24
+ self.chat_instance = chat_instance
25
+ return
26
+ if chat_instance is None:
27
+ raise ValueError(
28
+ "chat_instance must be provided for the first instantiation"
29
+ )
30
+
31
+ self.chat_instance = chat_instance
32
+ self.agent_process = None
33
+ self.agent_reader_thread = None
34
+ self.agent_output_queue = queue.Queue()
35
+ self.agent_mode = False
36
+ self.agent_name = None
37
+ self.status_manager = AgentStatusManager()
38
+ self._initialized = True
39
+
40
+ def cleanup_agent_process(self):
41
+ pid = self.status_manager.get_agent_pid()
42
+ if pid and psutil.pid_exists(pid):
43
+ try:
44
+ p = psutil.Process(pid)
45
+ p.terminate()
46
+ p.wait(timeout=3)
47
+ except (psutil.NoSuchProcess, psutil.TimeoutExpired):
48
+ pass
49
+
50
+ self.status_manager.clear_status()
51
+
52
+ if self.agent_process:
53
+ try:
54
+ self.agent_process.terminate()
55
+ except:
56
+ pass
57
+ self.agent_process = None
58
+
59
+ if self.agent_reader_thread and self.agent_reader_thread.is_alive():
60
+ self.agent_reader_thread = None
61
+
62
+ self.agent_mode = False
63
+ self.agent_name = None
64
+ if hasattr(self, "chat_instance"):
65
+ self.chat_instance.prompt = "ara> "
66
+
67
+ def start_agent(
68
+ self, agent_name, initial_prompt, artefact_classifier=None, artefact_name=None
69
+ ):
70
+ if self.get_agent_status() != "No agent is currently running.":
71
+ raise AraError(
72
+ "An agent is already running. Use AGENT_STOP to stop it first."
73
+ )
74
+
75
+ if agent_name not in self.chat_instance.AVAILABLE_AGENTS:
76
+ raise AraError(f"Unknown agent: {agent_name}")
77
+
78
+ base_work_dir = AgentWorkspaceManager.determine_base_work_dir(
79
+ self.chat_instance
80
+ )
81
+ agent_workspace_dir = AgentWorkspaceManager.determine_agent_workspace(
82
+ self.chat_instance, artefact_classifier, artefact_name
83
+ )
84
+
85
+ cmd = [
86
+ "ara-agents",
87
+ agent_name,
88
+ "-u",
89
+ initial_prompt,
90
+ "-g",
91
+ "roundrobin",
92
+ "-b",
93
+ ".",
94
+ "-s",
95
+ ]
96
+
97
+ if artefact_classifier and artefact_name:
98
+ try:
99
+ artefact_path = self._find_artefact_path(
100
+ artefact_classifier, artefact_name
101
+ )
102
+ cmd.extend(["-r", artefact_path])
103
+ print(f"Starting {agent_name} with artefact: {artefact_path}")
104
+ print(f"Base work directory: {base_work_dir}")
105
+ print(f"Agent logs directory: {agent_workspace_dir}")
106
+ except AraError as e:
107
+ raise AraError(f"Error: {e}")
108
+ else:
109
+ print(f"Starting {agent_name}...")
110
+ print(f"Base work directory: {base_work_dir}")
111
+ print(f"Agent logs directory: {agent_workspace_dir}")
112
+
113
+ env = os.environ.copy()
114
+ env["CENTRAL_LOG_PATH"] = os.path.join(
115
+ agent_workspace_dir, "io_context.log")
116
+
117
+ try:
118
+ self.agent_process = subprocess.Popen(
119
+ cmd,
120
+ stdin=subprocess.PIPE,
121
+ stdout=subprocess.PIPE,
122
+ stderr=subprocess.STDOUT,
123
+ text=True,
124
+ bufsize=1,
125
+ cwd=base_work_dir,
126
+ env=env,
127
+ )
128
+ self.agent_name = agent_name
129
+ self.status_manager.write_status(
130
+ self.agent_process.pid, self.agent_name, "processing"
131
+ )
132
+
133
+ communicator = AgentCommunicator(self)
134
+ self.agent_reader_thread = threading.Thread(
135
+ target=communicator.read_agent_output, daemon=True
136
+ )
137
+ self.agent_reader_thread.start()
138
+
139
+ print(f"Agent {agent_name} started. Waiting for response...\n")
140
+ communicator.process_agent_output()
141
+
142
+ except FileNotFoundError:
143
+ raise AraError(
144
+ "Agent could not started."
145
+ "\nReason: 'ara-agents' command not found. Make sure ara-agents is locally installed.",
146
+ level=ErrorLevel.CRITICAL,
147
+ )
148
+ except Exception as e:
149
+ self.cleanup_agent_process()
150
+ raise AraError(f"Error starting agent: {e}")
151
+
152
+ def _find_artefact_path(self, artefact_classifier, artefact_name):
153
+ from ara_cli.classifier import Classifier
154
+
155
+ classifier_dir = Classifier.get_sub_directory(artefact_classifier)
156
+ if not classifier_dir:
157
+ raise AraError(f"Unknown classifier: {artefact_classifier}")
158
+
159
+ chat_dir = os.path.dirname(self.chat_instance.chat_name)
160
+ ara_dir = os.path.join(chat_dir, "ara", classifier_dir)
161
+
162
+ for ext in [artefact_classifier, "md"]:
163
+ artefact_path = os.path.join(ara_dir, f"{artefact_name}.{ext}")
164
+ if os.path.exists(artefact_path):
165
+ return artefact_path
166
+
167
+ raise AraError(
168
+ f"Artefact not found: {artefact_name}.{artefact_classifier}")
169
+
170
+ def send_to_agent(self, text):
171
+ if not self.agent_process or self.agent_process.poll() is not None:
172
+ print("Error: Agent process is not running")
173
+ self.cleanup_agent_process()
174
+ return
175
+
176
+ try:
177
+ self.agent_process.stdin.write(text + "\n")
178
+ self.agent_process.stdin.flush()
179
+
180
+ self.agent_mode = False
181
+ self.chat_instance.prompt = "ara> "
182
+ self.status_manager.update_status_file(mode="processing")
183
+
184
+ communicator = AgentCommunicator(self)
185
+ communicator.process_agent_output()
186
+
187
+ except Exception as e:
188
+ print(f"Error sending to agent: {e}")
189
+ self.cleanup_agent_process()
190
+
191
+ def continue_agent(self):
192
+ status = self.get_agent_status()
193
+ if "No agent" in status:
194
+ print("No agent is currently running.")
195
+ return
196
+
197
+ if not self.agent_process or self.agent_process.poll() is not None:
198
+ print(
199
+ "AGENT_CONTINUE can only be used from the chat interface that started the agent."
200
+ )
201
+ return
202
+
203
+ if not self.agent_mode:
204
+ print("Agent is not waiting for input. Wait for 'ara-agent>' prompt.")
205
+ return
206
+
207
+ print("Continuing agent with empty input...")
208
+ self.send_to_agent("")
209
+
210
+ def get_agent_status(self):
211
+ return self.status_manager.get_agent_status()
@@ -0,0 +1,73 @@
1
+ import os
2
+ import json
3
+ import psutil
4
+ from ara_cli.directory_navigator import DirectoryNavigator
5
+
6
+
7
+ class AgentStatusManager:
8
+ def get_status_file_path(self):
9
+ ara_root = DirectoryNavigator.find_ara_directory_root()
10
+ if not ara_root:
11
+ return None
12
+ return os.path.join(ara_root, ".araconfig", "agent_status.json")
13
+
14
+ def get_agent_pid(self):
15
+ status_file = self.get_status_file_path()
16
+ if not status_file or not os.path.exists(status_file):
17
+ return None
18
+
19
+ with open(status_file, "r") as f:
20
+ try:
21
+ status = json.load(f)
22
+ return status.get("pid")
23
+ except json.JSONDecodeError:
24
+ return None
25
+
26
+ def update_status_file(self, mode):
27
+ status_file = self.get_status_file_path()
28
+ if not status_file or not os.path.exists(status_file):
29
+ return
30
+
31
+ try:
32
+ with open(status_file, "r") as f:
33
+ status = json.load(f)
34
+ except (FileNotFoundError, json.JSONDecodeError):
35
+ return
36
+
37
+ status["mode"] = mode
38
+
39
+ with open(status_file, "w") as f:
40
+ json.dump(status, f)
41
+
42
+ def get_agent_status(self):
43
+ status_file = self.get_status_file_path()
44
+ if not status_file or not os.path.exists(status_file):
45
+ return "No agent is currently running."
46
+
47
+ with open(status_file, "r") as f:
48
+ try:
49
+ status = json.load(f)
50
+ except json.JSONDecodeError:
51
+ return "No agent is currently running."
52
+
53
+ pid = status.get("pid")
54
+ if not pid or not psutil.pid_exists(pid):
55
+ if os.path.exists(status_file):
56
+ os.remove(status_file)
57
+ return "No agent is currently running."
58
+
59
+ name = status.get("name")
60
+ mode = status.get("mode")
61
+ return f"Agent: {name} (running - {mode})"
62
+
63
+ def write_status(self, pid, name, mode):
64
+ status = {"pid": pid, "name": name, "mode": mode}
65
+ status_file = self.get_status_file_path()
66
+ if status_file:
67
+ with open(status_file, "w") as f:
68
+ json.dump(status, f)
69
+
70
+ def clear_status(self):
71
+ status_file = self.get_status_file_path()
72
+ if status_file and os.path.exists(status_file):
73
+ os.remove(status_file)
@@ -0,0 +1,76 @@
1
+ from ara_cli.error_handler import AraError
2
+
3
+ import os
4
+
5
+
6
+ class AgentWorkspaceManager:
7
+ """Manages workspace directories for agent execution."""
8
+
9
+ @staticmethod
10
+ def determine_agent_workspace(chat_instance, artefact_classifier=None, artefact_name=None):
11
+ """
12
+ Determines the appropriate workspace directory for agent logs and data.
13
+
14
+ Args:
15
+ chat_instance: The Chat instance
16
+ artefact_classifier: Optional artefact classifier (e.g., 'feature')
17
+ artefact_name: Optional artefact name
18
+
19
+ Returns:
20
+ str: Absolute path to the agent workspace directory (for logs)
21
+ """
22
+ base_directory = os.path.dirname(chat_instance.chat_name)
23
+
24
+ if artefact_classifier and artefact_name:
25
+ # Use artefact-specific workspace for logs
26
+ from ara_cli.classifier import Classifier
27
+ classifier_dir = Classifier.get_sub_directory(artefact_classifier)
28
+ if not classifier_dir:
29
+ raise AraError(f"Unknown classifier: {artefact_classifier}")
30
+
31
+ workspace_dir = os.path.join(
32
+ base_directory,
33
+ "ara",
34
+ classifier_dir,
35
+ f"{artefact_name}.data"
36
+ )
37
+ else:
38
+ # Use chat-specific workspace for logs
39
+ chat_name_without_ext = os.path.splitext(
40
+ os.path.basename(chat_instance.chat_name))[0]
41
+ workspace_dir = os.path.join(
42
+ base_directory,
43
+ "ara",
44
+ f"{chat_name_without_ext}"
45
+ )
46
+
47
+ # Ensure workspace directory exists
48
+ os.makedirs(workspace_dir, exist_ok=True)
49
+ return os.path.abspath(workspace_dir)
50
+
51
+ @staticmethod
52
+ def determine_base_work_dir(chat_instance):
53
+ """
54
+ Determines the base working directory (project root).
55
+
56
+ Args:
57
+ chat_instance: The Chat instance
58
+
59
+ Returns:
60
+ str: Absolute path to the project root directory
61
+ """
62
+
63
+ return os.path.dirname("./")
64
+ # The bwd should be the project root, not inside ara/
65
+ chat_dir = os.path.dirname(chat_instance.chat_name)
66
+
67
+ # Find project root by looking for 'ara' directory
68
+ current_dir = chat_dir
69
+ while True:
70
+ if os.path.isdir(os.path.join(current_dir, "ara")):
71
+ return os.path.abspath(current_dir)
72
+ parent_dir = os.path.dirname(current_dir)
73
+ if parent_dir == current_dir: # Reached filesystem root
74
+ # Fallback to chat directory if no 'ara' folder found
75
+ return os.path.abspath(chat_dir)
76
+ current_dir = parent_dir
File without changes
@@ -0,0 +1,7 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class Command(ABC):
5
+ @abstractmethod
6
+ def execute(self, *args, **kwargs): # pragma: no cover
7
+ pass
@@ -0,0 +1,15 @@
1
+ from ara_cli.commands.command import Command
2
+ from ara_cli.prompt_extractor import extract_responses
3
+ import os
4
+
5
+ class ExtractCommand(Command):
6
+ def __init__(self, file_name, force=False, write=False, output=None):
7
+ self.file_name = file_name
8
+ self.force = force
9
+ self.write = write
10
+ self.output = output # Callable for standard output (optional)
11
+
12
+ def execute(self, *args, **kwargs):
13
+ extract_responses(self.file_name, True, force=self.force, write=self.write)
14
+ if self.output:
15
+ self.output("End of extraction")
@@ -0,0 +1,65 @@
1
+ from ara_cli.commands.command import Command
2
+ from ara_cli.file_loaders.file_loader import FileLoaderFactory
3
+ from ara_cli.file_loaders.binary_file_loader import BinaryFileLoader
4
+
5
+
6
+ class LoadCommand(Command):
7
+ def __init__(
8
+ self,
9
+ chat_instance,
10
+ file_path: str,
11
+ prefix: str = "",
12
+ suffix: str = "",
13
+ block_delimiter: str = "",
14
+ extract_images: bool = False,
15
+ output=None
16
+ ):
17
+ self.chat = chat_instance
18
+ self.file_path = file_path
19
+ self.prefix = prefix
20
+ self.suffix = suffix
21
+ self.block_delimiter = block_delimiter
22
+ self.extract_images = extract_images
23
+ self.output = output or print
24
+
25
+ def execute(self) -> bool:
26
+ loader = FileLoaderFactory.create_loader(self.file_path, self.chat)
27
+
28
+ if isinstance(loader, BinaryFileLoader):
29
+ # Determine mime type for binary files
30
+ file_name_lower = self.file_path.lower()
31
+ mime_type = None
32
+ for extension, mt in FileLoaderFactory.BINARY_TYPE_MAPPING.items():
33
+ if file_name_lower.endswith(extension):
34
+ mime_type = mt
35
+ break
36
+
37
+ if not mime_type:
38
+ self.output(
39
+ f"Could not determine mime type for {self.file_path}")
40
+ return False
41
+
42
+ success = loader.load(
43
+ self.file_path,
44
+ mime_type=mime_type,
45
+ prefix=self.prefix,
46
+ suffix=self.suffix
47
+ )
48
+ elif hasattr(loader, 'load'):
49
+ success = loader.load(
50
+ self.file_path,
51
+ prefix=self.prefix,
52
+ suffix=self.suffix,
53
+ block_delimiter=self.block_delimiter,
54
+ extract_images=self.extract_images
55
+ )
56
+ else:
57
+ return False
58
+
59
+ if success:
60
+ if self.extract_images and not isinstance(loader, BinaryFileLoader):
61
+ self.output(
62
+ f"Loaded contents of file {self.file_path} with images extracted")
63
+ else:
64
+ self.output(f"Loaded contents of file {self.file_path}")
65
+ return success
@@ -0,0 +1,34 @@
1
+ from ara_cli.commands.command import Command
2
+ from ara_cli.file_loaders.binary_file_loader import BinaryFileLoader
3
+
4
+
5
+ class LoadImageCommand(Command):
6
+ def __init__(
7
+ self,
8
+ chat_instance,
9
+ file_path: str,
10
+ mime_type: str,
11
+ prefix: str = "",
12
+ suffix: str = "",
13
+ output=None
14
+ ):
15
+ self.chat = chat_instance
16
+ self.file_path = file_path
17
+ self.mime_type = mime_type
18
+ self.prefix = prefix
19
+ self.suffix = suffix
20
+ self.output = output or print
21
+
22
+ def execute(self) -> bool:
23
+ loader = BinaryFileLoader(self.chat)
24
+ success = loader.load(
25
+ self.file_path,
26
+ mime_type=self.mime_type,
27
+ prefix=self.prefix,
28
+ suffix=self.suffix
29
+ )
30
+
31
+ if success:
32
+ self.output(f"Loaded image file {self.file_path}")
33
+
34
+ return success
@@ -0,0 +1,117 @@
1
+ from ara_cli.commands.command import Command
2
+ from ara_cli.artefact_reader import ArtefactReader
3
+ from ara_cli.file_classifier import FileClassifier
4
+ from ara_cli.list_filter import ListFilter, filter_list
5
+ from ara_cli.artefact_models.artefact_data_retrieval import (
6
+ artefact_content_retrieval,
7
+ artefact_path_retrieval,
8
+ artefact_tags_retrieval
9
+ )
10
+ from ara_cli.artefact_fuzzy_search import suggest_close_name_matches
11
+ import os
12
+
13
+
14
+ class ReadCommand(Command):
15
+ def __init__(
16
+ self,
17
+ classifier: str,
18
+ artefact_name: str,
19
+ read_mode: str = "default",
20
+ list_filter: ListFilter = None,
21
+ output=None
22
+ ):
23
+ self.classifier = classifier
24
+ self.artefact_name = artefact_name
25
+ self.read_mode = read_mode
26
+ self.list_filter = list_filter or ListFilter()
27
+ self.output = output or print
28
+
29
+ def execute(self) -> bool:
30
+ """Execute the read command and return success status."""
31
+ file_classifier = FileClassifier(os)
32
+ classified_artefacts = ArtefactReader.read_artefacts()
33
+
34
+ if not self.classifier or not self.artefact_name:
35
+ self._filter_and_print(classified_artefacts, file_classifier)
36
+ return True
37
+
38
+ artefacts = classified_artefacts.get(self.classifier, [])
39
+ all_artefact_names = [a.title for a in artefacts]
40
+
41
+ if self.artefact_name not in all_artefact_names:
42
+ suggest_close_name_matches(
43
+ self.artefact_name,
44
+ all_artefact_names
45
+ )
46
+ return False
47
+
48
+ target_artefact = next(filter(
49
+ lambda x: x.title == self.artefact_name, artefacts
50
+ ))
51
+
52
+ artefacts_by_classifier = {self.classifier: []}
53
+
54
+ try:
55
+ match self.read_mode:
56
+ case "branch":
57
+ self._handle_branch_mode(
58
+ classified_artefacts, artefacts_by_classifier
59
+ )
60
+ case "children":
61
+ artefacts_by_classifier = self._handle_children_mode(
62
+ classified_artefacts
63
+ )
64
+ case _:
65
+ self._handle_default_mode(
66
+ target_artefact, artefacts_by_classifier
67
+ )
68
+
69
+ # Apply filtering and print results
70
+ self._filter_and_print(artefacts_by_classifier, file_classifier)
71
+ # filtered_artefacts = self._apply_filtering(artefacts_by_classifier)
72
+ # file_classifier.print_classified_files(
73
+ # filtered_artefacts, print_content=True
74
+ # )
75
+ return True
76
+
77
+ except Exception as e:
78
+ self.output(f"Error reading artefact: {e}")
79
+ return False
80
+
81
+ def _handle_branch_mode(self, classified_artefacts, artefacts_by_classifier):
82
+ """Handle branch read mode."""
83
+ ArtefactReader.step_through_value_chain(
84
+ artefact_name=self.artefact_name,
85
+ classifier=self.classifier,
86
+ artefacts_by_classifier=artefacts_by_classifier,
87
+ classified_artefacts=classified_artefacts
88
+ )
89
+
90
+ def _handle_children_mode(self, classified_artefacts):
91
+ """Handle children read mode."""
92
+ return ArtefactReader.find_children(
93
+ artefact_name=self.artefact_name,
94
+ classifier=self.classifier,
95
+ classified_artefacts=classified_artefacts
96
+ )
97
+
98
+ def _handle_default_mode(self, target_artefact, artefacts_by_classifier):
99
+ """Handle default read mode."""
100
+ artefacts_by_classifier[self.classifier].append(target_artefact)
101
+
102
+ def _apply_filtering(self, artefacts_by_classifier):
103
+ """Apply list filtering to artefacts."""
104
+ return filter_list(
105
+ list_to_filter=artefacts_by_classifier,
106
+ list_filter=self.list_filter,
107
+ content_retrieval_strategy=artefact_content_retrieval,
108
+ file_path_retrieval=artefact_path_retrieval,
109
+ tag_retrieval=artefact_tags_retrieval
110
+ )
111
+
112
+ def _filter_and_print(self, artefacts_by_classifier, file_classifier):
113
+ """Apply list filtering and print results"""
114
+ filtered_artefacts = self._apply_filtering(artefacts_by_classifier)
115
+ file_classifier.print_classified_files(
116
+ filtered_artefacts, print_content=True
117
+ )