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.
- ara_cli/__init__.py +51 -6
- ara_cli/__main__.py +270 -103
- ara_cli/ara_command_action.py +106 -63
- ara_cli/ara_config.py +187 -128
- ara_cli/ara_subcommands/__init__.py +0 -0
- ara_cli/ara_subcommands/autofix.py +26 -0
- ara_cli/ara_subcommands/chat.py +27 -0
- ara_cli/ara_subcommands/classifier_directory.py +16 -0
- ara_cli/ara_subcommands/common.py +100 -0
- ara_cli/ara_subcommands/config.py +221 -0
- ara_cli/ara_subcommands/convert.py +43 -0
- ara_cli/ara_subcommands/create.py +75 -0
- ara_cli/ara_subcommands/delete.py +22 -0
- ara_cli/ara_subcommands/extract.py +22 -0
- ara_cli/ara_subcommands/fetch.py +41 -0
- ara_cli/ara_subcommands/fetch_agents.py +22 -0
- ara_cli/ara_subcommands/fetch_scripts.py +19 -0
- ara_cli/ara_subcommands/fetch_templates.py +19 -0
- ara_cli/ara_subcommands/list.py +139 -0
- ara_cli/ara_subcommands/list_tags.py +25 -0
- ara_cli/ara_subcommands/load.py +48 -0
- ara_cli/ara_subcommands/prompt.py +136 -0
- ara_cli/ara_subcommands/read.py +47 -0
- ara_cli/ara_subcommands/read_status.py +20 -0
- ara_cli/ara_subcommands/read_user.py +20 -0
- ara_cli/ara_subcommands/reconnect.py +27 -0
- ara_cli/ara_subcommands/rename.py +22 -0
- ara_cli/ara_subcommands/scan.py +14 -0
- ara_cli/ara_subcommands/set_status.py +22 -0
- ara_cli/ara_subcommands/set_user.py +22 -0
- ara_cli/ara_subcommands/template.py +16 -0
- ara_cli/artefact_autofix.py +154 -63
- ara_cli/artefact_converter.py +256 -0
- ara_cli/artefact_models/artefact_model.py +106 -25
- ara_cli/artefact_models/artefact_templates.py +20 -10
- ara_cli/artefact_models/epic_artefact_model.py +11 -2
- ara_cli/artefact_models/feature_artefact_model.py +31 -1
- ara_cli/artefact_models/userstory_artefact_model.py +15 -3
- ara_cli/artefact_scan.py +2 -2
- ara_cli/chat.py +283 -80
- ara_cli/chat_agent/__init__.py +0 -0
- ara_cli/chat_agent/agent_process_manager.py +155 -0
- ara_cli/chat_script_runner/__init__.py +0 -0
- ara_cli/chat_script_runner/script_completer.py +23 -0
- ara_cli/chat_script_runner/script_finder.py +41 -0
- ara_cli/chat_script_runner/script_lister.py +36 -0
- ara_cli/chat_script_runner/script_runner.py +36 -0
- ara_cli/chat_web_search/__init__.py +0 -0
- ara_cli/chat_web_search/web_search.py +263 -0
- ara_cli/commands/agent_run_command.py +98 -0
- ara_cli/commands/fetch_agents_command.py +106 -0
- ara_cli/commands/fetch_scripts_command.py +43 -0
- ara_cli/commands/fetch_templates_command.py +39 -0
- ara_cli/commands/fetch_templates_commands.py +39 -0
- ara_cli/commands/list_agents_command.py +39 -0
- ara_cli/commands/read_command.py +17 -4
- ara_cli/completers.py +180 -0
- ara_cli/constants.py +2 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/file_loaders/text_file_loader.py +2 -2
- ara_cli/global_file_lister.py +5 -15
- ara_cli/llm_utils.py +58 -0
- ara_cli/prompt_chat.py +20 -4
- ara_cli/prompt_extractor.py +199 -76
- ara_cli/prompt_handler.py +160 -59
- ara_cli/tag_extractor.py +38 -18
- ara_cli/template_loader.py +3 -2
- ara_cli/template_manager.py +52 -21
- ara_cli/templates/global-scripts/hello_global.py +1 -0
- ara_cli/templates/prompt-modules/commands/add_scenarios_for_new_behaviour.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/align_feature_with_implementation_changes.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/analyze_codebase_and_plan_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/choose_best_parent_artefact.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tasks_from_artefact_content.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/create_tests_for_uncovered_modules.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/derive_features_from_video_description.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/describe_agent_capabilities.agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/empty.commands.md +2 -12
- ara_cli/templates/prompt-modules/commands/execute_scoped_todos_in_task.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/explain_single_file_purpose.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_file_information_bullets.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/extract_general.commands.md +12 -0
- ara_cli/templates/prompt-modules/commands/extract_markdown.commands.md +11 -0
- ara_cli/templates/prompt-modules/commands/extract_python.commands.md +13 -0
- ara_cli/templates/prompt-modules/commands/feature_add_or_modifiy_specified_behavior.commands.md +36 -0
- ara_cli/templates/prompt-modules/commands/feature_generate_initial_specified_bevahior.commands.md +53 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_behave_step_definitions.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/fix_failing_pytest_tests.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/general_instruction_policy.commands.md +47 -0
- ara_cli/templates/prompt-modules/commands/generate_and_fix_pytest_tests.test_generation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/prompt_template_tech_stack_transformer.commands.md +95 -0
- ara_cli/templates/prompt-modules/commands/python_bug_fixing_code.commands.md +34 -0
- ara_cli/templates/prompt-modules/commands/python_generate_code.commands.md +27 -0
- ara_cli/templates/prompt-modules/commands/python_refactoring_code.commands.md +39 -0
- ara_cli/templates/prompt-modules/commands/python_step_definitions_generation_and_fixing.commands.md +40 -0
- ara_cli/templates/prompt-modules/commands/python_unittest_generation_and_fixing.commands.md +48 -0
- ara_cli/templates/prompt-modules/commands/suggest_next_story_child_tasks.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/summarize_or_transcribe_media.interview_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_feature_to_match_implementation.feature_creation_agent.commands.md +1 -0
- ara_cli/templates/prompt-modules/commands/update_user_story_with_requirements.interview_agent.commands.md +1 -0
- ara_cli/version.py +1 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +34 -1
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +123 -54
- tests/test_ara_command_action.py +31 -19
- tests/test_ara_config.py +177 -90
- tests/test_artefact_autofix.py +170 -97
- tests/test_artefact_autofix_integration.py +495 -0
- tests/test_artefact_converter.py +357 -0
- tests/test_artefact_extraction.py +564 -0
- tests/test_artefact_scan.py +1 -1
- tests/test_chat.py +162 -126
- tests/test_chat_givens_images.py +603 -0
- tests/test_chat_script_runner.py +454 -0
- tests/test_global_file_lister.py +1 -1
- tests/test_llm_utils.py +164 -0
- tests/test_prompt_chat.py +343 -0
- tests/test_prompt_extractor.py +683 -0
- tests/test_prompt_handler.py +12 -4
- tests/test_tag_extractor.py +19 -13
- tests/test_web_search.py +467 -0
- ara_cli/ara_command_parser.py +0 -605
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/task_todo_list_implement_feature_BDD_way.blueprint.md +0 -30
- ara_cli/templates/prompt-modules/commands/artefact_classification.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/artefact_extension.commands.md +0 -17
- ara_cli/templates/prompt-modules/commands/artefact_formulation.commands.md +0 -14
- ara_cli/templates/prompt-modules/commands/behave_step_generation.commands.md +0 -102
- ara_cli/templates/prompt-modules/commands/code_generation_complex.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/error_fixing.commands.md +0 -20
- ara_cli/templates/prompt-modules/commands/feature_file_update.commands.md +0 -18
- ara_cli/templates/prompt-modules/commands/feature_formulation.commands.md +0 -43
- ara_cli/templates/prompt-modules/commands/js_code_generation_simple.commands.md +0 -13
- ara_cli/templates/prompt-modules/commands/refactoring.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/refactoring_analysis.commands.md +0 -9
- ara_cli/templates/prompt-modules/commands/reverse_engineer_feature_file.commands.md +0 -15
- ara_cli/templates/prompt-modules/commands/reverse_engineer_program_flow.commands.md +0 -19
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.0.dist-info → ara_cli-0.1.13.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from ara_cli.commands.command import Command
|
|
4
|
+
from ara_cli.directory_navigator import DirectoryNavigator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FetchAgentsCommand(Command):
|
|
8
|
+
"""Command to fetch binary agents from a remote URL.
|
|
9
|
+
|
|
10
|
+
This command downloads a binary agent from a hardcoded URL and
|
|
11
|
+
saves it to the project's ara/.araconfig/agents/ directory.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
AGENT_URL = "https://s3-public.talsen.team/so-agents/feature-creation"
|
|
15
|
+
|
|
16
|
+
def __init__(self, output=None):
|
|
17
|
+
"""Initialize the FetchAgentsCommand.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
output : callable, optional
|
|
22
|
+
Output function for displaying messages. Defaults to print.
|
|
23
|
+
"""
|
|
24
|
+
self.output = output or print
|
|
25
|
+
|
|
26
|
+
def execute(self):
|
|
27
|
+
"""Execute the fetch-agents command.
|
|
28
|
+
|
|
29
|
+
Downloads a binary agent from a remote URL and saves it to the
|
|
30
|
+
project's .araconfig/agents directory.
|
|
31
|
+
"""
|
|
32
|
+
navigator = DirectoryNavigator()
|
|
33
|
+
original_directory = os.getcwd()
|
|
34
|
+
|
|
35
|
+
import requests
|
|
36
|
+
from rich.progress import (
|
|
37
|
+
BarColumn,
|
|
38
|
+
DownloadColumn,
|
|
39
|
+
Progress,
|
|
40
|
+
TextColumn,
|
|
41
|
+
TimeRemainingColumn,
|
|
42
|
+
TransferSpeedColumn,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
# Navigate to ara directory
|
|
47
|
+
navigator.navigate_to_target()
|
|
48
|
+
|
|
49
|
+
dest_dir = self._get_project_agents_dir()
|
|
50
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
51
|
+
|
|
52
|
+
agent_name = self.AGENT_URL.split("/")[-1]
|
|
53
|
+
dest_path = os.path.join(dest_dir, agent_name)
|
|
54
|
+
|
|
55
|
+
self.output(f"Downloading agent from {self.AGENT_URL}...")
|
|
56
|
+
|
|
57
|
+
response = requests.get(self.AGENT_URL, stream=True)
|
|
58
|
+
response.raise_for_status()
|
|
59
|
+
|
|
60
|
+
total_size = int(response.headers.get("content-length", 0))
|
|
61
|
+
block_size = 1024
|
|
62
|
+
progress = Progress(
|
|
63
|
+
TextColumn("[bold blue]{task.description}", justify="right"),
|
|
64
|
+
BarColumn(bar_width=None),
|
|
65
|
+
"[progress.percentage]{task.percentage:>3.1f}%",
|
|
66
|
+
"•",
|
|
67
|
+
DownloadColumn(),
|
|
68
|
+
"•",
|
|
69
|
+
TransferSpeedColumn(),
|
|
70
|
+
"•",
|
|
71
|
+
TimeRemainingColumn(),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
with progress:
|
|
75
|
+
task_id = progress.add_task(
|
|
76
|
+
f"Downloading {agent_name}", total=total_size
|
|
77
|
+
)
|
|
78
|
+
with open(dest_path, "wb") as f:
|
|
79
|
+
for data in response.iter_content(block_size):
|
|
80
|
+
progress.update(task_id, advance=len(data))
|
|
81
|
+
f.write(data)
|
|
82
|
+
|
|
83
|
+
if total_size != 0 and os.path.getsize(dest_path) != total_size:
|
|
84
|
+
raise Exception("ERROR, something went wrong during download")
|
|
85
|
+
|
|
86
|
+
# Make the binary executable
|
|
87
|
+
os.chmod(dest_path, 0o755)
|
|
88
|
+
|
|
89
|
+
self.output(f"Downloaded {agent_name} to ara/.araconfig/agents/")
|
|
90
|
+
self.output("Binary agents fetched successfully to ara/.araconfig/agents/")
|
|
91
|
+
|
|
92
|
+
except requests.exceptions.RequestException as e:
|
|
93
|
+
self.output(f"Error downloading agent: {e}")
|
|
94
|
+
finally:
|
|
95
|
+
# Return to original directory
|
|
96
|
+
os.chdir(original_directory)
|
|
97
|
+
|
|
98
|
+
def _get_project_agents_dir(self):
|
|
99
|
+
"""Get the path to the project agents directory.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
str
|
|
104
|
+
Path to ara/.araconfig/agents directory.
|
|
105
|
+
"""
|
|
106
|
+
return os.path.join(".araconfig", "agents")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from ara_cli.commands.command import Command
|
|
4
|
+
from ara_cli.ara_config import ConfigManager
|
|
5
|
+
from ara_cli.directory_navigator import DirectoryNavigator
|
|
6
|
+
|
|
7
|
+
class FetchScriptsCommand(Command):
|
|
8
|
+
def __init__(self, output=None):
|
|
9
|
+
self.output = output or print
|
|
10
|
+
self.config = ConfigManager.get_config()
|
|
11
|
+
|
|
12
|
+
def execute(self):
|
|
13
|
+
navigator = DirectoryNavigator()
|
|
14
|
+
original_directory = os.getcwd()
|
|
15
|
+
navigator.navigate_to_target()
|
|
16
|
+
os.chdir('..')
|
|
17
|
+
|
|
18
|
+
global_scripts_dir = self._get_global_scripts_dir()
|
|
19
|
+
global_scripts_config_dir = self._get_global_scripts_config_dir()
|
|
20
|
+
|
|
21
|
+
if not os.path.exists(global_scripts_dir):
|
|
22
|
+
self.output("Global scripts directory not found.")
|
|
23
|
+
os.chdir(original_directory)
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
if not os.path.exists(global_scripts_config_dir):
|
|
27
|
+
os.makedirs(global_scripts_config_dir)
|
|
28
|
+
|
|
29
|
+
for item in os.listdir(global_scripts_dir):
|
|
30
|
+
source = os.path.join(global_scripts_dir, item)
|
|
31
|
+
destination = os.path.join(global_scripts_config_dir, item)
|
|
32
|
+
if os.path.isfile(source):
|
|
33
|
+
shutil.copy2(source, destination)
|
|
34
|
+
self.output(f"Copied {item} to global scripts directory.")
|
|
35
|
+
|
|
36
|
+
os.chdir(original_directory)
|
|
37
|
+
|
|
38
|
+
def _get_global_scripts_dir(self):
|
|
39
|
+
base_path = os.path.dirname(os.path.dirname(__file__))
|
|
40
|
+
return os.path.join(base_path, "templates", "global-scripts")
|
|
41
|
+
|
|
42
|
+
def _get_global_scripts_config_dir(self):
|
|
43
|
+
return os.path.join(self.config.local_prompt_templates_dir, "global-scripts")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from os.path import join
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
from ara_cli.commands.command import Command
|
|
5
|
+
from ara_cli.ara_config import ConfigManager
|
|
6
|
+
from ara_cli.template_manager import TemplatePathManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FetchTemplatesCommand(Command):
|
|
10
|
+
def __init__(self, output=None):
|
|
11
|
+
self.output = output or print
|
|
12
|
+
|
|
13
|
+
def execute(self):
|
|
14
|
+
config = ConfigManager().get_config()
|
|
15
|
+
prompt_templates_dir = config.local_prompt_templates_dir
|
|
16
|
+
template_base_path = TemplatePathManager.get_template_base_path()
|
|
17
|
+
global_prompt_templates_path = join(
|
|
18
|
+
template_base_path, "prompt-modules")
|
|
19
|
+
|
|
20
|
+
subdirs = ["commands", "rules", "intentions", "blueprints"]
|
|
21
|
+
|
|
22
|
+
os.makedirs(join(prompt_templates_dir,
|
|
23
|
+
"global-prompt-modules"), exist_ok=True)
|
|
24
|
+
for subdir in subdirs:
|
|
25
|
+
target_dir = join(prompt_templates_dir,
|
|
26
|
+
"global-prompt-modules", subdir)
|
|
27
|
+
source_dir = join(global_prompt_templates_path, subdir)
|
|
28
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
29
|
+
for item in os.listdir(source_dir):
|
|
30
|
+
source = join(source_dir, item)
|
|
31
|
+
target = join(target_dir, item)
|
|
32
|
+
shutil.copy2(source, target)
|
|
33
|
+
|
|
34
|
+
custom_prompt_templates_subdir = config.custom_prompt_templates_subdir
|
|
35
|
+
local_prompt_modules_dir = join(
|
|
36
|
+
prompt_templates_dir, custom_prompt_templates_subdir)
|
|
37
|
+
os.makedirs(local_prompt_modules_dir, exist_ok=True)
|
|
38
|
+
for subdir in subdirs:
|
|
39
|
+
os.makedirs(join(local_prompt_modules_dir, subdir), exist_ok=True)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from os.path import join
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
from ara_cli.commands.command import Command
|
|
5
|
+
from ara_cli.ara_config import ConfigManager
|
|
6
|
+
from ara_cli.template_manager import TemplatePathManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FetchTemplatesCommand(Command):
|
|
10
|
+
def __init__(self, output=None):
|
|
11
|
+
self.output = output or print
|
|
12
|
+
|
|
13
|
+
def execute(self):
|
|
14
|
+
config = ConfigManager().get_config()
|
|
15
|
+
prompt_templates_dir = config.local_prompt_templates_dir
|
|
16
|
+
template_base_path = TemplatePathManager.get_template_base_path()
|
|
17
|
+
global_prompt_templates_path = join(
|
|
18
|
+
template_base_path, "prompt-modules")
|
|
19
|
+
|
|
20
|
+
subdirs = ["commands", "rules", "intentions", "blueprints"]
|
|
21
|
+
|
|
22
|
+
os.makedirs(join(prompt_templates_dir,
|
|
23
|
+
"global-prompt-modules"), exist_ok=True)
|
|
24
|
+
for subdir in subdirs:
|
|
25
|
+
target_dir = join(prompt_templates_dir,
|
|
26
|
+
"global-prompt-modules", subdir)
|
|
27
|
+
source_dir = join(global_prompt_templates_path, subdir)
|
|
28
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
29
|
+
for item in os.listdir(source_dir):
|
|
30
|
+
source = join(source_dir, item)
|
|
31
|
+
target = join(target_dir, item)
|
|
32
|
+
shutil.copy2(source, target)
|
|
33
|
+
|
|
34
|
+
custom_prompt_templates_subdir = config.custom_prompt_templates_subdir
|
|
35
|
+
local_prompt_modules_dir = join(
|
|
36
|
+
prompt_templates_dir, custom_prompt_templates_subdir)
|
|
37
|
+
os.makedirs(local_prompt_modules_dir, exist_ok=True)
|
|
38
|
+
for subdir in subdirs:
|
|
39
|
+
os.makedirs(join(local_prompt_modules_dir, subdir), exist_ok=True)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from ara_cli.commands.command import Command
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def list_available_binary_agents(chat_instance):
|
|
6
|
+
"""Helper to list executable files in the agents directory."""
|
|
7
|
+
try:
|
|
8
|
+
base_dir = chat_instance._find_project_root()
|
|
9
|
+
if not base_dir:
|
|
10
|
+
return [] # Can't find project root
|
|
11
|
+
|
|
12
|
+
agents_dir = os.path.join(base_dir, "ara", ".araconfig", "agents")
|
|
13
|
+
if not os.path.isdir(agents_dir):
|
|
14
|
+
return []
|
|
15
|
+
|
|
16
|
+
available_agents = []
|
|
17
|
+
for f in os.listdir(agents_dir):
|
|
18
|
+
path = os.path.join(agents_dir, f)
|
|
19
|
+
if os.path.isfile(path) and os.access(path, os.X_OK):
|
|
20
|
+
available_agents.append(f)
|
|
21
|
+
return available_agents
|
|
22
|
+
except Exception:
|
|
23
|
+
return [] # Fail silently
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ListAgentsCommand(Command):
|
|
27
|
+
def __init__(self, chat_instance):
|
|
28
|
+
self.chat_instance = chat_instance
|
|
29
|
+
|
|
30
|
+
def execute(self):
|
|
31
|
+
"""Lists all available executable binary agents."""
|
|
32
|
+
print("Searching for available agents in 'ara/.araconfig/agents/'...")
|
|
33
|
+
available_agents = list_available_binary_agents(self.chat_instance)
|
|
34
|
+
if available_agents:
|
|
35
|
+
print("\nAvailable binary agents:")
|
|
36
|
+
for agent in available_agents:
|
|
37
|
+
print(f" - {agent}")
|
|
38
|
+
else:
|
|
39
|
+
print("No executable binary agents found.")
|
ara_cli/commands/read_command.py
CHANGED
|
@@ -30,6 +30,11 @@ class ReadCommand(Command):
|
|
|
30
30
|
"""Execute the read command and return success status."""
|
|
31
31
|
file_classifier = FileClassifier(os)
|
|
32
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
|
+
|
|
33
38
|
artefacts = classified_artefacts.get(self.classifier, [])
|
|
34
39
|
all_artefact_names = [a.title for a in artefacts]
|
|
35
40
|
|
|
@@ -62,10 +67,11 @@ class ReadCommand(Command):
|
|
|
62
67
|
)
|
|
63
68
|
|
|
64
69
|
# Apply filtering and print results
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
# )
|
|
69
75
|
return True
|
|
70
76
|
|
|
71
77
|
except Exception as e:
|
|
@@ -102,3 +108,10 @@ class ReadCommand(Command):
|
|
|
102
108
|
file_path_retrieval=artefact_path_retrieval,
|
|
103
109
|
tag_retrieval=artefact_tags_retrieval
|
|
104
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
|
+
)
|
ara_cli/completers.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from ara_cli.classifier import Classifier
|
|
7
|
+
from ara_cli.constants import VALID_ASPECTS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def complete_classifier(incomplete: str) -> List[str]:
|
|
11
|
+
"""Complete classifier names."""
|
|
12
|
+
return [c for c in Classifier.ordered_classifiers() if c.startswith(incomplete)]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def complete_aspect(incomplete: str) -> List[str]:
|
|
16
|
+
"""Complete aspect names."""
|
|
17
|
+
aspects = VALID_ASPECTS
|
|
18
|
+
return [a for a in aspects if a.startswith(incomplete)]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def complete_status(incomplete: str) -> List[str]:
|
|
22
|
+
"""Complete task status values."""
|
|
23
|
+
statuses = ["to-do", "in-progress", "review", "done", "closed"]
|
|
24
|
+
return [s for s in statuses if s.startswith(incomplete)]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def complete_template_type(incomplete: str) -> List[str]:
|
|
28
|
+
"""Complete template type values."""
|
|
29
|
+
template_types = ["rules", "intention", "commands", "blueprint"]
|
|
30
|
+
return [t for t in template_types if t.startswith(incomplete)]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def complete_artefact_name(classifier: str, incomplete: str = "") -> List[str]:
|
|
34
|
+
"""Complete artefact names for a given classifier."""
|
|
35
|
+
try:
|
|
36
|
+
# Get the directory for the classifier
|
|
37
|
+
classifier_dir = f"ara/{Classifier.get_sub_directory(classifier)}"
|
|
38
|
+
|
|
39
|
+
if not os.path.exists(classifier_dir):
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
# Find all files with the classifier extension
|
|
43
|
+
artefacts = []
|
|
44
|
+
suffix = f".{classifier}"
|
|
45
|
+
|
|
46
|
+
# We only care about files that match the incomplete prefix AND the suffix
|
|
47
|
+
# Since os.listdir gives filenames, we can check startswith(incomplete)
|
|
48
|
+
# but we must be careful because the file starts with the artefact name, not necessarily the incomplete part + suffix directly unless full match.
|
|
49
|
+
# Actually, incomplete corresponds to the artefact name part.
|
|
50
|
+
# So file must start with incomplete AND end with suffix.
|
|
51
|
+
|
|
52
|
+
for file in os.listdir(classifier_dir):
|
|
53
|
+
if file.startswith(incomplete) and file.endswith(suffix):
|
|
54
|
+
# Remove the extension to get the artefact name
|
|
55
|
+
name = file[: -len(suffix)]
|
|
56
|
+
artefacts.append(name)
|
|
57
|
+
|
|
58
|
+
return sorted(artefacts)
|
|
59
|
+
except Exception:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def complete_artefact_name_for_classifier(classifier: str):
|
|
64
|
+
"""Create a completer function for artefact names of a specific classifier."""
|
|
65
|
+
|
|
66
|
+
def completer(incomplete: str) -> List[str]:
|
|
67
|
+
return complete_artefact_name(classifier, incomplete)
|
|
68
|
+
|
|
69
|
+
return completer
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def complete_chat_files(incomplete: str) -> List[str]:
|
|
73
|
+
"""Complete chat file names (without .md extension)."""
|
|
74
|
+
try:
|
|
75
|
+
chat_files = []
|
|
76
|
+
current_dir = Path.cwd()
|
|
77
|
+
|
|
78
|
+
# Optimize by using glob with the incomplete prefix
|
|
79
|
+
# Note: glob pattern needs to be careful with special chars, but for autocompletion usually fine.
|
|
80
|
+
pattern = f"{incomplete}*.md" if incomplete else "*.md"
|
|
81
|
+
|
|
82
|
+
for file in current_dir.glob(pattern):
|
|
83
|
+
chat_files.append(file.stem)
|
|
84
|
+
|
|
85
|
+
return sorted(chat_files)
|
|
86
|
+
except Exception:
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Dynamic completers that need context
|
|
91
|
+
class DynamicCompleters:
|
|
92
|
+
@staticmethod
|
|
93
|
+
def create_classifier_completer():
|
|
94
|
+
"""Create a completer for classifiers."""
|
|
95
|
+
|
|
96
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
97
|
+
return complete_classifier(incomplete)
|
|
98
|
+
|
|
99
|
+
return completer
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def create_aspect_completer():
|
|
103
|
+
"""Create a completer for aspects."""
|
|
104
|
+
|
|
105
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
106
|
+
return complete_aspect(incomplete)
|
|
107
|
+
|
|
108
|
+
return completer
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def create_status_completer():
|
|
112
|
+
"""Create a completer for status values."""
|
|
113
|
+
|
|
114
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
115
|
+
return complete_status(incomplete)
|
|
116
|
+
|
|
117
|
+
return completer
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def create_template_type_completer():
|
|
121
|
+
"""Create a completer for template types."""
|
|
122
|
+
|
|
123
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
124
|
+
return complete_template_type(incomplete)
|
|
125
|
+
|
|
126
|
+
return completer
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def create_artefact_name_completer():
|
|
130
|
+
"""Create a completer for artefact names based on classifier context."""
|
|
131
|
+
|
|
132
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
133
|
+
# Try to get classifier from context
|
|
134
|
+
if hasattr(ctx, "params") and "classifier" in ctx.params:
|
|
135
|
+
classifier = ctx.params["classifier"]
|
|
136
|
+
if hasattr(classifier, "value"):
|
|
137
|
+
classifier = classifier.value
|
|
138
|
+
return complete_artefact_name(classifier, incomplete)
|
|
139
|
+
return []
|
|
140
|
+
|
|
141
|
+
return completer
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def create_parent_name_completer():
|
|
145
|
+
"""Create a completer for parent artefact names based on parent classifier context."""
|
|
146
|
+
|
|
147
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
148
|
+
# Try to get parent_classifier from context
|
|
149
|
+
if hasattr(ctx, "params") and "parent_classifier" in ctx.params:
|
|
150
|
+
parent_classifier = ctx.params["parent_classifier"]
|
|
151
|
+
if hasattr(parent_classifier, "value"):
|
|
152
|
+
parent_classifier = parent_classifier.value
|
|
153
|
+
return complete_artefact_name(parent_classifier, incomplete)
|
|
154
|
+
return []
|
|
155
|
+
|
|
156
|
+
return completer
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def create_chat_file_completer():
|
|
160
|
+
"""Create a completer for chat files."""
|
|
161
|
+
|
|
162
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
163
|
+
return complete_chat_files(incomplete)
|
|
164
|
+
|
|
165
|
+
return completer
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def create_convert_source_artefact_name_completer():
|
|
169
|
+
"""Create a completer for convert command source artefact names based on old_classifier context."""
|
|
170
|
+
|
|
171
|
+
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
172
|
+
# Try to get old_classifier from context
|
|
173
|
+
if hasattr(ctx, "params") and "old_classifier" in ctx.params:
|
|
174
|
+
old_classifier = ctx.params["old_classifier"]
|
|
175
|
+
if hasattr(old_classifier, "value"):
|
|
176
|
+
old_classifier = old_classifier.value
|
|
177
|
+
return complete_artefact_name(old_classifier, incomplete)
|
|
178
|
+
return []
|
|
179
|
+
|
|
180
|
+
return completer
|
ara_cli/constants.py
ADDED
ara_cli/directory_navigator.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import sys
|
|
2
3
|
from os.path import join, exists, isdir, dirname, basename
|
|
3
4
|
# from ara_cli.directory_searcher import DirectorySearcher
|
|
4
5
|
|
|
@@ -23,7 +24,8 @@ class DirectoryNavigator:
|
|
|
23
24
|
return original_directory
|
|
24
25
|
|
|
25
26
|
current_directory = original_directory
|
|
26
|
-
|
|
27
|
+
# Ensure loop breaks at root
|
|
28
|
+
while current_directory != dirname(current_directory):
|
|
27
29
|
potential_path = join(current_directory, self.target_directory)
|
|
28
30
|
if self.exists(potential_path):
|
|
29
31
|
os.chdir(potential_path)
|
|
@@ -31,7 +33,8 @@ class DirectoryNavigator:
|
|
|
31
33
|
current_directory = dirname(current_directory)
|
|
32
34
|
|
|
33
35
|
# If the loop completes, the target directory was not found
|
|
34
|
-
user_input = input(
|
|
36
|
+
user_input = input(
|
|
37
|
+
f"Unable to locate the '{self.target_directory}' directory. Do you want to create an 'ara' folder in the working directory? (y/N): ").strip().lower()
|
|
35
38
|
|
|
36
39
|
if user_input == '' or user_input == 'y':
|
|
37
40
|
ara_folder_path = join(original_directory, 'ara')
|
|
@@ -40,7 +43,8 @@ class DirectoryNavigator:
|
|
|
40
43
|
os.chdir(ara_folder_path)
|
|
41
44
|
return original_directory
|
|
42
45
|
else:
|
|
43
|
-
print(
|
|
46
|
+
print(
|
|
47
|
+
f"Unable to locate the '{self.target_directory}' directory and user declined to create 'ara' folder.")
|
|
44
48
|
sys.exit(0)
|
|
45
49
|
|
|
46
50
|
def navigate_to_relative(self, relative_path):
|
|
@@ -56,7 +60,36 @@ class DirectoryNavigator:
|
|
|
56
60
|
if self.exists(path):
|
|
57
61
|
os.chdir(path)
|
|
58
62
|
else:
|
|
59
|
-
raise Exception(
|
|
63
|
+
raise Exception(
|
|
64
|
+
f"Unable to navigate to '{relative_path}' relative to the target directory.")
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def find_ara_directory_root():
|
|
68
|
+
"""Find the root ara directory by traversing up the directory tree."""
|
|
69
|
+
current_dir = os.getcwd()
|
|
70
|
+
|
|
71
|
+
# Check if we're already inside an ara directory structure
|
|
72
|
+
path_parts = current_dir.split(os.sep)
|
|
73
|
+
|
|
74
|
+
# Look for 'ara' in the path parts
|
|
75
|
+
if 'ara' in path_parts:
|
|
76
|
+
ara_index = path_parts.index('ara')
|
|
77
|
+
# Reconstruct path up to and including 'ara'
|
|
78
|
+
ara_root_parts = path_parts[:ara_index + 1]
|
|
79
|
+
potential_ara_root = os.sep.join(ara_root_parts)
|
|
80
|
+
if os.path.exists(potential_ara_root) and os.path.isdir(potential_ara_root):
|
|
81
|
+
return potential_ara_root
|
|
82
|
+
|
|
83
|
+
# If not inside ara directory, check current directory and parents
|
|
84
|
+
check_dir = current_dir
|
|
85
|
+
# Stop at filesystem root
|
|
86
|
+
while check_dir != os.path.dirname(check_dir):
|
|
87
|
+
ara_path = os.path.join(check_dir, 'ara')
|
|
88
|
+
if os.path.exists(ara_path) and os.path.isdir(ara_path):
|
|
89
|
+
return ara_path
|
|
90
|
+
check_dir = os.path.dirname(check_dir)
|
|
91
|
+
|
|
92
|
+
return None
|
|
60
93
|
|
|
61
94
|
# debug version
|
|
62
95
|
# def get_ara_directory(self):
|
|
@@ -19,14 +19,14 @@ class TextFileLoader(FileLoader):
|
|
|
19
19
|
|
|
20
20
|
if is_md_file and extract_images:
|
|
21
21
|
reader = MarkdownReader(file_path)
|
|
22
|
-
file_content = reader.read(extract_images=True)
|
|
22
|
+
file_content = reader.read(extract_images=True).replace('\r\n', '\n')
|
|
23
23
|
else:
|
|
24
24
|
# Use charset-normalizer to detect encoding
|
|
25
25
|
encoded_content = from_path(file_path).best()
|
|
26
26
|
if not encoded_content:
|
|
27
27
|
print(f"Failed to detect encoding for {file_path}")
|
|
28
28
|
return False
|
|
29
|
-
file_content = str(encoded_content)
|
|
29
|
+
file_content = str(encoded_content).replace('\r\n', '\n')
|
|
30
30
|
|
|
31
31
|
if block_delimiter:
|
|
32
32
|
file_content = f"{block_delimiter}\n{file_content}\n{block_delimiter}"
|
ara_cli/global_file_lister.py
CHANGED
|
@@ -2,22 +2,19 @@ import os
|
|
|
2
2
|
import fnmatch
|
|
3
3
|
from typing import List, Dict, Any
|
|
4
4
|
|
|
5
|
-
# Ağaç yapımız için bir tip tanımı yapalım
|
|
6
5
|
DirTree = Dict[str, Any]
|
|
7
6
|
|
|
8
7
|
def _build_tree(root_path: str, patterns: List[str]) -> DirTree:
|
|
9
|
-
"""
|
|
8
|
+
"""Creates a nested dictionary representing the directory structure in the specified path."""
|
|
10
9
|
tree: DirTree = {'files': [], 'dirs': {}}
|
|
11
10
|
try:
|
|
12
11
|
for item in os.listdir(root_path):
|
|
13
12
|
item_path = os.path.join(root_path, item)
|
|
14
13
|
if os.path.isdir(item_path):
|
|
15
14
|
subtree = _build_tree(item_path, patterns)
|
|
16
|
-
# Sadece içinde dosya olan veya dosyası olan alt klasörleri ekle
|
|
17
15
|
if subtree['files'] or subtree['dirs']:
|
|
18
16
|
tree['dirs'][item] = subtree
|
|
19
17
|
elif os.path.isfile(item_path):
|
|
20
|
-
# Dosyanın verilen desenlerden herhangi biriyle eşleşip eşleşmediğini kontrol et
|
|
21
18
|
if any(fnmatch.fnmatch(item, pattern) for pattern in patterns):
|
|
22
19
|
tree['files'].append(item)
|
|
23
20
|
except OSError as e:
|
|
@@ -25,23 +22,17 @@ def _build_tree(root_path: str, patterns: List[str]) -> DirTree:
|
|
|
25
22
|
return tree
|
|
26
23
|
|
|
27
24
|
def _write_tree_to_markdown(md_file, tree: DirTree, level: int):
|
|
28
|
-
"""
|
|
29
|
-
# Dosyaları girintili olarak yaz
|
|
25
|
+
"""Writes the tree data structure to the file in markdown format."""
|
|
30
26
|
indent = ' ' * level
|
|
31
27
|
for filename in sorted(tree['files']):
|
|
32
28
|
md_file.write(f"{indent}- [] {filename}\n")
|
|
33
29
|
|
|
34
|
-
# Alt dizinler için başlık oluştur ve recursive olarak devam et
|
|
35
30
|
for dirname, subtree in sorted(tree['dirs'].items()):
|
|
36
|
-
# Alt başlıklar için girinti yok, sadece başlık seviyesi artıyor
|
|
37
31
|
md_file.write(f"{' ' * (level -1)}{'#' * (level + 1)} {dirname}\n")
|
|
38
32
|
_write_tree_to_markdown(md_file, subtree, level + 1)
|
|
39
33
|
|
|
40
34
|
def generate_global_markdown_listing(directories: List[str], file_patterns: List[str], output_file: str):
|
|
41
|
-
"""
|
|
42
|
-
Global dizinler için hiyerarşik bir markdown dosya listesi oluşturur.
|
|
43
|
-
En üst başlık olarak mutlak yolu kullanır, alt öğeler için göreceli isimler kullanır.
|
|
44
|
-
"""
|
|
35
|
+
"""Creates a hierarchical list of markdown files for global directories. Uses the absolute path as the top heading and relative names for children."""
|
|
45
36
|
with open(output_file, 'w', encoding='utf-8') as md_file:
|
|
46
37
|
for directory in directories:
|
|
47
38
|
abs_dir = os.path.abspath(directory)
|
|
@@ -49,12 +40,11 @@ def generate_global_markdown_listing(directories: List[str], file_patterns: List
|
|
|
49
40
|
if not os.path.isdir(abs_dir):
|
|
50
41
|
print(f"Warning: Global directory not found: {abs_dir}")
|
|
51
42
|
md_file.write(f"# {directory}\n")
|
|
52
|
-
md_file.write(f" - !!
|
|
43
|
+
md_file.write(f" - !! Warning: Global directory not found: {abs_dir}\n\n")
|
|
53
44
|
continue
|
|
54
45
|
|
|
55
46
|
tree = _build_tree(abs_dir, file_patterns)
|
|
56
|
-
|
|
57
|
-
# Sadece ağaç boş değilse yaz
|
|
47
|
+
|
|
58
48
|
if tree['files'] or tree['dirs']:
|
|
59
49
|
md_file.write(f"# {abs_dir}\n")
|
|
60
50
|
_write_tree_to_markdown(md_file, tree, 1)
|