flock-core 0.3.31__py3-none-any.whl → 0.3.33__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 flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -1
- flock/cli/constants.py +3 -0
- flock/cli/registry_management.py +618 -0
- flock/core/flock.py +1 -0
- {flock_core-0.3.31.dist-info → flock_core-0.3.33.dist-info}/METADATA +1 -1
- {flock_core-0.3.31.dist-info → flock_core-0.3.33.dist-info}/RECORD +9 -8
- {flock_core-0.3.31.dist-info → flock_core-0.3.33.dist-info}/WHEEL +0 -0
- {flock_core-0.3.31.dist-info → flock_core-0.3.33.dist-info}/entry_points.txt +0 -0
- {flock_core-0.3.31.dist-info → flock_core-0.3.33.dist-info}/licenses/LICENSE +0 -0
flock/__init__.py
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from rich.panel import Panel
|
|
4
4
|
|
|
5
|
-
from flock.cli.constants import
|
|
5
|
+
from flock.cli.constants import (
|
|
6
|
+
CLI_EXIT,
|
|
7
|
+
CLI_NOTES,
|
|
8
|
+
CLI_REGISTRY_MANAGEMENT,
|
|
9
|
+
CLI_THEME_BUILDER,
|
|
10
|
+
)
|
|
6
11
|
from flock.cli.load_release_notes import load_release_notes
|
|
7
12
|
from flock.cli.settings import settings_editor
|
|
8
13
|
from flock.core.logging.formatters.theme_builder import theme_builder
|
|
@@ -46,6 +51,7 @@ def main():
|
|
|
46
51
|
# CLI_LOAD_EXAMPLE,
|
|
47
52
|
questionary.Separator(),
|
|
48
53
|
CLI_THEME_BUILDER,
|
|
54
|
+
CLI_REGISTRY_MANAGEMENT,
|
|
49
55
|
CLI_SETTINGS,
|
|
50
56
|
questionary.Separator(),
|
|
51
57
|
CLI_START_WEB_SERVER,
|
|
@@ -64,6 +70,11 @@ def main():
|
|
|
64
70
|
create_flock()
|
|
65
71
|
elif result == CLI_THEME_BUILDER:
|
|
66
72
|
theme_builder()
|
|
73
|
+
elif result == CLI_REGISTRY_MANAGEMENT:
|
|
74
|
+
# Import registry management when needed
|
|
75
|
+
from flock.cli.registry_management import manage_registry
|
|
76
|
+
|
|
77
|
+
manage_registry()
|
|
67
78
|
elif result == CLI_SETTINGS:
|
|
68
79
|
settings_editor()
|
|
69
80
|
elif result == CLI_START_WEB_SERVER:
|
flock/cli/constants.py
CHANGED
|
@@ -9,6 +9,7 @@ CLI_LOAD_EXAMPLE = "Load a example"
|
|
|
9
9
|
CLI_SETTINGS = "Settings"
|
|
10
10
|
CLI_NOTES = "'Hummingbird' release notes"
|
|
11
11
|
CLI_START_WEB_SERVER = "Start web server"
|
|
12
|
+
CLI_REGISTRY_MANAGEMENT = "Registry management"
|
|
12
13
|
CLI_EXIT = "Exit"
|
|
13
14
|
CLI_CHOICES = [
|
|
14
15
|
CLI_CREATE_AGENT,
|
|
@@ -16,6 +17,8 @@ CLI_CHOICES = [
|
|
|
16
17
|
CLI_LOAD_AGENT,
|
|
17
18
|
CLI_LOAD_FLOCK,
|
|
18
19
|
CLI_LOAD_EXAMPLE,
|
|
20
|
+
CLI_THEME_BUILDER,
|
|
21
|
+
CLI_REGISTRY_MANAGEMENT,
|
|
19
22
|
CLI_SETTINGS,
|
|
20
23
|
CLI_START_WEB_SERVER,
|
|
21
24
|
CLI_EXIT,
|
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
"""Registry Management Module for the Flock CLI."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import questionary
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
|
|
15
|
+
from flock.core.flock_registry import (
|
|
16
|
+
get_registry,
|
|
17
|
+
)
|
|
18
|
+
from flock.core.logging.logging import get_logger
|
|
19
|
+
|
|
20
|
+
logger = get_logger("registry_cli")
|
|
21
|
+
console = Console()
|
|
22
|
+
|
|
23
|
+
# Constants for registry item types
|
|
24
|
+
REGISTRY_CATEGORIES = ["Agent", "Callable", "Type", "Component"]
|
|
25
|
+
REGISTRY_ACTIONS = [
|
|
26
|
+
"View Registry Contents",
|
|
27
|
+
"Add Item to Registry",
|
|
28
|
+
"Remove Item from Registry",
|
|
29
|
+
"Auto-Registration Scanner",
|
|
30
|
+
"Export Registry",
|
|
31
|
+
"Back to Main Menu",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def manage_registry() -> None:
|
|
36
|
+
"""Main function for managing the Flock Registry from the CLI."""
|
|
37
|
+
while True:
|
|
38
|
+
console.clear()
|
|
39
|
+
console.print(
|
|
40
|
+
Panel("[bold blue]Flock Registry Management[/]"), justify="center"
|
|
41
|
+
)
|
|
42
|
+
console.line()
|
|
43
|
+
|
|
44
|
+
# Show registry stats
|
|
45
|
+
display_registry_stats()
|
|
46
|
+
|
|
47
|
+
action = questionary.select(
|
|
48
|
+
"What would you like to do?",
|
|
49
|
+
choices=REGISTRY_ACTIONS,
|
|
50
|
+
).ask()
|
|
51
|
+
|
|
52
|
+
if action == "View Registry Contents":
|
|
53
|
+
view_registry_contents()
|
|
54
|
+
elif action == "Add Item to Registry":
|
|
55
|
+
add_item_to_registry()
|
|
56
|
+
elif action == "Remove Item from Registry":
|
|
57
|
+
remove_item_from_registry()
|
|
58
|
+
elif action == "Auto-Registration Scanner":
|
|
59
|
+
auto_registration_scanner()
|
|
60
|
+
elif action == "Export Registry":
|
|
61
|
+
export_registry()
|
|
62
|
+
elif action == "Back to Main Menu":
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
input("\nPress Enter to continue...")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def display_registry_stats() -> None:
|
|
69
|
+
"""Display statistics about the current registry contents."""
|
|
70
|
+
registry = get_registry()
|
|
71
|
+
|
|
72
|
+
table = Table(title="Registry Statistics")
|
|
73
|
+
table.add_column("Category", style="cyan")
|
|
74
|
+
table.add_column("Count", style="green")
|
|
75
|
+
|
|
76
|
+
table.add_row("Agents", str(len(registry._agents)))
|
|
77
|
+
table.add_row("Callables", str(len(registry._callables)))
|
|
78
|
+
table.add_row("Types", str(len(registry._types)))
|
|
79
|
+
table.add_row("Components", str(len(registry._components)))
|
|
80
|
+
|
|
81
|
+
console.print(table)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def view_registry_contents(
|
|
85
|
+
category: str | None = None, search_pattern: str | None = None
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Display registry contents with filtering options."""
|
|
88
|
+
registry = get_registry()
|
|
89
|
+
|
|
90
|
+
if category is None:
|
|
91
|
+
category = questionary.select(
|
|
92
|
+
"Select a category to view:",
|
|
93
|
+
choices=REGISTRY_CATEGORIES + ["All Categories"],
|
|
94
|
+
).ask()
|
|
95
|
+
|
|
96
|
+
if search_pattern is None:
|
|
97
|
+
search_pattern = questionary.text(
|
|
98
|
+
"Enter search pattern (leave empty to show all):"
|
|
99
|
+
).ask()
|
|
100
|
+
|
|
101
|
+
console.clear()
|
|
102
|
+
|
|
103
|
+
if category == "All Categories" or category == "Agent":
|
|
104
|
+
display_registry_section("Agents", registry._agents, search_pattern)
|
|
105
|
+
|
|
106
|
+
if category == "All Categories" or category == "Callable":
|
|
107
|
+
display_registry_section(
|
|
108
|
+
"Callables", registry._callables, search_pattern
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if category == "All Categories" or category == "Type":
|
|
112
|
+
display_registry_section("Types", registry._types, search_pattern)
|
|
113
|
+
|
|
114
|
+
if category == "All Categories" or category == "Component":
|
|
115
|
+
display_registry_section(
|
|
116
|
+
"Components", registry._components, search_pattern
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def display_registry_section(
|
|
121
|
+
title: str, items: dict[str, Any], search_pattern: str
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Display a section of registry items in a table."""
|
|
124
|
+
filtered_items = {
|
|
125
|
+
k: v
|
|
126
|
+
for k, v in items.items()
|
|
127
|
+
if not search_pattern or search_pattern.lower() in k.lower()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if not filtered_items:
|
|
131
|
+
console.print(
|
|
132
|
+
f"[yellow]No {title.lower()} found matching the search pattern.[/]"
|
|
133
|
+
)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
table = Table(title=f"Registered {title}")
|
|
137
|
+
table.add_column("Name/Path", style="cyan")
|
|
138
|
+
table.add_column("Type", style="green")
|
|
139
|
+
|
|
140
|
+
for name, item in filtered_items.items():
|
|
141
|
+
item_type = type(item).__name__
|
|
142
|
+
table.add_row(name, item_type)
|
|
143
|
+
|
|
144
|
+
console.print(table)
|
|
145
|
+
console.print(f"Total: {len(filtered_items)} {title.lower()}")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def add_item_to_registry() -> None:
|
|
149
|
+
"""Add an item to the registry manually."""
|
|
150
|
+
registry = get_registry()
|
|
151
|
+
|
|
152
|
+
item_type = questionary.select(
|
|
153
|
+
"What type of item do you want to add?",
|
|
154
|
+
choices=["agent", "callable", "type", "component"],
|
|
155
|
+
).ask()
|
|
156
|
+
|
|
157
|
+
module_path = questionary.text(
|
|
158
|
+
"Enter the module path (e.g., 'your_module.submodule'):"
|
|
159
|
+
).ask()
|
|
160
|
+
|
|
161
|
+
item_name = questionary.text("Enter the item name within the module:").ask()
|
|
162
|
+
|
|
163
|
+
alias = questionary.text(
|
|
164
|
+
"Enter an alias (optional, press Enter to skip):"
|
|
165
|
+
).ask()
|
|
166
|
+
|
|
167
|
+
if not alias:
|
|
168
|
+
alias = None
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
# Attempt to import the module
|
|
172
|
+
module = importlib.import_module(module_path)
|
|
173
|
+
|
|
174
|
+
# Get the item from the module
|
|
175
|
+
if not hasattr(module, item_name):
|
|
176
|
+
console.print(
|
|
177
|
+
f"[red]Error: {item_name} not found in {module_path}[/]"
|
|
178
|
+
)
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
item = getattr(module, item_name)
|
|
182
|
+
|
|
183
|
+
# Register the item based on its type
|
|
184
|
+
if item_type == "agent":
|
|
185
|
+
registry.register_agent(item)
|
|
186
|
+
console.print(
|
|
187
|
+
f"[green]Successfully registered agent: {item_name}[/]"
|
|
188
|
+
)
|
|
189
|
+
elif item_type == "callable":
|
|
190
|
+
result = registry.register_callable(item, alias)
|
|
191
|
+
console.print(
|
|
192
|
+
f"[green]Successfully registered callable: {result}[/]"
|
|
193
|
+
)
|
|
194
|
+
elif item_type == "type":
|
|
195
|
+
result = registry.register_type(item, alias)
|
|
196
|
+
console.print(f"[green]Successfully registered type: {result}[/]")
|
|
197
|
+
elif item_type == "component":
|
|
198
|
+
result = registry.register_component(item, alias)
|
|
199
|
+
console.print(
|
|
200
|
+
f"[green]Successfully registered component: {result}[/]"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
except ImportError:
|
|
206
|
+
console.print(f"[red]Error: Could not import module {module_path}[/]")
|
|
207
|
+
except Exception as e:
|
|
208
|
+
console.print(f"[red]Error: {e!s}[/]")
|
|
209
|
+
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def remove_item_from_registry() -> None:
|
|
214
|
+
"""Remove an item from the registry."""
|
|
215
|
+
registry = get_registry()
|
|
216
|
+
|
|
217
|
+
item_type = questionary.select(
|
|
218
|
+
"What type of item do you want to remove?",
|
|
219
|
+
choices=["agent", "callable", "type", "component"],
|
|
220
|
+
).ask()
|
|
221
|
+
|
|
222
|
+
# Get the appropriate dictionary based on item type
|
|
223
|
+
if item_type == "agent":
|
|
224
|
+
items = registry._agents
|
|
225
|
+
elif item_type == "callable":
|
|
226
|
+
items = registry._callables
|
|
227
|
+
elif item_type == "type":
|
|
228
|
+
items = registry._types
|
|
229
|
+
elif item_type == "component":
|
|
230
|
+
items = registry._components
|
|
231
|
+
|
|
232
|
+
if not items:
|
|
233
|
+
console.print(f"[yellow]No {item_type}s registered.[/]")
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
# Create a list of items for selection
|
|
237
|
+
item_names = list(items.keys())
|
|
238
|
+
item_name = questionary.select(
|
|
239
|
+
f"Select the {item_type} to remove:",
|
|
240
|
+
choices=item_names + ["Cancel"],
|
|
241
|
+
).ask()
|
|
242
|
+
|
|
243
|
+
if item_name == "Cancel":
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
# Ask for confirmation
|
|
247
|
+
confirm = questionary.confirm(
|
|
248
|
+
f"Are you sure you want to remove {item_name}?",
|
|
249
|
+
default=False,
|
|
250
|
+
).ask()
|
|
251
|
+
|
|
252
|
+
if not confirm:
|
|
253
|
+
console.print("[yellow]Operation cancelled.[/]")
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
# Remove the item
|
|
257
|
+
try:
|
|
258
|
+
if item_type == "agent":
|
|
259
|
+
del registry._agents[item_name]
|
|
260
|
+
elif item_type == "callable":
|
|
261
|
+
del registry._callables[item_name]
|
|
262
|
+
elif item_type == "type":
|
|
263
|
+
del registry._types[item_name]
|
|
264
|
+
elif item_type == "component":
|
|
265
|
+
del registry._components[item_name]
|
|
266
|
+
|
|
267
|
+
console.print(
|
|
268
|
+
f"[green]Successfully removed {item_type}: {item_name}[/]"
|
|
269
|
+
)
|
|
270
|
+
return True
|
|
271
|
+
|
|
272
|
+
except Exception as e:
|
|
273
|
+
console.print(f"[red]Error: {e!s}[/]")
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def auto_registration_scanner() -> None:
|
|
278
|
+
"""Scan directory for potential registry items and optionally register them."""
|
|
279
|
+
# Ask for the target path
|
|
280
|
+
target_path = questionary.text(
|
|
281
|
+
"Enter the path to scan (file or directory):",
|
|
282
|
+
default=os.getcwd(),
|
|
283
|
+
).ask()
|
|
284
|
+
|
|
285
|
+
# Ask if we should recursively scan directories
|
|
286
|
+
recursive = True
|
|
287
|
+
if os.path.isdir(target_path):
|
|
288
|
+
recursive = questionary.confirm(
|
|
289
|
+
"Scan recursively through subdirectories?",
|
|
290
|
+
default=True,
|
|
291
|
+
).ask()
|
|
292
|
+
|
|
293
|
+
# Ask if we should auto-register or just preview
|
|
294
|
+
auto_register = questionary.confirm(
|
|
295
|
+
"Auto-register discovered items? (No for preview only)",
|
|
296
|
+
default=False,
|
|
297
|
+
).ask()
|
|
298
|
+
|
|
299
|
+
# Perform the scan
|
|
300
|
+
scan_results = scan_for_registry_items(
|
|
301
|
+
target_path, recursive, auto_register
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Display results
|
|
305
|
+
console.print(Panel("[bold green]Scan Results[/]"), justify="center")
|
|
306
|
+
|
|
307
|
+
for category, items in scan_results.items():
|
|
308
|
+
if items:
|
|
309
|
+
console.print(f"\n[cyan]{category}:[/] {len(items)} items")
|
|
310
|
+
for item in items:
|
|
311
|
+
console.print(f" - {item}")
|
|
312
|
+
|
|
313
|
+
if auto_register:
|
|
314
|
+
console.print("\n[green]Items have been registered to the registry.[/]")
|
|
315
|
+
else:
|
|
316
|
+
# Ask if we want to register the detected items
|
|
317
|
+
register_now = questionary.confirm(
|
|
318
|
+
"Register these items now?",
|
|
319
|
+
default=False,
|
|
320
|
+
).ask()
|
|
321
|
+
|
|
322
|
+
if register_now:
|
|
323
|
+
# Re-scan with auto-register=True
|
|
324
|
+
scan_for_registry_items(target_path, recursive, True)
|
|
325
|
+
console.print(
|
|
326
|
+
"\n[green]Items have been registered to the registry.[/]"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def scan_for_registry_items(
|
|
331
|
+
target_path: str, recursive: bool = True, auto_register: bool = False
|
|
332
|
+
) -> dict[str, list[str]]:
|
|
333
|
+
"""Scan directory for potential registry items and optionally register them."""
|
|
334
|
+
results = {
|
|
335
|
+
"Agents": [],
|
|
336
|
+
"Callables": [],
|
|
337
|
+
"Types": [],
|
|
338
|
+
"Components": [],
|
|
339
|
+
"Potential Items": [],
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
registry = get_registry()
|
|
343
|
+
path = Path(target_path)
|
|
344
|
+
|
|
345
|
+
with Progress(
|
|
346
|
+
SpinnerColumn(),
|
|
347
|
+
TextColumn("[progress.description]{task.description}"),
|
|
348
|
+
BarColumn(),
|
|
349
|
+
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
|
350
|
+
) as progress:
|
|
351
|
+
scan_task = progress.add_task(f"Scanning {target_path}...", total=100)
|
|
352
|
+
|
|
353
|
+
# If path is a file, scan it directly
|
|
354
|
+
if path.is_file() and path.suffix == ".py":
|
|
355
|
+
module_path = get_module_path_from_file(path)
|
|
356
|
+
if module_path:
|
|
357
|
+
scan_python_file(path, module_path, results, auto_register)
|
|
358
|
+
progress.update(scan_task, completed=100)
|
|
359
|
+
|
|
360
|
+
# If path is a directory, scan all Python files
|
|
361
|
+
elif path.is_dir():
|
|
362
|
+
python_files = []
|
|
363
|
+
if recursive:
|
|
364
|
+
for root, _, files in os.walk(path):
|
|
365
|
+
python_files.extend(
|
|
366
|
+
[
|
|
367
|
+
Path(os.path.join(root, f))
|
|
368
|
+
for f in files
|
|
369
|
+
if f.endswith(".py")
|
|
370
|
+
]
|
|
371
|
+
)
|
|
372
|
+
else:
|
|
373
|
+
python_files = [p for p in path.glob("*.py")]
|
|
374
|
+
|
|
375
|
+
total_files = len(python_files)
|
|
376
|
+
for i, file_path in enumerate(python_files):
|
|
377
|
+
module_path = get_module_path_from_file(file_path)
|
|
378
|
+
if module_path:
|
|
379
|
+
scan_python_file(
|
|
380
|
+
file_path, module_path, results, auto_register
|
|
381
|
+
)
|
|
382
|
+
progress.update(
|
|
383
|
+
scan_task, completed=(i + 1) / total_files * 100
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
return results
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def get_module_path_from_file(file_path: Path) -> str | None:
|
|
390
|
+
"""Convert a file path to a module path for import."""
|
|
391
|
+
try:
|
|
392
|
+
# Get absolute path
|
|
393
|
+
abs_path = file_path.resolve()
|
|
394
|
+
|
|
395
|
+
# Check if it's a Python file
|
|
396
|
+
if abs_path.suffix != ".py":
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
# Get the directory containing the file
|
|
400
|
+
file_dir = abs_path.parent
|
|
401
|
+
|
|
402
|
+
# Find the nearest parent directory with __init__.py
|
|
403
|
+
# to determine the package root
|
|
404
|
+
package_root = None
|
|
405
|
+
current_dir = file_dir
|
|
406
|
+
while current_dir != current_dir.parent:
|
|
407
|
+
if (current_dir / "__init__.py").exists():
|
|
408
|
+
if package_root is None:
|
|
409
|
+
package_root = current_dir
|
|
410
|
+
else:
|
|
411
|
+
# We've reached a directory without __init__.py
|
|
412
|
+
# If we found a package root earlier, use that
|
|
413
|
+
if package_root is not None:
|
|
414
|
+
break
|
|
415
|
+
current_dir = current_dir.parent
|
|
416
|
+
|
|
417
|
+
# If no package root was found, this file can't be imported as a module
|
|
418
|
+
if package_root is None:
|
|
419
|
+
return None
|
|
420
|
+
|
|
421
|
+
# Calculate the module path
|
|
422
|
+
rel_path = abs_path.relative_to(package_root.parent)
|
|
423
|
+
module_path = str(rel_path.with_suffix("")).replace(os.sep, ".")
|
|
424
|
+
|
|
425
|
+
return module_path
|
|
426
|
+
|
|
427
|
+
except Exception as e:
|
|
428
|
+
logger.error(f"Error determining module path: {e}")
|
|
429
|
+
return None
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def scan_python_file(
|
|
433
|
+
file_path: Path,
|
|
434
|
+
module_path: str,
|
|
435
|
+
results: dict[str, list[str]],
|
|
436
|
+
auto_register: bool,
|
|
437
|
+
) -> None:
|
|
438
|
+
"""Scan a Python file for registry-eligible items."""
|
|
439
|
+
try:
|
|
440
|
+
# Try to import the module
|
|
441
|
+
module = importlib.import_module(module_path)
|
|
442
|
+
|
|
443
|
+
# Scan for classes and functions
|
|
444
|
+
for name, obj in inspect.getmembers(module):
|
|
445
|
+
if name.startswith("_"):
|
|
446
|
+
continue
|
|
447
|
+
|
|
448
|
+
# Check for registry decorator presence
|
|
449
|
+
is_registry_item = False
|
|
450
|
+
|
|
451
|
+
# Check for classes
|
|
452
|
+
if inspect.isclass(obj):
|
|
453
|
+
# Check if it has a FlockAgent as a base class
|
|
454
|
+
if is_flock_agent(obj):
|
|
455
|
+
if auto_register:
|
|
456
|
+
get_registry().register_agent(obj)
|
|
457
|
+
results["Agents"].append(f"{module_path}.{name}")
|
|
458
|
+
is_registry_item = True
|
|
459
|
+
|
|
460
|
+
# Check for components
|
|
461
|
+
elif has_component_base(obj):
|
|
462
|
+
if auto_register:
|
|
463
|
+
get_registry().register_component(obj)
|
|
464
|
+
results["Components"].append(f"{module_path}.{name}")
|
|
465
|
+
is_registry_item = True
|
|
466
|
+
|
|
467
|
+
# Check for Pydantic models or dataclasses
|
|
468
|
+
elif is_potential_type(obj):
|
|
469
|
+
if auto_register:
|
|
470
|
+
get_registry().register_type(obj)
|
|
471
|
+
results["Types"].append(f"{module_path}.{name}")
|
|
472
|
+
is_registry_item = True
|
|
473
|
+
|
|
474
|
+
# If not already identified but seems like a potential candidate
|
|
475
|
+
elif not is_registry_item and is_potential_registry_candidate(
|
|
476
|
+
obj
|
|
477
|
+
):
|
|
478
|
+
results["Potential Items"].append(
|
|
479
|
+
f"{module_path}.{name} (class)"
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Check for functions (potential callables/tools)
|
|
483
|
+
elif inspect.isfunction(obj) and obj.__module__ == module.__name__:
|
|
484
|
+
if auto_register:
|
|
485
|
+
get_registry().register_callable(obj)
|
|
486
|
+
results["Callables"].append(f"{module_path}.{name}")
|
|
487
|
+
is_registry_item = True
|
|
488
|
+
|
|
489
|
+
except (ImportError, AttributeError) as e:
|
|
490
|
+
logger.warning(f"Could not import {module_path}: {e}")
|
|
491
|
+
except Exception as e:
|
|
492
|
+
logger.error(f"Error scanning {file_path}: {e}")
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def is_flock_agent(cls: type) -> bool:
|
|
496
|
+
"""Check if a class is a FlockAgent or a subclass of FlockAgent."""
|
|
497
|
+
try:
|
|
498
|
+
from flock.core.flock_agent import FlockAgent
|
|
499
|
+
|
|
500
|
+
return issubclass(cls, FlockAgent)
|
|
501
|
+
except (ImportError, TypeError):
|
|
502
|
+
# If FlockAgent can't be imported or cls is not a class
|
|
503
|
+
return False
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def has_component_base(cls: type) -> bool:
|
|
507
|
+
"""Check if a class has a base class that looks like a Flock component."""
|
|
508
|
+
try:
|
|
509
|
+
# Common Flock component base classes
|
|
510
|
+
component_bases = ["FlockModule", "FlockEvaluator", "FlockRouter"]
|
|
511
|
+
bases = [base.__name__ for base in cls.__mro__]
|
|
512
|
+
return any(base in bases for base in component_bases)
|
|
513
|
+
except (AttributeError, TypeError):
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def is_potential_type(cls: type) -> bool:
|
|
518
|
+
"""Check if a class is a Pydantic model or dataclass."""
|
|
519
|
+
try:
|
|
520
|
+
from dataclasses import is_dataclass
|
|
521
|
+
|
|
522
|
+
from pydantic import BaseModel
|
|
523
|
+
|
|
524
|
+
return issubclass(cls, BaseModel) or is_dataclass(cls)
|
|
525
|
+
except (ImportError, TypeError):
|
|
526
|
+
return False
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def is_potential_registry_candidate(obj: Any) -> bool:
|
|
530
|
+
"""Check if an object seems like it could be registry-eligible."""
|
|
531
|
+
# This is a heuristic function to identify potential registry candidates
|
|
532
|
+
if inspect.isclass(obj):
|
|
533
|
+
# Classes with "Flock" in their name
|
|
534
|
+
if "Flock" in obj.__name__:
|
|
535
|
+
return True
|
|
536
|
+
|
|
537
|
+
# Classes with docstrings mentioning certain keywords
|
|
538
|
+
if obj.__doc__ and any(
|
|
539
|
+
kw in obj.__doc__.lower()
|
|
540
|
+
for kw in [
|
|
541
|
+
"agent",
|
|
542
|
+
"flock",
|
|
543
|
+
"tool",
|
|
544
|
+
"module",
|
|
545
|
+
"evaluator",
|
|
546
|
+
"router",
|
|
547
|
+
]
|
|
548
|
+
):
|
|
549
|
+
return True
|
|
550
|
+
|
|
551
|
+
elif inspect.isfunction(obj):
|
|
552
|
+
# Functions with docstrings mentioning certain keywords
|
|
553
|
+
if obj.__doc__ and any(
|
|
554
|
+
kw in obj.__doc__.lower() for kw in ["tool", "agent", "flock"]
|
|
555
|
+
):
|
|
556
|
+
return True
|
|
557
|
+
|
|
558
|
+
return False
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def export_registry() -> None:
|
|
562
|
+
"""Export the current registry state to a file."""
|
|
563
|
+
registry = get_registry()
|
|
564
|
+
|
|
565
|
+
# Choose export format
|
|
566
|
+
export_format = questionary.select(
|
|
567
|
+
"Select export format:",
|
|
568
|
+
choices=["YAML", "JSON", "Text Report"],
|
|
569
|
+
).ask()
|
|
570
|
+
|
|
571
|
+
# Choose export path
|
|
572
|
+
export_path = questionary.text(
|
|
573
|
+
"Enter export file path:",
|
|
574
|
+
default=f"flock_registry_export.{export_format.lower()}",
|
|
575
|
+
).ask()
|
|
576
|
+
|
|
577
|
+
try:
|
|
578
|
+
export_data = {
|
|
579
|
+
"agents": list(registry._agents.keys()),
|
|
580
|
+
"callables": list(registry._callables.keys()),
|
|
581
|
+
"types": list(registry._types.keys()),
|
|
582
|
+
"components": list(registry._components.keys()),
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if export_format == "YAML":
|
|
586
|
+
import yaml
|
|
587
|
+
|
|
588
|
+
with open(export_path, "w") as f:
|
|
589
|
+
yaml.dump(export_data, f, sort_keys=False, indent=2)
|
|
590
|
+
|
|
591
|
+
elif export_format == "JSON":
|
|
592
|
+
import json
|
|
593
|
+
|
|
594
|
+
with open(export_path, "w") as f:
|
|
595
|
+
json.dump(export_data, f, indent=2)
|
|
596
|
+
|
|
597
|
+
elif export_format == "Text Report":
|
|
598
|
+
with open(export_path, "w") as f:
|
|
599
|
+
f.write("FLOCK REGISTRY EXPORT\n")
|
|
600
|
+
f.write("====================\n\n")
|
|
601
|
+
|
|
602
|
+
for category, items in export_data.items():
|
|
603
|
+
f.write(f"{category.upper()} ({len(items)})\n")
|
|
604
|
+
f.write(
|
|
605
|
+
"-" * (len(category) + 2 + len(str(len(items)))) + "\n"
|
|
606
|
+
)
|
|
607
|
+
for item in sorted(items):
|
|
608
|
+
f.write(f" - {item}\n")
|
|
609
|
+
f.write("\n")
|
|
610
|
+
|
|
611
|
+
console.print(f"[green]Registry exported to {export_path}[/]")
|
|
612
|
+
|
|
613
|
+
except Exception as e:
|
|
614
|
+
console.print(f"[red]Error exporting registry: {e!s}[/]")
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
if __name__ == "__main__":
|
|
618
|
+
manage_registry()
|
flock/core/flock.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
flock/__init__.py,sha256=
|
|
1
|
+
flock/__init__.py,sha256=OO6igJPMOmmFARjY8xjQ2SO96rnSEw2ykJfL_NigtWY,2863
|
|
2
2
|
flock/config.py,sha256=O5QJGlStf4DWSK4ovZsKw01ud4YK3_ij6Ay8sWU8ih0,1522
|
|
3
|
-
flock/cli/constants.py,sha256=
|
|
3
|
+
flock/cli/constants.py,sha256=EgGwFYl6TD7GVm9gASPJTjSl-aYROiFGltwFYp7u1AQ,668
|
|
4
4
|
flock/cli/create_agent.py,sha256=DkeLUlrb7rGx3nZ04aADU9HXXu5mZTf_DBwT0xhzIv4,7
|
|
5
5
|
flock/cli/create_flock.py,sha256=Lry15fAwvnh0rHl1pIJ1xKqVj-nYBteNAE1G83LRaAo,6123
|
|
6
6
|
flock/cli/execute_flock.py,sha256=rAP-5nFfyOivi5uWG8mloZwXF9Tj9iD8MYSbllPllQ4,5726
|
|
@@ -10,12 +10,13 @@ flock/cli/load_flock.py,sha256=5bsdjO5olLtpnFEl_UfisnwR8iSQAkOIWI49tnSwJHc,1537
|
|
|
10
10
|
flock/cli/load_release_notes.py,sha256=qFcgUrMddAE_TP6x1P-6ZywTUjTknfhTDW5LTxtg1yk,599
|
|
11
11
|
flock/cli/loaded_flock_cli.py,sha256=LXjvTjxt84VG67wBfownICGTbzx0Z2JmCOKAg_L2a6Q,5913
|
|
12
12
|
flock/cli/manage_agents.py,sha256=wkNF0IqNFfePrFueR57SILPW885IPqs3U8Cp-fcPPmo,12710
|
|
13
|
+
flock/cli/registry_management.py,sha256=z1ip6AeC78C_YCjGM3PA5rD4eEauqtG9v5Qud1QFRr4,19705
|
|
13
14
|
flock/cli/settings.py,sha256=Z_TXBzCYlCmSaKrJ_CQCdYy-Cj29gpI4kbC_2KzoKqg,27025
|
|
14
15
|
flock/cli/view_results.py,sha256=dOzK0O1FHSIDERnx48y-2Xke9BkOHS7pcOhs64AyIg0,781
|
|
15
16
|
flock/cli/yaml_editor.py,sha256=VpaSwC-Xcbu_gk4HgUeIL7PXNFu1CdstuJ3mRZOkmIk,8096
|
|
16
17
|
flock/cli/assets/release_notes.md,sha256=bqnk50jxM3w5uY44Dc7MkdT8XmRREFxrVBAG9XCOSSU,4896
|
|
17
18
|
flock/core/__init__.py,sha256=6NmSXsdQOoOPWjWqdF8BYiUveb54CjH8Ta0WAjNM0Ps,574
|
|
18
|
-
flock/core/flock.py,sha256=
|
|
19
|
+
flock/core/flock.py,sha256=NhGYNhrZ_QDd57xSwl0zLg4M3nIqtb1Q-Wgq3Eyp0zk,23755
|
|
19
20
|
flock/core/flock_agent.py,sha256=jkDwB7yVGYyLfg-WuwvR2X4XblXOs5DrB2dB30xBL7Q,24754
|
|
20
21
|
flock/core/flock_evaluator.py,sha256=dOXZeDOGZcAmJ9ahqq_2bdGUU1VOXY4skmwTVpAjiVw,1685
|
|
21
22
|
flock/core/flock_factory.py,sha256=MGTkJCP1WGpV614f87r1vwe0tqAvBCoH9PlqtqDyJDk,2828
|
|
@@ -428,8 +429,8 @@ flock/workflow/activities.py,sha256=eVZDnxGJl_quNO-UTV3YgvTV8LrRaHN3QDAA1ANKzac,
|
|
|
428
429
|
flock/workflow/agent_activities.py,sha256=NhBZscflEf2IMfSRa_pBM_TRP7uVEF_O0ROvWZ33eDc,963
|
|
429
430
|
flock/workflow/temporal_setup.py,sha256=VWBgmBgfTBjwM5ruS_dVpA5AVxx6EZ7oFPGw4j3m0l0,1091
|
|
430
431
|
flock/workflow/workflow.py,sha256=I9MryXW_bqYVTHx-nl2epbTqeRy27CAWHHA7ZZA0nAk,1696
|
|
431
|
-
flock_core-0.3.
|
|
432
|
-
flock_core-0.3.
|
|
433
|
-
flock_core-0.3.
|
|
434
|
-
flock_core-0.3.
|
|
435
|
-
flock_core-0.3.
|
|
432
|
+
flock_core-0.3.33.dist-info/METADATA,sha256=YLv1f0lRExTHDwPn1a-xL2-GQI6yG8VVyR9V5jpq5QQ,20745
|
|
433
|
+
flock_core-0.3.33.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
434
|
+
flock_core-0.3.33.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
|
|
435
|
+
flock_core-0.3.33.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
|
|
436
|
+
flock_core-0.3.33.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|