edsl 0.1.49__py3-none-any.whl → 0.1.51__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.
- edsl/__init__.py +124 -53
- edsl/__version__.py +1 -1
- edsl/agents/agent.py +21 -21
- edsl/agents/agent_list.py +2 -5
- edsl/agents/exceptions.py +119 -5
- edsl/base/__init__.py +10 -35
- edsl/base/base_class.py +71 -36
- edsl/base/base_exception.py +204 -0
- edsl/base/data_transfer_models.py +1 -1
- edsl/base/exceptions.py +94 -0
- edsl/buckets/__init__.py +15 -1
- edsl/buckets/bucket_collection.py +3 -4
- edsl/buckets/exceptions.py +107 -0
- edsl/buckets/model_buckets.py +1 -2
- edsl/buckets/token_bucket.py +11 -6
- edsl/buckets/token_bucket_api.py +27 -12
- edsl/buckets/token_bucket_client.py +9 -7
- edsl/caching/cache.py +12 -4
- edsl/caching/cache_entry.py +10 -9
- edsl/caching/exceptions.py +113 -7
- edsl/caching/remote_cache_sync.py +6 -7
- edsl/caching/sql_dict.py +20 -14
- edsl/cli.py +43 -0
- edsl/config/__init__.py +1 -1
- edsl/config/config_class.py +32 -6
- edsl/conversation/Conversation.py +8 -4
- edsl/conversation/car_buying.py +1 -3
- edsl/conversation/exceptions.py +58 -0
- edsl/conversation/mug_negotiation.py +2 -8
- edsl/coop/__init__.py +28 -6
- edsl/coop/coop.py +120 -29
- edsl/coop/coop_functions.py +1 -1
- edsl/coop/ep_key_handling.py +1 -1
- edsl/coop/exceptions.py +188 -9
- edsl/coop/price_fetcher.py +5 -8
- edsl/coop/utils.py +4 -6
- edsl/dataset/__init__.py +5 -4
- edsl/dataset/dataset.py +177 -86
- edsl/dataset/dataset_operations_mixin.py +98 -76
- edsl/dataset/dataset_tree.py +11 -7
- edsl/dataset/display/table_display.py +0 -2
- edsl/dataset/display/table_renderers.py +6 -4
- edsl/dataset/exceptions.py +125 -0
- edsl/dataset/file_exports.py +18 -11
- edsl/dataset/r/ggplot.py +13 -6
- edsl/display/__init__.py +27 -0
- edsl/display/core.py +147 -0
- edsl/display/plugin.py +189 -0
- edsl/display/utils.py +52 -0
- edsl/inference_services/__init__.py +9 -1
- edsl/inference_services/available_model_cache_handler.py +1 -1
- edsl/inference_services/available_model_fetcher.py +5 -6
- edsl/inference_services/data_structures.py +10 -7
- edsl/inference_services/exceptions.py +132 -1
- edsl/inference_services/inference_service_abc.py +2 -2
- edsl/inference_services/inference_services_collection.py +2 -6
- edsl/inference_services/registry.py +4 -3
- edsl/inference_services/service_availability.py +4 -3
- edsl/inference_services/services/anthropic_service.py +4 -1
- edsl/inference_services/services/aws_bedrock.py +13 -12
- edsl/inference_services/services/azure_ai.py +12 -10
- edsl/inference_services/services/deep_infra_service.py +1 -4
- edsl/inference_services/services/deep_seek_service.py +1 -5
- edsl/inference_services/services/google_service.py +7 -3
- edsl/inference_services/services/groq_service.py +1 -1
- edsl/inference_services/services/mistral_ai_service.py +4 -2
- edsl/inference_services/services/ollama_service.py +1 -1
- edsl/inference_services/services/open_ai_service.py +7 -5
- edsl/inference_services/services/perplexity_service.py +6 -2
- edsl/inference_services/services/test_service.py +8 -7
- edsl/inference_services/services/together_ai_service.py +2 -3
- edsl/inference_services/services/xai_service.py +1 -1
- edsl/instructions/__init__.py +1 -1
- edsl/instructions/change_instruction.py +7 -5
- edsl/instructions/exceptions.py +61 -0
- edsl/instructions/instruction.py +6 -2
- edsl/instructions/instruction_collection.py +6 -4
- edsl/instructions/instruction_handler.py +12 -15
- edsl/interviews/ReportErrors.py +0 -3
- edsl/interviews/__init__.py +9 -2
- edsl/interviews/answering_function.py +11 -13
- edsl/interviews/exception_tracking.py +15 -8
- edsl/interviews/exceptions.py +79 -0
- edsl/interviews/interview.py +33 -30
- edsl/interviews/interview_status_dictionary.py +4 -2
- edsl/interviews/interview_status_log.py +2 -1
- edsl/interviews/interview_task_manager.py +5 -5
- edsl/interviews/request_token_estimator.py +5 -2
- edsl/interviews/statistics.py +3 -4
- edsl/invigilators/__init__.py +7 -1
- edsl/invigilators/exceptions.py +79 -0
- edsl/invigilators/invigilator_base.py +0 -1
- edsl/invigilators/invigilators.py +9 -13
- edsl/invigilators/prompt_constructor.py +1 -5
- edsl/invigilators/prompt_helpers.py +8 -4
- edsl/invigilators/question_instructions_prompt_builder.py +1 -1
- edsl/invigilators/question_option_processor.py +9 -5
- edsl/invigilators/question_template_replacements_builder.py +3 -2
- edsl/jobs/__init__.py +42 -5
- edsl/jobs/async_interview_runner.py +25 -23
- edsl/jobs/check_survey_scenario_compatibility.py +11 -10
- edsl/jobs/data_structures.py +8 -5
- edsl/jobs/exceptions.py +177 -8
- edsl/jobs/fetch_invigilator.py +1 -1
- edsl/jobs/jobs.py +74 -69
- edsl/jobs/jobs_checks.py +6 -7
- edsl/jobs/jobs_component_constructor.py +4 -4
- edsl/jobs/jobs_pricing_estimation.py +4 -3
- edsl/jobs/jobs_remote_inference_logger.py +5 -4
- edsl/jobs/jobs_runner_asyncio.py +3 -4
- edsl/jobs/jobs_runner_status.py +8 -9
- edsl/jobs/remote_inference.py +27 -24
- edsl/jobs/results_exceptions_handler.py +10 -7
- edsl/key_management/__init__.py +3 -1
- edsl/key_management/exceptions.py +62 -0
- edsl/key_management/key_lookup.py +1 -1
- edsl/key_management/key_lookup_builder.py +37 -14
- edsl/key_management/key_lookup_collection.py +2 -0
- edsl/language_models/__init__.py +1 -1
- edsl/language_models/exceptions.py +302 -14
- edsl/language_models/language_model.py +9 -8
- edsl/language_models/model.py +4 -4
- edsl/language_models/model_list.py +1 -1
- edsl/language_models/price_manager.py +1 -1
- edsl/language_models/raw_response_handler.py +14 -9
- edsl/language_models/registry.py +17 -21
- edsl/language_models/repair.py +0 -6
- edsl/language_models/unused/fake_openai_service.py +0 -1
- edsl/load_plugins.py +69 -0
- edsl/logger.py +146 -0
- edsl/notebooks/__init__.py +24 -1
- edsl/notebooks/exceptions.py +82 -0
- edsl/notebooks/notebook.py +7 -3
- edsl/notebooks/notebook_to_latex.py +1 -2
- edsl/plugins/__init__.py +63 -0
- edsl/plugins/built_in/export_example.py +50 -0
- edsl/plugins/built_in/pig_latin.py +67 -0
- edsl/plugins/cli.py +372 -0
- edsl/plugins/cli_typer.py +283 -0
- edsl/plugins/exceptions.py +31 -0
- edsl/plugins/hookspec.py +51 -0
- edsl/plugins/plugin_host.py +128 -0
- edsl/plugins/plugin_manager.py +633 -0
- edsl/plugins/plugins_registry.py +168 -0
- edsl/prompts/__init__.py +24 -1
- edsl/prompts/exceptions.py +107 -5
- edsl/prompts/prompt.py +15 -7
- edsl/questions/HTMLQuestion.py +5 -11
- edsl/questions/Quick.py +0 -1
- edsl/questions/__init__.py +6 -4
- edsl/questions/answer_validator_mixin.py +318 -323
- edsl/questions/compose_questions.py +3 -3
- edsl/questions/descriptors.py +11 -50
- edsl/questions/exceptions.py +278 -22
- edsl/questions/loop_processor.py +7 -5
- edsl/questions/prompt_templates/question_list.jinja +3 -0
- edsl/questions/question_base.py +46 -19
- edsl/questions/question_base_gen_mixin.py +2 -2
- edsl/questions/question_base_prompts_mixin.py +13 -7
- edsl/questions/question_budget.py +503 -98
- edsl/questions/question_check_box.py +660 -160
- edsl/questions/question_dict.py +345 -194
- edsl/questions/question_extract.py +401 -61
- edsl/questions/question_free_text.py +80 -14
- edsl/questions/question_functional.py +119 -9
- edsl/questions/{derived/question_likert_five.py → question_likert_five.py} +2 -2
- edsl/questions/{derived/question_linear_scale.py → question_linear_scale.py} +3 -4
- edsl/questions/question_list.py +275 -28
- edsl/questions/question_matrix.py +643 -96
- edsl/questions/question_multiple_choice.py +219 -51
- edsl/questions/question_numerical.py +361 -32
- edsl/questions/question_rank.py +401 -124
- edsl/questions/question_registry.py +7 -5
- edsl/questions/{derived/question_top_k.py → question_top_k.py} +3 -3
- edsl/questions/{derived/question_yes_no.py → question_yes_no.py} +3 -4
- edsl/questions/register_questions_meta.py +2 -2
- edsl/questions/response_validator_abc.py +13 -15
- edsl/questions/response_validator_factory.py +10 -12
- edsl/questions/templates/dict/answering_instructions.jinja +1 -0
- edsl/questions/templates/rank/question_presentation.jinja +1 -1
- edsl/results/__init__.py +1 -1
- edsl/results/exceptions.py +141 -7
- edsl/results/report.py +1 -2
- edsl/results/result.py +11 -9
- edsl/results/results.py +480 -321
- edsl/results/results_selector.py +8 -4
- edsl/scenarios/PdfExtractor.py +2 -2
- edsl/scenarios/construct_download_link.py +69 -35
- edsl/scenarios/directory_scanner.py +33 -14
- edsl/scenarios/document_chunker.py +1 -1
- edsl/scenarios/exceptions.py +238 -14
- edsl/scenarios/file_methods.py +1 -1
- edsl/scenarios/file_store.py +7 -3
- edsl/scenarios/handlers/__init__.py +17 -0
- edsl/scenarios/handlers/docx_file_store.py +0 -5
- edsl/scenarios/handlers/pdf_file_store.py +0 -1
- edsl/scenarios/handlers/pptx_file_store.py +0 -5
- edsl/scenarios/handlers/py_file_store.py +0 -1
- edsl/scenarios/handlers/sql_file_store.py +1 -4
- edsl/scenarios/handlers/sqlite_file_store.py +0 -1
- edsl/scenarios/handlers/txt_file_store.py +1 -1
- edsl/scenarios/scenario.py +1 -3
- edsl/scenarios/scenario_list.py +179 -27
- edsl/scenarios/scenario_list_pdf_tools.py +1 -0
- edsl/scenarios/scenario_selector.py +0 -1
- edsl/surveys/__init__.py +3 -4
- edsl/surveys/dag/__init__.py +4 -2
- edsl/surveys/descriptors.py +1 -1
- edsl/surveys/edit_survey.py +1 -0
- edsl/surveys/exceptions.py +165 -9
- edsl/surveys/memory/__init__.py +5 -3
- edsl/surveys/memory/memory_management.py +1 -0
- edsl/surveys/memory/memory_plan.py +6 -15
- edsl/surveys/rules/__init__.py +5 -3
- edsl/surveys/rules/rule.py +1 -2
- edsl/surveys/rules/rule_collection.py +1 -1
- edsl/surveys/survey.py +12 -24
- edsl/surveys/survey_css.py +3 -3
- edsl/surveys/survey_export.py +6 -3
- edsl/surveys/survey_flow_visualization.py +10 -1
- edsl/surveys/survey_simulator.py +2 -1
- edsl/tasks/__init__.py +23 -1
- edsl/tasks/exceptions.py +72 -0
- edsl/tasks/question_task_creator.py +3 -3
- edsl/tasks/task_creators.py +1 -3
- edsl/tasks/task_history.py +8 -10
- edsl/tasks/task_status_log.py +1 -2
- edsl/tokens/__init__.py +29 -1
- edsl/tokens/exceptions.py +37 -0
- edsl/tokens/interview_token_usage.py +3 -2
- edsl/tokens/token_usage.py +4 -3
- edsl/utilities/__init__.py +21 -1
- edsl/utilities/decorators.py +1 -2
- edsl/utilities/markdown_to_docx.py +2 -2
- edsl/utilities/markdown_to_pdf.py +1 -1
- edsl/utilities/repair_functions.py +0 -1
- edsl/utilities/restricted_python.py +0 -1
- edsl/utilities/template_loader.py +2 -3
- edsl/utilities/utilities.py +8 -29
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/METADATA +32 -2
- edsl-0.1.51.dist-info/RECORD +365 -0
- edsl-0.1.51.dist-info/entry_points.txt +3 -0
- edsl/dataset/smart_objects.py +0 -96
- edsl/exceptions/BaseException.py +0 -21
- edsl/exceptions/__init__.py +0 -54
- edsl/exceptions/configuration.py +0 -16
- edsl/exceptions/general.py +0 -34
- edsl/questions/derived/__init__.py +0 -0
- edsl/study/ObjectEntry.py +0 -173
- edsl/study/ProofOfWork.py +0 -113
- edsl/study/SnapShot.py +0 -80
- edsl/study/Study.py +0 -520
- edsl/study/__init__.py +0 -6
- edsl/utilities/interface.py +0 -135
- edsl-0.1.49.dist-info/RECORD +0 -347
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/LICENSE +0 -0
- {edsl-0.1.49.dist-info → edsl-0.1.51.dist-info}/WHEEL +0 -0
edsl/plugins/cli.py
ADDED
@@ -0,0 +1,372 @@
|
|
1
|
+
"""
|
2
|
+
Command-line interface for managing EDSL plugins.
|
3
|
+
|
4
|
+
This module provides a text-based interface for listing, installing,
|
5
|
+
updating, and removing EDSL plugins.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import sys
|
9
|
+
import argparse
|
10
|
+
import textwrap
|
11
|
+
from typing import Optional, List, Dict, Any, Tuple
|
12
|
+
from dataclasses import asdict
|
13
|
+
import os
|
14
|
+
import re
|
15
|
+
|
16
|
+
from .plugin_host import PluginHost, get_plugin_manager
|
17
|
+
from .exceptions import (
|
18
|
+
PluginException,
|
19
|
+
PluginNotFoundError,
|
20
|
+
PluginInstallationError,
|
21
|
+
GitHubRepoError,
|
22
|
+
InvalidPluginError
|
23
|
+
)
|
24
|
+
from .plugins_registry import (
|
25
|
+
AvailablePlugin,
|
26
|
+
get_available_plugins,
|
27
|
+
search_plugins,
|
28
|
+
get_plugin_details,
|
29
|
+
get_github_url_by_name
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
class PluginCLI:
|
34
|
+
"""Command-line interface for managing EDSL plugins."""
|
35
|
+
|
36
|
+
def __init__(self):
|
37
|
+
"""Initialize the CLI."""
|
38
|
+
self.plugin_manager = get_plugin_manager()
|
39
|
+
self.parser = self._create_parser()
|
40
|
+
|
41
|
+
def _create_parser(self) -> argparse.ArgumentParser:
|
42
|
+
"""Create the argument parser for the CLI."""
|
43
|
+
parser = argparse.ArgumentParser(
|
44
|
+
description="EDSL Plugin Manager",
|
45
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
46
|
+
epilog=textwrap.dedent("""
|
47
|
+
Examples:
|
48
|
+
edsl plugins list # List all installed plugins
|
49
|
+
edsl plugins available # List available plugins from repository
|
50
|
+
edsl plugins search text # Search for plugins related to text
|
51
|
+
edsl plugins info text_analysis # Get detailed info about a plugin
|
52
|
+
edsl plugins install text_analysis # Install a plugin by name
|
53
|
+
edsl plugins install text_analysis --url https://github.com/example/repo # Install with explicit URL
|
54
|
+
edsl plugins uninstall text_analysis
|
55
|
+
""")
|
56
|
+
)
|
57
|
+
|
58
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
59
|
+
|
60
|
+
# List command
|
61
|
+
list_parser = subparsers.add_parser("list", help="List installed plugins")
|
62
|
+
|
63
|
+
# Available command
|
64
|
+
available_parser = subparsers.add_parser("available", help="List available plugins")
|
65
|
+
|
66
|
+
# Search command
|
67
|
+
search_parser = subparsers.add_parser("search", help="Search for plugins")
|
68
|
+
search_parser.add_argument("query", help="Search query")
|
69
|
+
search_parser.add_argument("--tags", nargs="+", help="Filter by tags")
|
70
|
+
|
71
|
+
# Info command
|
72
|
+
info_parser = subparsers.add_parser("info", help="Get detailed info about a plugin")
|
73
|
+
info_parser.add_argument("name", help="Plugin name")
|
74
|
+
|
75
|
+
# Install command
|
76
|
+
install_parser = subparsers.add_parser("install", help="Install a plugin")
|
77
|
+
install_parser.add_argument("name", help="Name of the plugin to install")
|
78
|
+
install_parser.add_argument("--branch", help="Branch to install from")
|
79
|
+
install_parser.add_argument("--url", help="Directly specify GitHub URL instead of using the registry")
|
80
|
+
|
81
|
+
# Uninstall command
|
82
|
+
uninstall_parser = subparsers.add_parser("uninstall", help="Uninstall a plugin")
|
83
|
+
uninstall_parser.add_argument("name", help="Plugin name")
|
84
|
+
|
85
|
+
return parser
|
86
|
+
|
87
|
+
def run(self, args: Optional[List[str]] = None) -> int:
|
88
|
+
"""
|
89
|
+
Run the CLI with the given arguments.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
args: Command-line arguments (defaults to sys.argv[1:])
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
Exit code (0 for success, non-zero for error)
|
96
|
+
"""
|
97
|
+
if args is None:
|
98
|
+
args = sys.argv[1:]
|
99
|
+
|
100
|
+
# Parse arguments
|
101
|
+
args = self.parser.parse_args(args)
|
102
|
+
|
103
|
+
try:
|
104
|
+
# Execute the requested command
|
105
|
+
if args.command == "list":
|
106
|
+
self._list_plugins()
|
107
|
+
elif args.command == "available":
|
108
|
+
self._list_available_plugins()
|
109
|
+
elif args.command == "search":
|
110
|
+
self._search_plugins(args.query, args.tags)
|
111
|
+
elif args.command == "info":
|
112
|
+
self._show_plugin_info(args.name)
|
113
|
+
elif args.command == "install":
|
114
|
+
self._install_plugin(args.name, args.branch, args.url)
|
115
|
+
elif args.command == "uninstall":
|
116
|
+
self._uninstall_plugin(args.name)
|
117
|
+
else:
|
118
|
+
self.parser.print_help()
|
119
|
+
return 1
|
120
|
+
|
121
|
+
return 0
|
122
|
+
|
123
|
+
except PluginException as e:
|
124
|
+
print(f"Error: {str(e)}")
|
125
|
+
return 1
|
126
|
+
except Exception as e:
|
127
|
+
print(f"Unexpected error: {str(e)}")
|
128
|
+
return 1
|
129
|
+
|
130
|
+
def _list_plugins(self) -> None:
|
131
|
+
"""List all installed plugins."""
|
132
|
+
plugins_info = self.plugin_manager.list_plugins()
|
133
|
+
|
134
|
+
if not plugins_info:
|
135
|
+
print("No plugins installed.")
|
136
|
+
return
|
137
|
+
|
138
|
+
# Print a table of installed plugins
|
139
|
+
print("\nInstalled Plugins:")
|
140
|
+
print("-" * 80)
|
141
|
+
print(f"{'Name':<20} {'Version':<10} {'Description':<50}")
|
142
|
+
print("-" * 80)
|
143
|
+
|
144
|
+
for name, info in plugins_info.items():
|
145
|
+
# Truncate description if too long
|
146
|
+
description = info.get('description', '')
|
147
|
+
if description and len(description) > 47:
|
148
|
+
description = description[:47] + "..."
|
149
|
+
|
150
|
+
print(f"{name:<20} {'unknown':<10} {description:<50}")
|
151
|
+
|
152
|
+
print("\nUse 'edsl plugins info <name>' for more details about a specific plugin.")
|
153
|
+
|
154
|
+
def _list_available_plugins(self) -> None:
|
155
|
+
"""List all available plugins from the repository."""
|
156
|
+
try:
|
157
|
+
plugins = get_available_plugins()
|
158
|
+
|
159
|
+
if not plugins:
|
160
|
+
print("No plugins available.")
|
161
|
+
return
|
162
|
+
|
163
|
+
# Print a table of available plugins
|
164
|
+
print("\nAvailable Plugins:")
|
165
|
+
print("-" * 100)
|
166
|
+
print(f"{'Name':<20} {'Version':<10} {'Rating':<8} {'Downloads':<10} {'Description':<50}")
|
167
|
+
print("-" * 100)
|
168
|
+
|
169
|
+
for plugin in plugins:
|
170
|
+
# Truncate description if too long
|
171
|
+
description = plugin.description
|
172
|
+
if len(description) > 47:
|
173
|
+
description = description[:47] + "..."
|
174
|
+
|
175
|
+
print(f"{plugin.name:<20} {plugin.version:<10} {plugin.rating:<8.1f} {plugin.downloads:<10} {description:<50}")
|
176
|
+
|
177
|
+
print("\nUse 'edsl plugins info <name>' for more details about a specific plugin.")
|
178
|
+
print("Use 'edsl plugins install <name>' to install a plugin.")
|
179
|
+
|
180
|
+
except Exception as e:
|
181
|
+
print(f"Error fetching available plugins: {str(e)}")
|
182
|
+
|
183
|
+
def _search_plugins(self, query: str, tags: Optional[List[str]] = None) -> None:
|
184
|
+
"""
|
185
|
+
Search for plugins matching a query string or tags.
|
186
|
+
|
187
|
+
Args:
|
188
|
+
query: Search query string
|
189
|
+
tags: Optional list of tags to filter by
|
190
|
+
"""
|
191
|
+
try:
|
192
|
+
results = search_plugins(query, tags)
|
193
|
+
|
194
|
+
if not results:
|
195
|
+
print(f"No plugins found matching '{query}'.")
|
196
|
+
return
|
197
|
+
|
198
|
+
# Print a table of search results
|
199
|
+
print(f"\nSearch Results for '{query}':")
|
200
|
+
if tags:
|
201
|
+
print(f"Filtered by tags: {', '.join(tags)}")
|
202
|
+
|
203
|
+
print("-" * 100)
|
204
|
+
print(f"{'Name':<20} {'Version':<10} {'Rating':<8} {'Tags':<25} {'Description':<35}")
|
205
|
+
print("-" * 100)
|
206
|
+
|
207
|
+
for plugin in results:
|
208
|
+
# Truncate description if too long
|
209
|
+
description = plugin.description
|
210
|
+
if len(description) > 32:
|
211
|
+
description = description[:32] + "..."
|
212
|
+
|
213
|
+
# Format tags
|
214
|
+
tag_str = ", ".join(plugin.tags[:3])
|
215
|
+
if len(plugin.tags) > 3:
|
216
|
+
tag_str += "..."
|
217
|
+
if len(tag_str) > 22:
|
218
|
+
tag_str = tag_str[:22] + "..."
|
219
|
+
|
220
|
+
print(f"{plugin.name:<20} {plugin.version:<10} {plugin.rating:<8.1f} {tag_str:<25} {description:<35}")
|
221
|
+
|
222
|
+
print("\nUse 'edsl plugins info <name>' for more details about a specific plugin.")
|
223
|
+
|
224
|
+
except Exception as e:
|
225
|
+
print(f"Error searching for plugins: {str(e)}")
|
226
|
+
|
227
|
+
def _show_plugin_info(self, name: str) -> None:
|
228
|
+
"""
|
229
|
+
Show detailed information about a specific plugin.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
name: Name of the plugin to show info for
|
233
|
+
"""
|
234
|
+
# First check if it's an installed plugin
|
235
|
+
local_plugins = self.plugin_manager.list_plugins()
|
236
|
+
|
237
|
+
if name in local_plugins:
|
238
|
+
info = local_plugins[name]
|
239
|
+
print(f"\nPlugin: {name} (Installed)")
|
240
|
+
print("-" * 80)
|
241
|
+
print(f"Description: {info.get('description', 'No description available')}")
|
242
|
+
print(f"Methods: {', '.join(info.get('methods', []))}")
|
243
|
+
print(f"Installed from: {info.get('installed_from', 'Unknown')}")
|
244
|
+
print("\nUse 'edsl plugins uninstall {name}' to uninstall this plugin.")
|
245
|
+
return
|
246
|
+
|
247
|
+
# If not installed, check if it's available
|
248
|
+
try:
|
249
|
+
info = get_plugin_details(name)
|
250
|
+
|
251
|
+
if not info:
|
252
|
+
print(f"Plugin '{name}' not found.")
|
253
|
+
return
|
254
|
+
|
255
|
+
print(f"\nPlugin: {info['name']}")
|
256
|
+
print("-" * 80)
|
257
|
+
print(f"Description: {info['description']}")
|
258
|
+
print(f"Version: {info['version']}")
|
259
|
+
print(f"Author: {info['author']}")
|
260
|
+
print(f"Tags: {', '.join(info['tags'])}")
|
261
|
+
print(f"GitHub URL: {info['github_url']}")
|
262
|
+
print(f"Created: {info['created_at']}")
|
263
|
+
print(f"Last Updated: {info.get('last_update', 'Unknown')}")
|
264
|
+
print(f"Downloads: {info['downloads']}")
|
265
|
+
print(f"Rating: {info['rating']:.1f}/5.0")
|
266
|
+
print(f"License: {info.get('license', 'Unknown')}")
|
267
|
+
print(f"Dependencies: {', '.join(info.get('dependencies', ['None']))}")
|
268
|
+
print(f"Compatible EDSL versions: {', '.join(info.get('compatible_edsl_versions', ['Unknown']))}")
|
269
|
+
print("\nDocumentation:")
|
270
|
+
print(f" Homepage: {info.get('homepage', 'N/A')}")
|
271
|
+
print(f" Docs: {info.get('documentation', 'N/A')}")
|
272
|
+
|
273
|
+
if 'examples' in info and info['examples']:
|
274
|
+
print("\nExamples:")
|
275
|
+
for example in info['examples']:
|
276
|
+
print(f" - {example}")
|
277
|
+
|
278
|
+
print(f"\nUse 'edsl plugins install {info['github_url']}' to install this plugin.")
|
279
|
+
|
280
|
+
except Exception as e:
|
281
|
+
print(f"Error retrieving plugin information: {str(e)}")
|
282
|
+
|
283
|
+
def _install_plugin(self, plugin_name: str, branch: Optional[str] = None, url: Optional[str] = None) -> None:
|
284
|
+
"""
|
285
|
+
Install a plugin by name or URL.
|
286
|
+
|
287
|
+
Args:
|
288
|
+
plugin_name: Name of the plugin to install
|
289
|
+
branch: Optional branch to checkout (defaults to main/master)
|
290
|
+
url: Optional GitHub URL to use instead of registry lookup
|
291
|
+
"""
|
292
|
+
try:
|
293
|
+
github_url = url
|
294
|
+
|
295
|
+
# If URL not provided, look up in registry by name
|
296
|
+
if not github_url:
|
297
|
+
github_url = get_github_url_by_name(plugin_name)
|
298
|
+
if not github_url:
|
299
|
+
print(f"Plugin '{plugin_name}' not found in registry.")
|
300
|
+
print("Use 'edsl plugins available' to see available plugins.")
|
301
|
+
print("Or provide the GitHub URL with '--url' if you know it.")
|
302
|
+
return
|
303
|
+
|
304
|
+
print(f"Installing plugin '{plugin_name}' from {github_url}...")
|
305
|
+
if branch:
|
306
|
+
print(f"Using branch: {branch}")
|
307
|
+
|
308
|
+
# Install the plugin
|
309
|
+
installed_plugins = PluginHost.install_from_github(github_url, branch)
|
310
|
+
|
311
|
+
print(f"Successfully installed plugin(s): {', '.join(installed_plugins)}")
|
312
|
+
print("\nUse 'edsl plugins list' to see all installed plugins.")
|
313
|
+
|
314
|
+
except PluginException as e:
|
315
|
+
print(f"Error installing plugin: {str(e)}")
|
316
|
+
raise
|
317
|
+
except Exception as e:
|
318
|
+
print(f"Unexpected error during installation: {str(e)}")
|
319
|
+
raise
|
320
|
+
|
321
|
+
def _uninstall_plugin(self, name: str) -> None:
|
322
|
+
"""
|
323
|
+
Uninstall a plugin by name.
|
324
|
+
|
325
|
+
Args:
|
326
|
+
name: Name of the plugin to uninstall
|
327
|
+
"""
|
328
|
+
try:
|
329
|
+
print(f"Uninstalling plugin '{name}'...")
|
330
|
+
|
331
|
+
# Uninstall the plugin
|
332
|
+
PluginHost.uninstall_plugin(name)
|
333
|
+
|
334
|
+
print(f"Successfully uninstalled plugin '{name}'.")
|
335
|
+
|
336
|
+
except PluginException as e:
|
337
|
+
print(f"Error uninstalling plugin: {str(e)}")
|
338
|
+
raise
|
339
|
+
except Exception as e:
|
340
|
+
print(f"Unexpected error during uninstallation: {str(e)}")
|
341
|
+
raise
|
342
|
+
|
343
|
+
def _get_plugin_name_from_url(self, url: str) -> str:
|
344
|
+
"""
|
345
|
+
Extract a plugin name from a GitHub URL.
|
346
|
+
|
347
|
+
Args:
|
348
|
+
url: GitHub URL
|
349
|
+
|
350
|
+
Returns:
|
351
|
+
Extracted plugin name or a placeholder
|
352
|
+
"""
|
353
|
+
# Try to extract the repository name from the URL
|
354
|
+
match = re.search(r"github\.com/[^/]+/([^/]+)", url)
|
355
|
+
if match:
|
356
|
+
repo_name = match.group(1)
|
357
|
+
# Convert repo name to plugin name
|
358
|
+
if repo_name.startswith("plugin-"):
|
359
|
+
return repo_name[7:] # Remove "plugin-" prefix
|
360
|
+
elif repo_name.startswith("edsl-plugin-"):
|
361
|
+
return repo_name[12:] # Remove "edsl-plugin-" prefix
|
362
|
+
else:
|
363
|
+
return repo_name
|
364
|
+
|
365
|
+
# If we can't extract a name, return a placeholder
|
366
|
+
return "plugin"
|
367
|
+
|
368
|
+
|
369
|
+
def main():
|
370
|
+
"""Main entry point for the CLI."""
|
371
|
+
cli = PluginCLI()
|
372
|
+
sys.exit(cli.run())
|
@@ -0,0 +1,283 @@
|
|
1
|
+
"""
|
2
|
+
Command-line interface for managing EDSL plugins using Typer.
|
3
|
+
|
4
|
+
This module provides a Typer-based CLI for listing, installing,
|
5
|
+
updating, and removing EDSL plugins.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import sys
|
9
|
+
from typing import Optional, List, Dict, Any
|
10
|
+
import re
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
import typer
|
14
|
+
from rich.console import Console
|
15
|
+
from rich.table import Table
|
16
|
+
from rich.panel import Panel
|
17
|
+
from rich.text import Text
|
18
|
+
from rich import box
|
19
|
+
|
20
|
+
from .plugin_host import PluginHost, get_plugin_manager
|
21
|
+
from .exceptions import (
|
22
|
+
PluginException,
|
23
|
+
PluginNotFoundError,
|
24
|
+
PluginInstallationError,
|
25
|
+
GitHubRepoError,
|
26
|
+
InvalidPluginError
|
27
|
+
)
|
28
|
+
from .plugins_registry import (
|
29
|
+
AvailablePlugin,
|
30
|
+
get_available_plugins,
|
31
|
+
search_plugins,
|
32
|
+
get_plugin_details,
|
33
|
+
get_github_url_by_name
|
34
|
+
)
|
35
|
+
|
36
|
+
# Create the Typer app
|
37
|
+
app = typer.Typer(help="Manage EDSL plugins")
|
38
|
+
console = Console()
|
39
|
+
|
40
|
+
@app.command("list")
|
41
|
+
def list_plugins():
|
42
|
+
"""List all installed plugins."""
|
43
|
+
plugin_manager = get_plugin_manager()
|
44
|
+
plugins_info = plugin_manager.list_plugins()
|
45
|
+
|
46
|
+
if not plugins_info:
|
47
|
+
console.print("[yellow]No plugins installed.[/yellow]")
|
48
|
+
return
|
49
|
+
|
50
|
+
# Create a rich table
|
51
|
+
table = Table(title="Installed Plugins", box=box.ROUNDED)
|
52
|
+
table.add_column("Name", style="cyan")
|
53
|
+
table.add_column("Version", style="green")
|
54
|
+
table.add_column("Description")
|
55
|
+
|
56
|
+
for name, info in plugins_info.items():
|
57
|
+
description = info.get('description', '')
|
58
|
+
table.add_row(
|
59
|
+
name,
|
60
|
+
info.get('version', 'unknown'),
|
61
|
+
description
|
62
|
+
)
|
63
|
+
|
64
|
+
console.print(table)
|
65
|
+
console.print("\nUse [bold cyan]edsl plugins info <name>[/bold cyan] for more details about a specific plugin.")
|
66
|
+
|
67
|
+
@app.command("available")
|
68
|
+
def list_available_plugins():
|
69
|
+
"""List all available plugins from the repository."""
|
70
|
+
try:
|
71
|
+
plugins = get_available_plugins()
|
72
|
+
|
73
|
+
if not plugins:
|
74
|
+
console.print("[yellow]No plugins available.[/yellow]")
|
75
|
+
return
|
76
|
+
|
77
|
+
# Create a rich table
|
78
|
+
table = Table(title="Available Plugins", box=box.ROUNDED)
|
79
|
+
table.add_column("Name", style="cyan")
|
80
|
+
table.add_column("Version", style="green")
|
81
|
+
table.add_column("Description")
|
82
|
+
|
83
|
+
for plugin in plugins:
|
84
|
+
table.add_row(
|
85
|
+
plugin.name,
|
86
|
+
plugin.version,
|
87
|
+
plugin.description
|
88
|
+
)
|
89
|
+
|
90
|
+
console.print(table)
|
91
|
+
console.print("\nUse [bold cyan]edsl plugins info <name>[/bold cyan] for more details about a specific plugin.")
|
92
|
+
console.print("Use [bold cyan]edsl plugins install <name>[/bold cyan] to install a plugin.")
|
93
|
+
|
94
|
+
except Exception as e:
|
95
|
+
console.print(f"[red]Error fetching available plugins: {str(e)}[/red]")
|
96
|
+
|
97
|
+
@app.command("search")
|
98
|
+
def search_for_plugins(
|
99
|
+
query: str = typer.Argument(..., help="Search query string"),
|
100
|
+
tags: Optional[List[str]] = typer.Option(None, help="Filter by tags")
|
101
|
+
):
|
102
|
+
"""Search for plugins matching a query string or tags."""
|
103
|
+
try:
|
104
|
+
results = search_plugins(query, tags)
|
105
|
+
|
106
|
+
if not results:
|
107
|
+
console.print(f"[yellow]No plugins found matching '{query}'.[/yellow]")
|
108
|
+
return
|
109
|
+
|
110
|
+
# Create a rich table
|
111
|
+
title = f"Search Results for '{query}'"
|
112
|
+
if tags:
|
113
|
+
title += f" (Tags: {', '.join(tags)})"
|
114
|
+
|
115
|
+
table = Table(title=title, box=box.ROUNDED)
|
116
|
+
table.add_column("Name", style="cyan")
|
117
|
+
table.add_column("Version", style="green")
|
118
|
+
table.add_column("Tags")
|
119
|
+
table.add_column("Description")
|
120
|
+
|
121
|
+
for plugin in results:
|
122
|
+
# Format tags
|
123
|
+
tag_str = ", ".join(plugin.tags[:3])
|
124
|
+
if len(plugin.tags) > 3:
|
125
|
+
tag_str += "..."
|
126
|
+
|
127
|
+
table.add_row(
|
128
|
+
plugin.name,
|
129
|
+
plugin.version,
|
130
|
+
tag_str,
|
131
|
+
plugin.description
|
132
|
+
)
|
133
|
+
|
134
|
+
console.print(table)
|
135
|
+
console.print("\nUse [bold cyan]edsl plugins info <name>[/bold cyan] for more details about a specific plugin.")
|
136
|
+
|
137
|
+
except Exception as e:
|
138
|
+
console.print(f"[red]Error searching for plugins: {str(e)}[/red]")
|
139
|
+
|
140
|
+
@app.command("info")
|
141
|
+
def show_plugin_info(name: str = typer.Argument(..., help="Plugin name")):
|
142
|
+
"""Get detailed information about a specific plugin."""
|
143
|
+
plugin_manager = get_plugin_manager()
|
144
|
+
local_plugins = plugin_manager.list_plugins()
|
145
|
+
|
146
|
+
if name in local_plugins:
|
147
|
+
info = local_plugins[name]
|
148
|
+
|
149
|
+
panel_content = []
|
150
|
+
panel_content.append(f"[bold cyan]Description:[/bold cyan] {info.get('description', 'No description available')}")
|
151
|
+
panel_content.append(f"[bold cyan]Methods:[/bold cyan] {', '.join(info.get('methods', []))}")
|
152
|
+
panel_content.append(f"[bold cyan]Installed from:[/bold cyan] {info.get('installed_from', 'Unknown')}")
|
153
|
+
|
154
|
+
panel = Panel(
|
155
|
+
"\n".join(panel_content),
|
156
|
+
title=f"[bold]Plugin: {name} (Installed)[/bold]",
|
157
|
+
expand=False
|
158
|
+
)
|
159
|
+
console.print(panel)
|
160
|
+
console.print(f"\nUse [bold cyan]edsl plugins uninstall {name}[/bold cyan] to uninstall this plugin.")
|
161
|
+
return
|
162
|
+
|
163
|
+
# If not installed, check if it's available
|
164
|
+
try:
|
165
|
+
info = get_plugin_details(name)
|
166
|
+
|
167
|
+
if not info:
|
168
|
+
console.print(f"[yellow]Plugin '{name}' not found.[/yellow]")
|
169
|
+
return
|
170
|
+
|
171
|
+
# Create a rich panel with detailed information
|
172
|
+
panel_content = []
|
173
|
+
panel_content.append(f"[bold cyan]Description:[/bold cyan] {info['description']}")
|
174
|
+
panel_content.append(f"[bold cyan]Version:[/bold cyan] {info['version']}")
|
175
|
+
panel_content.append(f"[bold cyan]Author:[/bold cyan] {info['author']}")
|
176
|
+
panel_content.append(f"[bold cyan]Tags:[/bold cyan] {', '.join(info['tags'])}")
|
177
|
+
panel_content.append(f"[bold cyan]GitHub URL:[/bold cyan] {info['github_url']}")
|
178
|
+
panel_content.append(f"[bold cyan]Created:[/bold cyan] {info['created_at']}")
|
179
|
+
panel_content.append(f"[bold cyan]Last Updated:[/bold cyan] {info.get('last_update', 'Unknown')}")
|
180
|
+
panel_content.append(f"[bold cyan]License:[/bold cyan] {info.get('license', 'Unknown')}")
|
181
|
+
panel_content.append(f"[bold cyan]Dependencies:[/bold cyan] {', '.join(info.get('dependencies', ['None']))}")
|
182
|
+
panel_content.append(f"[bold cyan]Compatible EDSL versions:[/bold cyan] {', '.join(info.get('compatible_edsl_versions', ['Unknown']))}")
|
183
|
+
|
184
|
+
panel_content.append("\n[bold cyan]Documentation:[/bold cyan]")
|
185
|
+
panel_content.append(f" Homepage: {info.get('homepage', 'N/A')}")
|
186
|
+
panel_content.append(f" Docs: {info.get('documentation', 'N/A')}")
|
187
|
+
|
188
|
+
if 'examples' in info and info['examples']:
|
189
|
+
panel_content.append("\n[bold cyan]Examples:[/bold cyan]")
|
190
|
+
for example in info['examples']:
|
191
|
+
panel_content.append(f" • {example}")
|
192
|
+
|
193
|
+
panel = Panel(
|
194
|
+
"\n".join(panel_content),
|
195
|
+
title=f"[bold]Plugin: {info['name']}[/bold]",
|
196
|
+
expand=False
|
197
|
+
)
|
198
|
+
console.print(panel)
|
199
|
+
console.print(f"\nUse [bold cyan]edsl plugins install {info['name']}[/bold cyan] to install this plugin.")
|
200
|
+
|
201
|
+
except Exception as e:
|
202
|
+
console.print(f"[red]Error retrieving plugin information: {str(e)}[/red]")
|
203
|
+
|
204
|
+
@app.command("install")
|
205
|
+
def install_plugin(
|
206
|
+
name: str = typer.Argument(..., help="Name of the plugin to install"),
|
207
|
+
branch: Optional[str] = typer.Option(None, help="Branch to install from"),
|
208
|
+
url: Optional[str] = typer.Option(None, help="Directly specify GitHub URL instead of using the registry")
|
209
|
+
):
|
210
|
+
"""Install a plugin by name or URL."""
|
211
|
+
try:
|
212
|
+
github_url = url
|
213
|
+
|
214
|
+
# If URL not provided, look up in registry by name
|
215
|
+
if not github_url:
|
216
|
+
github_url = get_github_url_by_name(name)
|
217
|
+
if not github_url:
|
218
|
+
console.print(f"[yellow]Plugin '{name}' not found in registry.[/yellow]")
|
219
|
+
console.print("Use [bold cyan]edsl plugins available[/bold cyan] to see available plugins.")
|
220
|
+
console.print("Or provide the GitHub URL with '--url' if you know it.")
|
221
|
+
raise typer.Exit(code=1)
|
222
|
+
|
223
|
+
with console.status(f"Installing plugin '{name}' from {github_url}...", spinner="dots"):
|
224
|
+
# Install the plugin
|
225
|
+
installed_plugins = PluginHost.install_from_github(github_url, branch)
|
226
|
+
|
227
|
+
console.print(f"[green]Successfully installed plugin(s): {', '.join(installed_plugins)}[/green]")
|
228
|
+
console.print("\nUse [bold cyan]edsl plugins list[/bold cyan] to see all installed plugins.")
|
229
|
+
|
230
|
+
except PluginException as e:
|
231
|
+
console.print(f"[red]Error installing plugin: {str(e)}[/red]")
|
232
|
+
raise typer.Exit(code=1)
|
233
|
+
except Exception as e:
|
234
|
+
console.print(f"[red]Unexpected error during installation: {str(e)}[/red]")
|
235
|
+
raise typer.Exit(code=1)
|
236
|
+
|
237
|
+
@app.command("uninstall")
|
238
|
+
def uninstall_plugin(
|
239
|
+
name: str = typer.Argument(..., help="Plugin name")
|
240
|
+
):
|
241
|
+
"""Uninstall a plugin by name."""
|
242
|
+
try:
|
243
|
+
with console.status(f"Uninstalling plugin '{name}'...", spinner="dots"):
|
244
|
+
# Uninstall the plugin
|
245
|
+
PluginHost.uninstall_plugin(name)
|
246
|
+
|
247
|
+
console.print(f"[green]Successfully uninstalled plugin '{name}'.[/green]")
|
248
|
+
|
249
|
+
except PluginException as e:
|
250
|
+
console.print(f"[red]Error uninstalling plugin: {str(e)}[/red]")
|
251
|
+
raise typer.Exit(code=1)
|
252
|
+
except Exception as e:
|
253
|
+
console.print(f"[red]Unexpected error during uninstallation: {str(e)}[/red]")
|
254
|
+
raise typer.Exit(code=1)
|
255
|
+
|
256
|
+
def _get_plugin_name_from_url(url: str) -> str:
|
257
|
+
"""
|
258
|
+
Extract a plugin name from a GitHub URL.
|
259
|
+
|
260
|
+
Args:
|
261
|
+
url: GitHub URL
|
262
|
+
|
263
|
+
Returns:
|
264
|
+
Extracted plugin name or a placeholder
|
265
|
+
"""
|
266
|
+
# Try to extract the repository name from the URL
|
267
|
+
match = re.search(r"github\.com/[^/]+/([^/]+)", url)
|
268
|
+
if match:
|
269
|
+
repo_name = match.group(1)
|
270
|
+
# Convert repo name to plugin name
|
271
|
+
if repo_name.startswith("plugin-"):
|
272
|
+
return repo_name[7:] # Remove "plugin-" prefix
|
273
|
+
elif repo_name.startswith("edsl-plugin-"):
|
274
|
+
return repo_name[12:] # Remove "edsl-plugin-" prefix
|
275
|
+
else:
|
276
|
+
return repo_name
|
277
|
+
|
278
|
+
# If we can't extract a name, return a placeholder
|
279
|
+
return "plugin"
|
280
|
+
|
281
|
+
def run():
|
282
|
+
"""Run the plugin CLI."""
|
283
|
+
app()
|
@@ -0,0 +1,31 @@
|
|
1
|
+
"""Exceptions for the plugins module."""
|
2
|
+
|
3
|
+
from ..base.base_exception import BaseException
|
4
|
+
|
5
|
+
class PluginException(BaseException):
|
6
|
+
"""Base exception for all plugin-related errors."""
|
7
|
+
relevant_doc = "https://docs.expectedparrot.com/plugins.html"
|
8
|
+
|
9
|
+
class PluginNotFoundError(PluginException):
|
10
|
+
"""Raised when a requested plugin is not found."""
|
11
|
+
pass
|
12
|
+
|
13
|
+
class PluginInstallationError(PluginException):
|
14
|
+
"""Raised when a plugin installation fails."""
|
15
|
+
pass
|
16
|
+
|
17
|
+
class GitHubRepoError(PluginInstallationError):
|
18
|
+
"""Raised when there's an error with a GitHub repository."""
|
19
|
+
pass
|
20
|
+
|
21
|
+
class InvalidPluginError(PluginException):
|
22
|
+
"""Raised when a plugin is invalid or does not implement required hooks."""
|
23
|
+
pass
|
24
|
+
|
25
|
+
class PluginDependencyError(PluginInstallationError):
|
26
|
+
"""Raised when plugin dependencies cannot be satisfied."""
|
27
|
+
pass
|
28
|
+
|
29
|
+
class PluginMethodError(PluginException):
|
30
|
+
"""Raised when there is an error with a plugin method."""
|
31
|
+
pass
|