ara-cli 0.1.10.5__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 +87 -75
- ara_cli/ara_command_action.py +95 -57
- ara_cli/ara_config.py +187 -128
- ara_cli/ara_subcommands/common.py +2 -2
- ara_cli/ara_subcommands/config.py +221 -0
- ara_cli/ara_subcommands/convert.py +43 -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 +15 -10
- ara_cli/ara_subcommands/list.py +97 -23
- ara_cli/artefact_autofix.py +115 -62
- ara_cli/artefact_converter.py +256 -0
- ara_cli/chat.py +283 -62
- 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/completers.py +71 -35
- ara_cli/constants.py +2 -0
- ara_cli/directory_navigator.py +37 -4
- ara_cli/llm_utils.py +58 -0
- ara_cli/prompt_chat.py +20 -4
- ara_cli/prompt_extractor.py +47 -32
- ara_cli/template_loader.py +2 -1
- 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.5.dist-info → ara_cli-0.1.13.3.dist-info}/METADATA +33 -1
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/RECORD +89 -43
- 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_chat.py +162 -126
- tests/test_chat_givens_images.py +603 -0
- tests/test_chat_script_runner.py +454 -0
- tests/test_llm_utils.py +164 -0
- tests/test_prompt_chat.py +343 -0
- tests/test_prompt_extractor.py +683 -0
- tests/test_web_search.py +467 -0
- ara_cli/templates/prompt-modules/blueprints/complete_pytest_unittest.blueprint.md +0 -27
- ara_cli/templates/prompt-modules/blueprints/pytest_unittest_prompt.blueprint.md +0 -32
- 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.5.dist-info → ara_cli-0.1.13.3.dist-info}/WHEEL +0 -0
- {ara_cli-0.1.10.5.dist-info → ara_cli-0.1.13.3.dist-info}/entry_points.txt +0 -0
- {ara_cli-0.1.10.5.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/completers.py
CHANGED
|
@@ -4,18 +4,17 @@ from pathlib import Path
|
|
|
4
4
|
import typer
|
|
5
5
|
|
|
6
6
|
from ara_cli.classifier import Classifier
|
|
7
|
-
from ara_cli.
|
|
7
|
+
from ara_cli.constants import VALID_ASPECTS
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def complete_classifier(incomplete: str) -> List[str]:
|
|
11
11
|
"""Complete classifier names."""
|
|
12
|
-
|
|
13
|
-
return [c for c in classifiers if c.startswith(incomplete)]
|
|
12
|
+
return [c for c in Classifier.ordered_classifiers() if c.startswith(incomplete)]
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def complete_aspect(incomplete: str) -> List[str]:
|
|
17
16
|
"""Complete aspect names."""
|
|
18
|
-
aspects =
|
|
17
|
+
aspects = VALID_ASPECTS
|
|
19
18
|
return [a for a in aspects if a.startswith(incomplete)]
|
|
20
19
|
|
|
21
20
|
|
|
@@ -31,23 +30,31 @@ def complete_template_type(incomplete: str) -> List[str]:
|
|
|
31
30
|
return [t for t in template_types if t.startswith(incomplete)]
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
def complete_artefact_name(classifier: str) -> List[str]:
|
|
33
|
+
def complete_artefact_name(classifier: str, incomplete: str = "") -> List[str]:
|
|
35
34
|
"""Complete artefact names for a given classifier."""
|
|
36
35
|
try:
|
|
37
36
|
# Get the directory for the classifier
|
|
38
37
|
classifier_dir = f"ara/{Classifier.get_sub_directory(classifier)}"
|
|
39
|
-
|
|
38
|
+
|
|
40
39
|
if not os.path.exists(classifier_dir):
|
|
41
40
|
return []
|
|
42
|
-
|
|
41
|
+
|
|
43
42
|
# Find all files with the classifier extension
|
|
44
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
|
+
|
|
45
52
|
for file in os.listdir(classifier_dir):
|
|
46
|
-
if file.endswith(
|
|
53
|
+
if file.startswith(incomplete) and file.endswith(suffix):
|
|
47
54
|
# Remove the extension to get the artefact name
|
|
48
|
-
name = file[
|
|
55
|
+
name = file[: -len(suffix)]
|
|
49
56
|
artefacts.append(name)
|
|
50
|
-
|
|
57
|
+
|
|
51
58
|
return sorted(artefacts)
|
|
52
59
|
except Exception:
|
|
53
60
|
return []
|
|
@@ -55,9 +62,10 @@ def complete_artefact_name(classifier: str) -> List[str]:
|
|
|
55
62
|
|
|
56
63
|
def complete_artefact_name_for_classifier(classifier: str):
|
|
57
64
|
"""Create a completer function for artefact names of a specific classifier."""
|
|
65
|
+
|
|
58
66
|
def completer(incomplete: str) -> List[str]:
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
return complete_artefact_name(classifier, incomplete)
|
|
68
|
+
|
|
61
69
|
return completer
|
|
62
70
|
|
|
63
71
|
|
|
@@ -66,13 +74,14 @@ def complete_chat_files(incomplete: str) -> List[str]:
|
|
|
66
74
|
try:
|
|
67
75
|
chat_files = []
|
|
68
76
|
current_dir = Path.cwd()
|
|
69
|
-
|
|
70
|
-
#
|
|
71
|
-
for
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
|
|
76
85
|
return sorted(chat_files)
|
|
77
86
|
except Exception:
|
|
78
87
|
return []
|
|
@@ -83,62 +92,89 @@ class DynamicCompleters:
|
|
|
83
92
|
@staticmethod
|
|
84
93
|
def create_classifier_completer():
|
|
85
94
|
"""Create a completer for classifiers."""
|
|
95
|
+
|
|
86
96
|
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
87
97
|
return complete_classifier(incomplete)
|
|
98
|
+
|
|
88
99
|
return completer
|
|
89
|
-
|
|
100
|
+
|
|
90
101
|
@staticmethod
|
|
91
102
|
def create_aspect_completer():
|
|
92
103
|
"""Create a completer for aspects."""
|
|
104
|
+
|
|
93
105
|
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
94
106
|
return complete_aspect(incomplete)
|
|
107
|
+
|
|
95
108
|
return completer
|
|
96
|
-
|
|
109
|
+
|
|
97
110
|
@staticmethod
|
|
98
111
|
def create_status_completer():
|
|
99
112
|
"""Create a completer for status values."""
|
|
113
|
+
|
|
100
114
|
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
101
115
|
return complete_status(incomplete)
|
|
116
|
+
|
|
102
117
|
return completer
|
|
103
|
-
|
|
118
|
+
|
|
104
119
|
@staticmethod
|
|
105
120
|
def create_template_type_completer():
|
|
106
121
|
"""Create a completer for template types."""
|
|
122
|
+
|
|
107
123
|
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
108
124
|
return complete_template_type(incomplete)
|
|
125
|
+
|
|
109
126
|
return completer
|
|
110
|
-
|
|
127
|
+
|
|
111
128
|
@staticmethod
|
|
112
129
|
def create_artefact_name_completer():
|
|
113
130
|
"""Create a completer for artefact names based on classifier context."""
|
|
131
|
+
|
|
114
132
|
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
115
133
|
# Try to get classifier from context
|
|
116
|
-
if hasattr(ctx,
|
|
117
|
-
classifier = ctx.params[
|
|
118
|
-
if hasattr(classifier,
|
|
134
|
+
if hasattr(ctx, "params") and "classifier" in ctx.params:
|
|
135
|
+
classifier = ctx.params["classifier"]
|
|
136
|
+
if hasattr(classifier, "value"):
|
|
119
137
|
classifier = classifier.value
|
|
120
|
-
|
|
121
|
-
return [a for a in artefacts if a.startswith(incomplete)]
|
|
138
|
+
return complete_artefact_name(classifier, incomplete)
|
|
122
139
|
return []
|
|
140
|
+
|
|
123
141
|
return completer
|
|
124
|
-
|
|
142
|
+
|
|
125
143
|
@staticmethod
|
|
126
144
|
def create_parent_name_completer():
|
|
127
145
|
"""Create a completer for parent artefact names based on parent classifier context."""
|
|
146
|
+
|
|
128
147
|
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
129
148
|
# Try to get parent_classifier from context
|
|
130
|
-
if hasattr(ctx,
|
|
131
|
-
parent_classifier = ctx.params[
|
|
132
|
-
if hasattr(parent_classifier,
|
|
149
|
+
if hasattr(ctx, "params") and "parent_classifier" in ctx.params:
|
|
150
|
+
parent_classifier = ctx.params["parent_classifier"]
|
|
151
|
+
if hasattr(parent_classifier, "value"):
|
|
133
152
|
parent_classifier = parent_classifier.value
|
|
134
|
-
|
|
135
|
-
return [a for a in artefacts if a.startswith(incomplete)]
|
|
153
|
+
return complete_artefact_name(parent_classifier, incomplete)
|
|
136
154
|
return []
|
|
155
|
+
|
|
137
156
|
return completer
|
|
138
|
-
|
|
157
|
+
|
|
139
158
|
@staticmethod
|
|
140
159
|
def create_chat_file_completer():
|
|
141
160
|
"""Create a completer for chat files."""
|
|
161
|
+
|
|
142
162
|
def completer(ctx: typer.Context, incomplete: str) -> List[str]:
|
|
143
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
|
+
|
|
144
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):
|
ara_cli/llm_utils.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from ara_cli.ara_config import ConfigManager
|
|
2
|
+
from pydantic_ai import Agent
|
|
3
|
+
|
|
4
|
+
FALLBACK_MODEL = "anthropic:claude-4-sonnet-20250514"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_configured_conversion_llm_model() -> str:
|
|
8
|
+
"""
|
|
9
|
+
Retrieves the configured conversion LLM model string, adapted for pydantic_ai.
|
|
10
|
+
Falls back to a default model if configuration is missing or invalid.
|
|
11
|
+
"""
|
|
12
|
+
model_name = FALLBACK_MODEL
|
|
13
|
+
try:
|
|
14
|
+
config = ConfigManager.get_config()
|
|
15
|
+
conversion_llm_key = config.conversion_llm
|
|
16
|
+
|
|
17
|
+
if conversion_llm_key and conversion_llm_key in config.llm_config:
|
|
18
|
+
llm_config_item = config.llm_config[conversion_llm_key]
|
|
19
|
+
raw_model_name = llm_config_item.model
|
|
20
|
+
|
|
21
|
+
# Adapt LiteLLM model string to PydanticAI format
|
|
22
|
+
# LiteLLM: provider/model-name (e.g. openai/gpt-4o)
|
|
23
|
+
# PydanticAI: provider:model-name (e.g. openai:gpt-4o)
|
|
24
|
+
if "/" in raw_model_name and ":" not in raw_model_name:
|
|
25
|
+
parts = raw_model_name.split("/", 1)
|
|
26
|
+
if len(parts) == 2:
|
|
27
|
+
model_name = f"{parts[0]}:{parts[1]}"
|
|
28
|
+
else:
|
|
29
|
+
model_name = raw_model_name
|
|
30
|
+
else:
|
|
31
|
+
model_name = raw_model_name
|
|
32
|
+
else:
|
|
33
|
+
print(
|
|
34
|
+
f"Warning: Conversion LLM configuration issue. Using fallback model: {FALLBACK_MODEL}"
|
|
35
|
+
)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(
|
|
38
|
+
f"Warning: Error resolving LLM config ({e}). Using fallback model: {FALLBACK_MODEL}"
|
|
39
|
+
)
|
|
40
|
+
model_name = FALLBACK_MODEL
|
|
41
|
+
|
|
42
|
+
return model_name
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_pydantic_ai_agent(
|
|
46
|
+
output_type, model_name: str = None, instrument: bool = True
|
|
47
|
+
) -> Agent:
|
|
48
|
+
"""
|
|
49
|
+
Creates a pydantic_ai Agent with the specified or configured model.
|
|
50
|
+
"""
|
|
51
|
+
if not model_name:
|
|
52
|
+
model_name = get_configured_conversion_llm_model()
|
|
53
|
+
|
|
54
|
+
return Agent(
|
|
55
|
+
model=model_name,
|
|
56
|
+
output_type=output_type,
|
|
57
|
+
instrument=instrument,
|
|
58
|
+
)
|
ara_cli/prompt_chat.py
CHANGED
|
@@ -6,9 +6,18 @@ from ara_cli.update_config_prompt import update_artefact_config_prompt_files
|
|
|
6
6
|
from ara_cli.output_suppressor import suppress_stdout
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def initialize_prompt_chat_mode(
|
|
9
|
+
def initialize_prompt_chat_mode(
|
|
10
|
+
classifier,
|
|
11
|
+
param,
|
|
12
|
+
chat_name,
|
|
13
|
+
reset=None,
|
|
14
|
+
output_mode=False,
|
|
15
|
+
append_strings=[],
|
|
16
|
+
restricted=False,
|
|
17
|
+
):
|
|
10
18
|
sub_directory = Classifier.get_sub_directory(classifier)
|
|
11
|
-
|
|
19
|
+
# f"ara/{sub_directory}/{parameter}.data"
|
|
20
|
+
artefact_data_path = os.path.join("ara", sub_directory, f"{param}.data")
|
|
12
21
|
|
|
13
22
|
if chat_name is None:
|
|
14
23
|
chat_name = classifier
|
|
@@ -17,11 +26,18 @@ def initialize_prompt_chat_mode(classifier, param, chat_name, reset=None, output
|
|
|
17
26
|
update_artefact_config_prompt_files(classifier, param, automatic_update=True)
|
|
18
27
|
|
|
19
28
|
classifier_chat_file = os.path.join(artefact_data_path, f"{chat_name}")
|
|
20
|
-
start_chat_session(
|
|
29
|
+
start_chat_session(
|
|
30
|
+
classifier_chat_file, reset, output_mode, append_strings, restricted
|
|
31
|
+
)
|
|
32
|
+
|
|
21
33
|
|
|
22
34
|
def start_chat_session(chat_file, reset, output_mode, append_strings, restricted):
|
|
23
35
|
with suppress_stdout(suppress=output_mode):
|
|
24
|
-
chat =
|
|
36
|
+
chat = (
|
|
37
|
+
Chat(chat_file, reset=reset)
|
|
38
|
+
if not restricted
|
|
39
|
+
else Chat(chat_file, reset=reset, enable_commands=whitelisted_commands)
|
|
40
|
+
)
|
|
25
41
|
if append_strings:
|
|
26
42
|
chat.append_strings(append_strings)
|
|
27
43
|
if output_mode:
|