1
from __future__ import annotations
2
3
-import re
3
+import re, json
4
from dataclasses import dataclass
5
from pathlib import Path
6
-from typing import Any, Dict, List, Optional
6
+from typing import Any, Dict, List, Optional, TYPE_CHECKING
7
8
from python.helpers import files, print_style
9
10
+if TYPE_CHECKING:
11
+ from agent import Agent
12
+
13
# Extracts target selector from <meta name="plugin-target" content="...">
14
_META_TARGET_RE = re.compile(
15
r'<meta\s+name=["\']plugin-target["\']\s+content=["\']([^"\']+)["\']',
16
re.IGNORECASE,
17
)
18
19
+META_FILE_NAME = "plugin.json"
20
+SETTINGS_FILE_NAME = "settings.json"
21
22
@dataclass(slots=True)
18
-class Plugin:
19
- id: str
23
+class PluginListItem:
24
name: str
25
path: Path
26
31
# projects = files.find_existing_paths_by_pattern("usr/projects/*/.a0proj/plugins")
32
return [
33
# *projects,
30
- files.get_abs_path("usr/plugins"),
31
- files.get_abs_path("plugins"),
34
+ files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR),
35
+ files.get_abs_path(files.PLUGINS_DIR),
36
]
37
38
35
-def list_plugins() -> List[Plugin]:
39
+def list_plugins() -> List[PluginListItem]:
40
"""Discover plugins by directory convention. First root wins on ID conflict."""
37
- by_id: Dict[str, Plugin] = {}
41
+ by_id: Dict[str, PluginListItem] = {}
42
for root in get_plugin_roots():
43
root_path = Path(root)
44
if not root_path.exists():
47
if not d.is_dir() or d.name.startswith("."):
48
continue
49
if d.name not in by_id:
46
- by_id[d.name] = Plugin(id=d.name, name=d.name, path=d)
50
+ by_id[d.name] = PluginListItem(name=d.name, path=d)
51
return list(by_id.values())
52
53
50
-def find_plugin(plugin_id: str) -> Optional[Plugin]:
54
+def find_plugin_dir(plugin_name: str):
55
"""Find a single plugin by ID."""
52
- if not plugin_id:
56
+ if not plugin_name:
57
return None
54
- for p in list_plugins():
55
- if p.id == plugin_id:
56
- return p
58
+
59
+ # check if the plugin is in the user directory
60
+ user_plugin_path = files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR, plugin_name, META_FILE_NAME)
61
+ if files.exists(user_plugin_path):
62
+ return files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR, plugin_name)
63
+
64
+ # check if the plugin is in the default directory
65
+ default_plugin_path = files.get_abs_path(files.PLUGINS_DIR, plugin_name, META_FILE_NAME)
66
+ if files.exists(default_plugin_path):
67
+ return files.get_abs_path(files.PLUGINS_DIR, plugin_name)
68
+
69
return None
70
71
109
try:
110
rel_path = files.deabsolute_path(str(ext_file))
111
entry: Dict[str, Any] = {
100
- "plugin_id": plugin.id,
112
+ "plugin_name": plugin.name,
113
"path": rel_path,
114
}
115
entries.append(entry)
116
except Exception:
117
print_style.PrintStyle.error(f"Failed to load frontend extension file {ext_file}")
118
return entries
119
+
120
+def get_plugin_settings(plugin_name:str, agent:Agent|None):
121
+ file_path = find_plugin_file(plugin_name, SETTINGS_FILE_NAME, agent=agent)
122
+ if file_path:
123
+ return json.loads(files.read_file(file_path))
124
+ return None
125
+
126
+def save_plugin_settings(plugin_name:str, project_name:str, agent_profile:str, settings:dict):
127
+ file_path = determine_plugin_save_file_path(plugin_name, project_name, agent_profile, SETTINGS_FILE_NAME)
128
+ if file_path:
129
+ files.write_file(file_path, json.dumps(settings))
130
+
131
+def find_plugin_file(plugin_name:str, *subpaths:str, agent:Agent|None=None):
132
+ profile_name = agent.config.profile if agent and agent.config.profile else ""
133
+ project_name = ""
134
+
135
+ if agent:
136
+ from python.helpers import projects
137
+ project_name = projects.get_context_project_name(agent.context) or ""
138
+
139
+ if project_name and profile_name:
140
+ # project/.a0proj/agents/<profile>/plugins/<plugin_name>/...
141
+ project_agent_file = projects.get_project_meta_folder(
142
+ project_name, files.AGENTS_DIR, profile_name, files.PLUGINS_DIR, plugin_name, *subpaths
143
+ )
144
+ if files.exists(project_agent_file):
145
+ return project_agent_file
146
+
147
+ if project_name:
148
+ # project/.a0proj/plugins/<plugin_name>/...
149
+ project_file = projects.get_project_meta_folder(project_name, files.PLUGINS_DIR, plugin_name, *subpaths)
150
+ if files.exists(project_file):
151
+ return project_file
152
+
153
+ if profile_name:
154
+ from python.helpers import subagents
155
+ # usr/agents/<profile>/plugins/<plugin_name>/...
156
+ path = files.get_abs_path(subagents.USER_AGENTS_DIR, profile_name, files.PLUGINS_DIR, plugin_name, *subpaths)
157
+ if files.exists(path):
158
+ return path
159
+
160
+ # agents/<profile>/plugins/<plugin_name>/...
161
+ path = files.get_abs_path(subagents.DEFAULT_AGENTS_DIR, profile_name, files.PLUGINS_DIR, plugin_name, *subpaths)
162
+ if files.exists(path):
163
+ return path
164
+
165
+ # usr/plugins/<plugin_name>/...
166
+ path = files.get_abs_path(files.USER_DIR, files.PLUGINS_DIR, plugin_name, *subpaths)
167
+ if files.exists(path):
168
+ return path
169
+
170
+ # plugins/<plugin_name>/...
171
+ path = files.get_abs_path(files.PLUGINS_DIR, plugin_name, *subpaths)
172
+ if files.exists(path):
173
+ return path
174
+
175
+ return None
176
+
177
+def determine_plugin_save_file_path(plugin_name:str, project_name:str, agent_profile:str, *subpaths:str):
178
+ base_path = files.get_abs_path(files.USER_DIR)
179
+
180
+ if project_name:
181
+ from python.helpers import projects
182
+ base_path = projects.get_project_meta_folder(project_name)
183
+
184
+ if agent_profile:
185
+ base_path = files.get_abs_path(base_path, files.AGENTS_DIR, agent_profile)
186
+
187
+ return files.get_abs_path(base_path, files.PLUGINS_DIR, plugin_name, *subpaths)
\ No newline at end of file