talkops-opscloud 0.3.0b1__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.
- opscloud/__init__.py +5 -0
- opscloud/__main__.py +6 -0
- opscloud/_constants.py +53 -0
- opscloud/_debug.py +145 -0
- opscloud/_debug_buffer.py +250 -0
- opscloud/_textual_patches.py +367 -0
- opscloud/_version.py +15 -0
- opscloud/agent/__init__.py +27 -0
- opscloud/agent/config.py +168 -0
- opscloud/agent/factory.py +1103 -0
- opscloud/approval_mode.py +44 -0
- opscloud/backend/__init__.py +49 -0
- opscloud/backend/composite.py +89 -0
- opscloud/backend/local.py +183 -0
- opscloud/backend/registry.py +79 -0
- opscloud/backend/sandbox_config.py +107 -0
- opscloud/backend/sandbox_factory.py +432 -0
- opscloud/built_in_skills/remember/SKILL.md +124 -0
- opscloud/built_in_skills/skill-creator/SKILL.md +348 -0
- opscloud/built_in_skills/skill-creator/scripts/init_skill.py +371 -0
- opscloud/built_in_skills/skill-creator/scripts/quick_validate.py +165 -0
- opscloud/cli/__init__.py +1 -0
- opscloud/cli/commands/__init__.py +15 -0
- opscloud/cli/commands/auth.py +45 -0
- opscloud/cli/commands/doctor.py +100 -0
- opscloud/cli/commands/mcp.py +137 -0
- opscloud/cli/commands/threads.py +51 -0
- opscloud/cli/interactive.py +154 -0
- opscloud/cli/main.py +903 -0
- opscloud/cli/non_interactive.py +425 -0
- opscloud/cli/server_manager.py +58 -0
- opscloud/commands/__init__.py +19 -0
- opscloud/commands/_base.py +110 -0
- opscloud/commands/_guards.py +52 -0
- opscloud/commands/_router.py +146 -0
- opscloud/commands/_types.py +36 -0
- opscloud/commands/core/__init__.py +47 -0
- opscloud/commands/core/auth.py +94 -0
- opscloud/commands/core/bug.py +46 -0
- opscloud/commands/core/clear.py +106 -0
- opscloud/commands/core/cloud.py +185 -0
- opscloud/commands/core/compact.py +128 -0
- opscloud/commands/core/config_cmd.py +120 -0
- opscloud/commands/core/context.py +220 -0
- opscloud/commands/core/cost.py +44 -0
- opscloud/commands/core/doctor.py +535 -0
- opscloud/commands/core/effort.py +108 -0
- opscloud/commands/core/exit_cmd.py +41 -0
- opscloud/commands/core/fast.py +148 -0
- opscloud/commands/core/help_cmd.py +94 -0
- opscloud/commands/core/mcp.py +134 -0
- opscloud/commands/core/model.py +109 -0
- opscloud/commands/core/permissions.py +196 -0
- opscloud/commands/core/plugins.py +92 -0
- opscloud/commands/core/pool.py +108 -0
- opscloud/commands/core/resume.py +97 -0
- opscloud/commands/core/skills.py +178 -0
- opscloud/commands/power/__init__.py +51 -0
- opscloud/commands/power/agents.py +98 -0
- opscloud/commands/power/btw.py +85 -0
- opscloud/commands/power/copy.py +97 -0
- opscloud/commands/power/goal.py +548 -0
- opscloud/commands/power/loop.py +295 -0
- opscloud/commands/power/memory.py +287 -0
- opscloud/commands/power/review.py +152 -0
- opscloud/commands/power/rubric.py +287 -0
- opscloud/commands/power/runtime.py +321 -0
- opscloud/commands/power/skill_creator.py +70 -0
- opscloud/commands/power/skill_invoke.py +165 -0
- opscloud/commands/power/tasks.py +209 -0
- opscloud/commands/power/trace.py +117 -0
- opscloud/commands/power/ui_toggles.py +135 -0
- opscloud/commands/power/version.py +52 -0
- opscloud/commands/registry.py +176 -0
- opscloud/config/__init__.py +114 -0
- opscloud/config/adapters/__init__.py +5 -0
- opscloud/config/adapters/postgres.py +776 -0
- opscloud/config/adapters/sqlite.py +769 -0
- opscloud/config/aws.py +248 -0
- opscloud/config/cloud_profiles.py +437 -0
- opscloud/config/env_vars.py +81 -0
- opscloud/config/langsmith.py +319 -0
- opscloud/config/manifest.py +817 -0
- opscloud/config/metadata.py +205 -0
- opscloud/config/paths.py +604 -0
- opscloud/config/plugins.py +130 -0
- opscloud/config/settings.py +834 -0
- opscloud/config/store.py +453 -0
- opscloud/config/store_factory.py +97 -0
- opscloud/config/toml_config.py +562 -0
- opscloud/editor.py +173 -0
- opscloud/exceptions.py +65 -0
- opscloud/file_ops.py +217 -0
- opscloud/hooks/__init__.py +139 -0
- opscloud/hooks/env.py +44 -0
- opscloud/hooks/interrupt.py +121 -0
- opscloud/hooks/models/__init__.py +109 -0
- opscloud/hooks/models/adapters.py +19 -0
- opscloud/hooks/models/config.py +79 -0
- opscloud/hooks/models/domain.py +434 -0
- opscloud/hooks/models/transport.py +37 -0
- opscloud/hooks/tools.py +231 -0
- opscloud/input.py +48 -0
- opscloud/integrations/__init__.py +80 -0
- opscloud/integrations/base.py +153 -0
- opscloud/integrations/content_filter.py +119 -0
- opscloud/integrations/event_bus.py +480 -0
- opscloud/integrations/hooks.py +238 -0
- opscloud/integrations/identity.py +181 -0
- opscloud/integrations/notifications.py +134 -0
- opscloud/integrations/stream_bridge.py +397 -0
- opscloud/integrations/thread_store.py +81 -0
- opscloud/json_types.py +20 -0
- opscloud/mcp/__init__.py +57 -0
- opscloud/mcp/config.py +203 -0
- opscloud/mcp/discovery.py +229 -0
- opscloud/mcp/mcp_info.py +109 -0
- opscloud/mcp/middleware.py +102 -0
- opscloud/mcp/preload.py +505 -0
- opscloud/mcp/raw_config.py +149 -0
- opscloud/mcp/semantic_profiler.py +305 -0
- opscloud/mcp/session_manager.py +727 -0
- opscloud/mcp/trust.py +105 -0
- opscloud/memory/__init__.py +27 -0
- opscloud/memory/branch.py +61 -0
- opscloud/memory/guard.py +19 -0
- opscloud/memory/onboarding.py +120 -0
- opscloud/memory/registry.py +134 -0
- opscloud/memory/store.py +223 -0
- opscloud/middleware/__init__.py +106 -0
- opscloud/middleware/_repository_bounds.py +459 -0
- opscloud/middleware/ask_user.py +152 -0
- opscloud/middleware/auto_mode.py +60 -0
- opscloud/middleware/auto_mode_hitl.py +2434 -0
- opscloud/middleware/compaction.py +566 -0
- opscloud/middleware/configurable_model.py +233 -0
- opscloud/middleware/cost_tracking.py +1021 -0
- opscloud/middleware/glm_stall_recovery.py +109 -0
- opscloud/middleware/goal_criteria.py +1234 -0
- opscloud/middleware/goal_state_notice.py +712 -0
- opscloud/middleware/goal_tools.py +407 -0
- opscloud/middleware/headless_mcp_guard.py +88 -0
- opscloud/middleware/jev_model_router.py +1018 -0
- opscloud/middleware/local_context.py +460 -0
- opscloud/middleware/mcp_context.py +151 -0
- opscloud/middleware/mcp_middleware.py +146 -0
- opscloud/middleware/memory_guard.py +393 -0
- opscloud/middleware/model_retry.py +205 -0
- opscloud/middleware/registry.py +137 -0
- opscloud/middleware/reliable_rubric.py +709 -0
- opscloud/middleware/resume_state.py +91 -0
- opscloud/middleware/server_hooks.py +1189 -0
- opscloud/middleware/shell_allow_list.py +90 -0
- opscloud/middleware/skills.py +382 -0
- opscloud/middleware/subagent_artifacts.py +84 -0
- opscloud/middleware/subagent_telemetry.py +261 -0
- opscloud/middleware/subagents.py +243 -0
- opscloud/middleware/tool_filter.py +384 -0
- opscloud/middleware/unified_system_message.py +62 -0
- opscloud/model/__init__.py +37 -0
- opscloud/model/config.py +2992 -0
- opscloud/model/factory.py +477 -0
- opscloud/model/pool.py +459 -0
- opscloud/model/reasoning.py +282 -0
- opscloud/offload.py +173 -0
- opscloud/output.py +64 -0
- opscloud/plugins/__init__.py +189 -0
- opscloud/plugins/_json.py +22 -0
- opscloud/plugins/adapters/__init__.py +13 -0
- opscloud/plugins/adapters/agents.py +112 -0
- opscloud/plugins/adapters/commands.py +190 -0
- opscloud/plugins/adapters/mcp.py +363 -0
- opscloud/plugins/adapters/skills.py +132 -0
- opscloud/plugins/commands_cli.py +541 -0
- opscloud/plugins/discovery.py +512 -0
- opscloud/plugins/manifest.py +335 -0
- opscloud/plugins/marketplace.py +893 -0
- opscloud/plugins/models.py +334 -0
- opscloud/plugins/project_plugins.py +317 -0
- opscloud/plugins/protocol.py +31 -0
- opscloud/plugins/store.py +905 -0
- opscloud/plugins/substitution.py +122 -0
- opscloud/project_utils.py +145 -0
- opscloud/prompts/__init__.py +37 -0
- opscloud/prompts/system.py +483 -0
- opscloud/prompts/templates/system_prompt.md +166 -0
- opscloud/prompts/types.py +54 -0
- opscloud/rubrics/__init__.py +42 -0
- opscloud/rubrics/evaluator.py +404 -0
- opscloud/rubrics/evidence_extractor.py +229 -0
- opscloud/rubrics/generator.py +239 -0
- opscloud/rubrics/jev_compiler.py +175 -0
- opscloud/rubrics/jev_grader.py +297 -0
- opscloud/rubrics/middleware.py +8 -0
- opscloud/schema/__init__.py +107 -0
- opscloud/schema/interrupts.py +981 -0
- opscloud/security/__init__.py +91 -0
- opscloud/security/approval_mode.py +528 -0
- opscloud/security/approval_mode_source.py +230 -0
- opscloud/security/cli_ast_evaluator.py +552 -0
- opscloud/security/jev_classifier.py +651 -0
- opscloud/security/shell_safety.py +340 -0
- opscloud/security/unicode_security.py +439 -0
- opscloud/security/url_validation.py +160 -0
- opscloud/server/__init__.py +13 -0
- opscloud/server/_server_config.py +158 -0
- opscloud/server/server.py +242 -0
- opscloud/server/server_graph.py +93 -0
- opscloud/skills/__init__.py +91 -0
- opscloud/skills/commands.py +1099 -0
- opscloud/skills/invocation.py +200 -0
- opscloud/skills/load.py +388 -0
- opscloud/skills/loader.py +23 -0
- opscloud/skills/merge.py +65 -0
- opscloud/skills/registry.py +263 -0
- opscloud/skills/sources.py +174 -0
- opscloud/skills/trust.py +417 -0
- opscloud/state/__init__.py +142 -0
- opscloud/state/base.py +154 -0
- opscloud/state/cloud_context.py +262 -0
- opscloud/state/goal_channels.py +124 -0
- opscloud/state/goal_state_limits.py +175 -0
- opscloud/state/resume_state.py +58 -0
- opscloud/state/service_context.py +281 -0
- opscloud/state/session.py +957 -0
- opscloud/state/state_migration.py +86 -0
- opscloud/subagents/__init__.py +30 -0
- opscloud/subagents/loader.py +331 -0
- opscloud/subagents/subagents_parser.py +117 -0
- opscloud/subagents/types.py +25 -0
- opscloud/tools/__init__.py +36 -0
- opscloud/tools/catalog.py +37 -0
- opscloud/tools/display.py +24 -0
- opscloud/tools/fetch_url.py +176 -0
- opscloud/tools/goal_tools.py +423 -0
- opscloud/tools/registry.py +105 -0
- opscloud/tools/thread.py +32 -0
- opscloud/tools/web_search.py +215 -0
- opscloud/ui/__init__.py +119 -0
- opscloud/ui/app.py +5400 -0
- opscloud/ui/app.tcss +479 -0
- opscloud/ui/clipboard.py +147 -0
- opscloud/ui/command_registry.py +482 -0
- opscloud/ui/message_store.py +152 -0
- opscloud/ui/permission_store.py +360 -0
- opscloud/ui/preferences.py +113 -0
- opscloud/ui/prompt_manager.py +96 -0
- opscloud/ui/remote_client.py +505 -0
- opscloud/ui/textual_adapter.py +1269 -0
- opscloud/ui/theme.py +728 -0
- opscloud/ui/ui_help.py +415 -0
- opscloud/ui/widgets/__init__.py +383 -0
- opscloud/ui/widgets/_ask_user_types.py +81 -0
- opscloud/ui/widgets/_copy_spans.py +52 -0
- opscloud/ui/widgets/_inline_prompt.py +290 -0
- opscloud/ui/widgets/_js_eval_display.py +139 -0
- opscloud/ui/widgets/_links.py +175 -0
- opscloud/ui/widgets/_tool_stream.py +308 -0
- opscloud/ui/widgets/agent_selector.py +297 -0
- opscloud/ui/widgets/approval.py +741 -0
- opscloud/ui/widgets/ask_user.py +539 -0
- opscloud/ui/widgets/auth.py +695 -0
- opscloud/ui/widgets/auth_manager.py +214 -0
- opscloud/ui/widgets/auto_mode_notice.py +146 -0
- opscloud/ui/widgets/autocomplete.py +709 -0
- opscloud/ui/widgets/chat_input.py +805 -0
- opscloud/ui/widgets/cloud_selector.py +528 -0
- opscloud/ui/widgets/config_manager.py +652 -0
- opscloud/ui/widgets/debug_console.py +1269 -0
- opscloud/ui/widgets/devops_renderers.py +184 -0
- opscloud/ui/widgets/diff.py +237 -0
- opscloud/ui/widgets/effort_selector.py +166 -0
- opscloud/ui/widgets/goal_review.py +612 -0
- opscloud/ui/widgets/goal_status.py +57 -0
- opscloud/ui/widgets/infra_panel.py +141 -0
- opscloud/ui/widgets/install_confirm.py +114 -0
- opscloud/ui/widgets/loading.py +170 -0
- opscloud/ui/widgets/mcp_viewer.py +1204 -0
- opscloud/ui/widgets/messages.py +4461 -0
- opscloud/ui/widgets/model_selector.py +863 -0
- opscloud/ui/widgets/notification_center.py +108 -0
- opscloud/ui/widgets/notification_settings.py +119 -0
- opscloud/ui/widgets/operation_card.py +106 -0
- opscloud/ui/widgets/permissions_manager.py +667 -0
- opscloud/ui/widgets/plugin_manager.py +1212 -0
- opscloud/ui/widgets/pool_selector.py +376 -0
- opscloud/ui/widgets/preamble.py +53 -0
- opscloud/ui/widgets/skills_viewer.py +432 -0
- opscloud/ui/widgets/status.py +1037 -0
- opscloud/ui/widgets/subagent_panel.py +927 -0
- opscloud/ui/widgets/theme_selector.py +232 -0
- opscloud/ui/widgets/thread_selector.py +358 -0
- opscloud/ui/widgets/toast.py +31 -0
- opscloud/ui/widgets/tool_display.py +325 -0
- opscloud/ui/widgets/tool_renderers.py +234 -0
- opscloud/ui/widgets/tool_widgets.py +278 -0
- opscloud/ui/widgets/welcome.py +421 -0
- opscloud/ui/widgets/welcome_popup.py +102 -0
- opscloud/utils/__init__.py +24 -0
- opscloud/utils/cost_estimation.py +169 -0
- opscloud/utils/git.py +390 -0
- opscloud/utils/logger.py +571 -0
- opscloud/utils/session_stats.py +115 -0
- opscloud/utils/startup_error.py +37 -0
- talkops_opscloud-0.3.0b1.dist-info/METADATA +449 -0
- talkops_opscloud-0.3.0b1.dist-info/RECORD +309 -0
- talkops_opscloud-0.3.0b1.dist-info/WHEEL +4 -0
- talkops_opscloud-0.3.0b1.dist-info/entry_points.txt +2 -0
- talkops_opscloud-0.3.0b1.dist-info/licenses/LICENSE +175 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
"""CLI helpers for plugin management in OpsCloud."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
|
|
12
|
+
from opscloud.plugins.discovery import (
|
|
13
|
+
add_marketplace_source,
|
|
14
|
+
install_plugin,
|
|
15
|
+
list_available_plugins,
|
|
16
|
+
refresh_all_marketplaces,
|
|
17
|
+
refresh_marketplace,
|
|
18
|
+
remove_marketplace,
|
|
19
|
+
set_installed_plugin_enabled,
|
|
20
|
+
uninstall_plugin,
|
|
21
|
+
)
|
|
22
|
+
from opscloud.plugins.marketplace import (
|
|
23
|
+
MarketplaceError,
|
|
24
|
+
redact_marketplace_source,
|
|
25
|
+
redact_urls_in_text,
|
|
26
|
+
)
|
|
27
|
+
from opscloud.plugins.models import InstallScope
|
|
28
|
+
from opscloud.plugins.store import load_marketplace_records
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _resolve_project_root(scope: str | None) -> Path | None:
|
|
32
|
+
"""Resolve the project root for project/local scoped commands.
|
|
33
|
+
|
|
34
|
+
Returns ``None`` when ``scope`` is ``"user"`` or the project root
|
|
35
|
+
cannot be determined.
|
|
36
|
+
"""
|
|
37
|
+
if scope in {None, "user"}:
|
|
38
|
+
return None
|
|
39
|
+
from opscloud.config.paths import find_project_root
|
|
40
|
+
|
|
41
|
+
root = find_project_root()
|
|
42
|
+
if root is None and scope in {"project", "local"}:
|
|
43
|
+
# Fall back to cwd when no marker is found
|
|
44
|
+
root = Path.cwd()
|
|
45
|
+
return root
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def setup_plugin_parser(
|
|
49
|
+
subparsers: Any, # noqa: ANN401
|
|
50
|
+
*,
|
|
51
|
+
make_help_action: Callable[[Callable[[], None]], type[argparse.Action]] | None = None,
|
|
52
|
+
add_output_args: Callable[[argparse.ArgumentParser], None] | None = None,
|
|
53
|
+
) -> argparse.ArgumentParser:
|
|
54
|
+
"""Set up the `plugin` CLI parser.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
subparsers: Parent argparse subparsers object.
|
|
58
|
+
make_help_action: Optional factory for parser-specific help actions.
|
|
59
|
+
add_output_args: Optional callback that adds output-format flags.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Plugin command parser.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def _default_help() -> None:
|
|
66
|
+
print("Usage: opscloud plugin {list,install,uninstall,enable,disable,marketplace}")
|
|
67
|
+
|
|
68
|
+
parent = argparse.ArgumentParser(add_help=False)
|
|
69
|
+
if make_help_action is not None:
|
|
70
|
+
parent.add_argument("-h", "--help", action=make_help_action(_default_help))
|
|
71
|
+
else:
|
|
72
|
+
parent.add_argument("-h", "--help", action="help")
|
|
73
|
+
|
|
74
|
+
parser = subparsers.add_parser(
|
|
75
|
+
"plugin",
|
|
76
|
+
aliases=["plugins"],
|
|
77
|
+
help="Manage plugins",
|
|
78
|
+
add_help=False,
|
|
79
|
+
parents=[parent],
|
|
80
|
+
)
|
|
81
|
+
if add_output_args is not None:
|
|
82
|
+
add_output_args(parser)
|
|
83
|
+
plugin_sub = parser.add_subparsers(dest="plugin_command")
|
|
84
|
+
|
|
85
|
+
list_parser = plugin_sub.add_parser("list", aliases=["ls"], help="List plugins")
|
|
86
|
+
if add_output_args is not None:
|
|
87
|
+
add_output_args(list_parser)
|
|
88
|
+
|
|
89
|
+
search_parser = plugin_sub.add_parser("search", help="Search available plugins")
|
|
90
|
+
search_parser.add_argument(
|
|
91
|
+
"query", help="Search query (matches name, description, category, or keywords)"
|
|
92
|
+
)
|
|
93
|
+
if add_output_args is not None:
|
|
94
|
+
add_output_args(search_parser)
|
|
95
|
+
|
|
96
|
+
install_parser = plugin_sub.add_parser("install", help="Install a plugin")
|
|
97
|
+
install_parser.add_argument("plugin_id")
|
|
98
|
+
install_parser.add_argument(
|
|
99
|
+
"--scope",
|
|
100
|
+
choices=["user", "project", "local"],
|
|
101
|
+
default="user",
|
|
102
|
+
help="Installation scope: user (global), project (shared), or local (gitignored)",
|
|
103
|
+
)
|
|
104
|
+
uninstall_parser = plugin_sub.add_parser("uninstall", help="Uninstall a plugin")
|
|
105
|
+
uninstall_parser.add_argument("plugin_id")
|
|
106
|
+
uninstall_parser.add_argument(
|
|
107
|
+
"--scope",
|
|
108
|
+
choices=["user", "project", "local"],
|
|
109
|
+
default=None,
|
|
110
|
+
help="Scope to uninstall from (omit to remove all scopes)",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
enable_parser = plugin_sub.add_parser("enable", help="Enable a plugin")
|
|
114
|
+
enable_parser.add_argument("plugin_id")
|
|
115
|
+
enable_parser.add_argument(
|
|
116
|
+
"--scope",
|
|
117
|
+
choices=["user", "project", "local"],
|
|
118
|
+
default="user",
|
|
119
|
+
help="Scope to enable in",
|
|
120
|
+
)
|
|
121
|
+
disable_parser = plugin_sub.add_parser("disable", help="Disable a plugin")
|
|
122
|
+
disable_parser.add_argument("plugin_id")
|
|
123
|
+
disable_parser.add_argument(
|
|
124
|
+
"--scope",
|
|
125
|
+
choices=["user", "project", "local"],
|
|
126
|
+
default="user",
|
|
127
|
+
help="Scope to disable in",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
refresh_parser = plugin_sub.add_parser(
|
|
131
|
+
"refresh", aliases=["update"], help="Refresh marketplace(s) to fetch latest plugins"
|
|
132
|
+
)
|
|
133
|
+
refresh_parser.add_argument(
|
|
134
|
+
"name",
|
|
135
|
+
nargs="?",
|
|
136
|
+
default=None,
|
|
137
|
+
help="Optional marketplace name to refresh (omit to refresh all)",
|
|
138
|
+
)
|
|
139
|
+
if add_output_args is not None:
|
|
140
|
+
add_output_args(refresh_parser)
|
|
141
|
+
|
|
142
|
+
marketplace_parser = plugin_sub.add_parser(
|
|
143
|
+
"marketplace", help="Manage plugin marketplaces"
|
|
144
|
+
)
|
|
145
|
+
marketplace_sub = marketplace_parser.add_subparsers(dest="marketplace_command")
|
|
146
|
+
marketplace_list = marketplace_sub.add_parser(
|
|
147
|
+
"list", aliases=["ls"], help="List marketplaces"
|
|
148
|
+
)
|
|
149
|
+
if add_output_args is not None:
|
|
150
|
+
add_output_args(marketplace_list)
|
|
151
|
+
marketplace_add = marketplace_sub.add_parser("add", help="Add a marketplace")
|
|
152
|
+
marketplace_add.add_argument("source")
|
|
153
|
+
marketplace_refresh = marketplace_sub.add_parser(
|
|
154
|
+
"refresh", aliases=["update"], help="Refresh marketplace(s) to fetch latest plugins"
|
|
155
|
+
)
|
|
156
|
+
marketplace_refresh.add_argument(
|
|
157
|
+
"name",
|
|
158
|
+
nargs="?",
|
|
159
|
+
default=None,
|
|
160
|
+
help="Optional marketplace name to refresh (omit to refresh all)",
|
|
161
|
+
)
|
|
162
|
+
if add_output_args is not None:
|
|
163
|
+
add_output_args(marketplace_refresh)
|
|
164
|
+
marketplace_remove = marketplace_sub.add_parser(
|
|
165
|
+
"remove", help="Remove a marketplace and uninstall its plugins"
|
|
166
|
+
)
|
|
167
|
+
marketplace_remove.add_argument("name")
|
|
168
|
+
return parser
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _plugin_list_rows(project_root: Path | None = None) -> list[dict[str, object]]:
|
|
172
|
+
return [
|
|
173
|
+
{"id": plugin_id, "description": description, "enabled": enabled}
|
|
174
|
+
for plugin_id, description, enabled in list_available_plugins(project_root=project_root)
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def execute_plugin_command(args: argparse.Namespace) -> str | None:
|
|
179
|
+
"""Execute a plugin management command.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
args: Parsed argparse namespace.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Text output for slash-command callers, or `None` when output was written.
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
SystemExit: With status 1 when a mutating command fails.
|
|
189
|
+
"""
|
|
190
|
+
output_format = getattr(args, "output_format", "text")
|
|
191
|
+
command = getattr(args, "plugin_command", None)
|
|
192
|
+
if command is None:
|
|
193
|
+
text = "Usage: opscloud plugin {list,search,install,uninstall,enable,disable,refresh,marketplace}"
|
|
194
|
+
print(text)
|
|
195
|
+
return text
|
|
196
|
+
if command in {"list", "ls"}:
|
|
197
|
+
project_root = _resolve_project_root("project")
|
|
198
|
+
rows = _plugin_list_rows(project_root=project_root)
|
|
199
|
+
if output_format == "json":
|
|
200
|
+
import json
|
|
201
|
+
text = json.dumps({"plugins": rows}, indent=2)
|
|
202
|
+
print(text)
|
|
203
|
+
return None
|
|
204
|
+
if not rows:
|
|
205
|
+
text = "No plugin marketplaces configured."
|
|
206
|
+
print(text)
|
|
207
|
+
return text
|
|
208
|
+
|
|
209
|
+
from opscloud.plugins.store import load_all_enabled_plugin_ids, load_installed_plugins
|
|
210
|
+
|
|
211
|
+
installed_ids = load_installed_plugins()
|
|
212
|
+
enabled_ids = load_all_enabled_plugin_ids(project_root=project_root)
|
|
213
|
+
|
|
214
|
+
lines: list[str] = []
|
|
215
|
+
lines.append("Installed Plugins:")
|
|
216
|
+
if not installed_ids:
|
|
217
|
+
lines.append(" (none installed)")
|
|
218
|
+
else:
|
|
219
|
+
for pid in sorted(installed_ids):
|
|
220
|
+
status = "enabled" if pid in enabled_ids else "disabled"
|
|
221
|
+
lines.append(f" • {pid} [{status}]")
|
|
222
|
+
lines.append("")
|
|
223
|
+
|
|
224
|
+
records = load_marketplace_records(project_root=project_root)
|
|
225
|
+
found_any_marketplace = False
|
|
226
|
+
for name, record in sorted(records.items()):
|
|
227
|
+
try:
|
|
228
|
+
from opscloud.plugins.marketplace import load_marketplace_location
|
|
229
|
+
|
|
230
|
+
marketplace = load_marketplace_location(Path(record.install_location))
|
|
231
|
+
except Exception:
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
found_any_marketplace = True
|
|
235
|
+
from opscloud.config.plugins import is_default_marketplace
|
|
236
|
+
|
|
237
|
+
mp_label = f"Available in {name}"
|
|
238
|
+
if getattr(record, "is_default", False) or is_default_marketplace(name):
|
|
239
|
+
mp_label += " (Default Marketplace)"
|
|
240
|
+
lines.append(f"{mp_label}:")
|
|
241
|
+
|
|
242
|
+
# Group plugins dynamically by category
|
|
243
|
+
categories: dict[str, list[Any]] = {}
|
|
244
|
+
for p in marketplace.plugins:
|
|
245
|
+
categories.setdefault(p.category_label, []).append(p)
|
|
246
|
+
|
|
247
|
+
for cat_label, cat_plugins in categories.items():
|
|
248
|
+
lines.append(f" {cat_label}:")
|
|
249
|
+
for p in cat_plugins:
|
|
250
|
+
desc = p.description or ""
|
|
251
|
+
short_desc = desc if len(desc) <= 60 else f"{desc[:57]}..."
|
|
252
|
+
lines.append(f" {p.name:<28} v{p.version or '0.1.0':<7} {short_desc}")
|
|
253
|
+
lines.append("")
|
|
254
|
+
|
|
255
|
+
if not found_any_marketplace:
|
|
256
|
+
simple_lines = []
|
|
257
|
+
for row in rows:
|
|
258
|
+
status = "enabled" if row["enabled"] else "disabled"
|
|
259
|
+
simple_lines.append(f"{status} {row['id']} {row['description']}".rstrip())
|
|
260
|
+
text = "\n".join(simple_lines)
|
|
261
|
+
print(text)
|
|
262
|
+
return text
|
|
263
|
+
|
|
264
|
+
text = "\n".join(lines).rstrip()
|
|
265
|
+
print(text)
|
|
266
|
+
return text
|
|
267
|
+
if command == "search":
|
|
268
|
+
query = (getattr(args, "query", "") or "").strip().lower()
|
|
269
|
+
if not query:
|
|
270
|
+
text = "Please provide a search query: opscloud plugin search <query>"
|
|
271
|
+
print(text)
|
|
272
|
+
return text
|
|
273
|
+
|
|
274
|
+
from opscloud.plugins.store import load_all_enabled_plugin_ids, load_installed_plugins
|
|
275
|
+
|
|
276
|
+
records = load_marketplace_records(project_root=_resolve_project_root("project"))
|
|
277
|
+
installed = load_installed_plugins()
|
|
278
|
+
enabled = load_all_enabled_plugin_ids()
|
|
279
|
+
matches: list[dict[str, Any]] = []
|
|
280
|
+
|
|
281
|
+
for mp_name, record in sorted(records.items()):
|
|
282
|
+
try:
|
|
283
|
+
from opscloud.plugins.marketplace import load_marketplace_location
|
|
284
|
+
|
|
285
|
+
marketplace = load_marketplace_location(Path(record.install_location))
|
|
286
|
+
except Exception:
|
|
287
|
+
continue
|
|
288
|
+
for plugin in marketplace.plugins:
|
|
289
|
+
keywords_str = " ".join(plugin.keywords).lower()
|
|
290
|
+
target_str = (
|
|
291
|
+
f"{plugin.name} {plugin.display_name or ''} {plugin.description or ''} "
|
|
292
|
+
f"{plugin.category or ''} {keywords_str}"
|
|
293
|
+
).lower()
|
|
294
|
+
if query in target_str:
|
|
295
|
+
plugin_id = f"{plugin.name}@{marketplace.name}"
|
|
296
|
+
matches.append(
|
|
297
|
+
{
|
|
298
|
+
"id": plugin_id,
|
|
299
|
+
"name": plugin.name,
|
|
300
|
+
"display_name": plugin.display_name or plugin.name,
|
|
301
|
+
"marketplace": marketplace.name,
|
|
302
|
+
"category_label": plugin.category_label,
|
|
303
|
+
"category": plugin.category,
|
|
304
|
+
"version": plugin.version or "0.1.0",
|
|
305
|
+
"description": plugin.description or "",
|
|
306
|
+
"installed": plugin_id in installed,
|
|
307
|
+
"enabled": plugin_id in enabled,
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if output_format == "json":
|
|
312
|
+
import json
|
|
313
|
+
|
|
314
|
+
text = json.dumps({"query": query, "results": matches}, indent=2)
|
|
315
|
+
print(text)
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
if not matches:
|
|
319
|
+
text = f"No plugins found matching '{query}'."
|
|
320
|
+
print(text)
|
|
321
|
+
return text
|
|
322
|
+
|
|
323
|
+
lines = [f"Found {len(matches)} plugin(s) matching '{query}':\n"]
|
|
324
|
+
for m in matches:
|
|
325
|
+
status = (
|
|
326
|
+
"[installed & enabled]"
|
|
327
|
+
if m["enabled"]
|
|
328
|
+
else ("[installed]" if m["installed"] else "[available]")
|
|
329
|
+
)
|
|
330
|
+
lines.append(f" • {m['display_name']} ({m['id']}, v{m['version']}) {status}")
|
|
331
|
+
lines.append(f" Category: {m['category_label']}")
|
|
332
|
+
if m["description"]:
|
|
333
|
+
lines.append(f" {m['description']}")
|
|
334
|
+
lines.append("")
|
|
335
|
+
text = "\n".join(lines).rstrip()
|
|
336
|
+
print(text)
|
|
337
|
+
return text
|
|
338
|
+
if command == "install":
|
|
339
|
+
raw_scope = getattr(args, "scope", "user") or "user"
|
|
340
|
+
scope = cast(InstallScope, raw_scope)
|
|
341
|
+
project_root = _resolve_project_root(scope)
|
|
342
|
+
try:
|
|
343
|
+
instance = install_plugin(
|
|
344
|
+
args.plugin_id, scope=scope, project_root=project_root,
|
|
345
|
+
)
|
|
346
|
+
except (MarketplaceError, FileNotFoundError, OSError, ValueError) as exc:
|
|
347
|
+
text = f"Failed to install {args.plugin_id}: {exc}"
|
|
348
|
+
print(text)
|
|
349
|
+
raise SystemExit(1) from exc
|
|
350
|
+
details = ""
|
|
351
|
+
if instance.version is not None:
|
|
352
|
+
details = f" (version: {instance.version})"
|
|
353
|
+
scope_label = {"user": "for you", "project": "for all collaborators", "local": "for you in this repo"}
|
|
354
|
+
text = (
|
|
355
|
+
f"Installed plugin {instance.plugin_id}{details} "
|
|
356
|
+
f"({scope_label[scope]}). Run /reload to activate."
|
|
357
|
+
)
|
|
358
|
+
print(text)
|
|
359
|
+
return text
|
|
360
|
+
if command == "uninstall":
|
|
361
|
+
raw_scope = getattr(args, "scope", None)
|
|
362
|
+
scope = cast(InstallScope, raw_scope) if raw_scope else None
|
|
363
|
+
project_root = _resolve_project_root(scope) if scope else None
|
|
364
|
+
uninstall_plugin(args.plugin_id, scope=scope, project_root=project_root)
|
|
365
|
+
text = f"Uninstalled plugin {args.plugin_id}."
|
|
366
|
+
print(text)
|
|
367
|
+
return text
|
|
368
|
+
if command in {"enable", "disable"}:
|
|
369
|
+
enabled = command == "enable"
|
|
370
|
+
raw_scope = getattr(args, "scope", "user") or "user"
|
|
371
|
+
scope = cast(InstallScope, raw_scope)
|
|
372
|
+
project_root = _resolve_project_root(scope)
|
|
373
|
+
try:
|
|
374
|
+
set_installed_plugin_enabled(
|
|
375
|
+
args.plugin_id, enabled=enabled, scope=scope, project_root=project_root,
|
|
376
|
+
)
|
|
377
|
+
except (MarketplaceError, OSError, ValueError) as exc:
|
|
378
|
+
text = f"Failed to {command} {args.plugin_id}: {exc}"
|
|
379
|
+
print(text)
|
|
380
|
+
raise SystemExit(1) from exc
|
|
381
|
+
text = f"{command.title()}d plugin {args.plugin_id}."
|
|
382
|
+
print(text)
|
|
383
|
+
return text
|
|
384
|
+
if command == "marketplace":
|
|
385
|
+
marketplace_command = getattr(args, "marketplace_command", None)
|
|
386
|
+
if marketplace_command in {"list", "ls"}:
|
|
387
|
+
records = load_marketplace_records()
|
|
388
|
+
rows = [
|
|
389
|
+
{
|
|
390
|
+
"name": record.name,
|
|
391
|
+
"source_type": record.source_type,
|
|
392
|
+
"source": redact_marketplace_source(record.source),
|
|
393
|
+
"install_location": (
|
|
394
|
+
record.install_location
|
|
395
|
+
if record.source_type in {"directory", "file"}
|
|
396
|
+
else "<managed cache>"
|
|
397
|
+
),
|
|
398
|
+
}
|
|
399
|
+
for record in records.values()
|
|
400
|
+
]
|
|
401
|
+
if output_format == "json":
|
|
402
|
+
import json
|
|
403
|
+
text = json.dumps({"marketplaces": rows}, indent=2)
|
|
404
|
+
print(text)
|
|
405
|
+
return None
|
|
406
|
+
text = (
|
|
407
|
+
"No plugin marketplaces configured."
|
|
408
|
+
if not rows
|
|
409
|
+
else "\n".join(f"{row['name']} {row['source']}" for row in rows)
|
|
410
|
+
)
|
|
411
|
+
print(text)
|
|
412
|
+
return text
|
|
413
|
+
if marketplace_command == "add":
|
|
414
|
+
try:
|
|
415
|
+
marketplace = add_marketplace_source(args.source)
|
|
416
|
+
except (MarketplaceError, FileNotFoundError, OSError, ValueError) as exc:
|
|
417
|
+
source = redact_marketplace_source(args.source)
|
|
418
|
+
text = (
|
|
419
|
+
f"Failed to add marketplace {source}: "
|
|
420
|
+
f"{redact_urls_in_text(str(exc))}"
|
|
421
|
+
)
|
|
422
|
+
print(text)
|
|
423
|
+
raise SystemExit(1) from exc
|
|
424
|
+
text = (
|
|
425
|
+
f"Added marketplace {marketplace.name} "
|
|
426
|
+
f"({len(marketplace.plugins)} plugin(s))."
|
|
427
|
+
)
|
|
428
|
+
print(text)
|
|
429
|
+
return text
|
|
430
|
+
if marketplace_command == "remove":
|
|
431
|
+
removed = remove_marketplace(args.name)
|
|
432
|
+
text = (
|
|
433
|
+
f"Removed marketplace {args.name} and its installed plugins."
|
|
434
|
+
if removed
|
|
435
|
+
else f"Marketplace {args.name} is not configured."
|
|
436
|
+
)
|
|
437
|
+
print(text)
|
|
438
|
+
return text
|
|
439
|
+
if marketplace_command in {"refresh", "update"}:
|
|
440
|
+
target_name = getattr(args, "name", None)
|
|
441
|
+
if target_name:
|
|
442
|
+
try:
|
|
443
|
+
marketplace = refresh_marketplace(target_name)
|
|
444
|
+
except Exception as exc:
|
|
445
|
+
text = f"Failed to refresh marketplace {target_name}: {exc}"
|
|
446
|
+
print(text)
|
|
447
|
+
raise SystemExit(1) from exc
|
|
448
|
+
|
|
449
|
+
if output_format == "json":
|
|
450
|
+
import json
|
|
451
|
+
|
|
452
|
+
text = json.dumps(
|
|
453
|
+
{
|
|
454
|
+
"name": marketplace.name,
|
|
455
|
+
"plugin_count": len(marketplace.plugins),
|
|
456
|
+
"status": "refreshed",
|
|
457
|
+
},
|
|
458
|
+
indent=2,
|
|
459
|
+
)
|
|
460
|
+
print(text)
|
|
461
|
+
return None
|
|
462
|
+
|
|
463
|
+
text = f"Refreshed marketplace '{marketplace.name}' ({len(marketplace.plugins)} plugin(s) available)."
|
|
464
|
+
print(text)
|
|
465
|
+
return text
|
|
466
|
+
|
|
467
|
+
refreshed = refresh_all_marketplaces()
|
|
468
|
+
if output_format == "json":
|
|
469
|
+
import json
|
|
470
|
+
|
|
471
|
+
rows = [
|
|
472
|
+
{
|
|
473
|
+
"name": name,
|
|
474
|
+
"plugin_count": len(mp.plugins),
|
|
475
|
+
"status": "refreshed",
|
|
476
|
+
}
|
|
477
|
+
for name, mp in refreshed.items()
|
|
478
|
+
]
|
|
479
|
+
text = json.dumps({"marketplaces": rows}, indent=2)
|
|
480
|
+
print(text)
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
lines = [f"Refreshed {len(refreshed)} marketplace(s):"]
|
|
484
|
+
for name, mp in refreshed.items():
|
|
485
|
+
lines.append(f" • {name}: {len(mp.plugins)} plugin(s) available")
|
|
486
|
+
text = "\n".join(lines)
|
|
487
|
+
print(text)
|
|
488
|
+
return text
|
|
489
|
+
if command in {"refresh", "update"}:
|
|
490
|
+
target_name = getattr(args, "name", None)
|
|
491
|
+
if target_name:
|
|
492
|
+
try:
|
|
493
|
+
marketplace = refresh_marketplace(target_name)
|
|
494
|
+
except Exception as exc:
|
|
495
|
+
text = f"Failed to refresh marketplace {target_name}: {exc}"
|
|
496
|
+
print(text)
|
|
497
|
+
raise SystemExit(1) from exc
|
|
498
|
+
|
|
499
|
+
if output_format == "json":
|
|
500
|
+
import json
|
|
501
|
+
|
|
502
|
+
text = json.dumps(
|
|
503
|
+
{
|
|
504
|
+
"name": marketplace.name,
|
|
505
|
+
"plugin_count": len(marketplace.plugins),
|
|
506
|
+
"status": "refreshed",
|
|
507
|
+
},
|
|
508
|
+
indent=2,
|
|
509
|
+
)
|
|
510
|
+
print(text)
|
|
511
|
+
return None
|
|
512
|
+
|
|
513
|
+
text = f"Refreshed marketplace '{marketplace.name}' ({len(marketplace.plugins)} plugin(s) available)."
|
|
514
|
+
print(text)
|
|
515
|
+
return text
|
|
516
|
+
|
|
517
|
+
refreshed = refresh_all_marketplaces()
|
|
518
|
+
if output_format == "json":
|
|
519
|
+
import json
|
|
520
|
+
|
|
521
|
+
rows = [
|
|
522
|
+
{
|
|
523
|
+
"name": name,
|
|
524
|
+
"plugin_count": len(mp.plugins),
|
|
525
|
+
"status": "refreshed",
|
|
526
|
+
}
|
|
527
|
+
for name, mp in refreshed.items()
|
|
528
|
+
]
|
|
529
|
+
text = json.dumps({"marketplaces": rows}, indent=2)
|
|
530
|
+
print(text)
|
|
531
|
+
return None
|
|
532
|
+
|
|
533
|
+
lines = [f"Refreshed {len(refreshed)} marketplace(s):"]
|
|
534
|
+
for name, mp in refreshed.items():
|
|
535
|
+
lines.append(f" • {name}: {len(mp.plugins)} plugin(s) available")
|
|
536
|
+
text = "\n".join(lines)
|
|
537
|
+
print(text)
|
|
538
|
+
return text
|
|
539
|
+
text = "Usage: opscloud plugin {list,search,install,uninstall,enable,disable,refresh,marketplace}"
|
|
540
|
+
print(text)
|
|
541
|
+
return text
|