titan-cli 0.1.0__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.
- titan_cli/__init__.py +3 -0
- titan_cli/__main__.py +4 -0
- titan_cli/ai/__init__.py +0 -0
- titan_cli/ai/agents/__init__.py +15 -0
- titan_cli/ai/agents/base.py +152 -0
- titan_cli/ai/client.py +170 -0
- titan_cli/ai/constants.py +56 -0
- titan_cli/ai/exceptions.py +48 -0
- titan_cli/ai/models.py +34 -0
- titan_cli/ai/oauth_helper.py +120 -0
- titan_cli/ai/providers/__init__.py +9 -0
- titan_cli/ai/providers/anthropic.py +117 -0
- titan_cli/ai/providers/base.py +75 -0
- titan_cli/ai/providers/gemini.py +278 -0
- titan_cli/cli.py +59 -0
- titan_cli/clients/__init__.py +1 -0
- titan_cli/clients/gcloud_client.py +52 -0
- titan_cli/core/__init__.py +3 -0
- titan_cli/core/config.py +274 -0
- titan_cli/core/discovery.py +51 -0
- titan_cli/core/errors.py +81 -0
- titan_cli/core/models.py +52 -0
- titan_cli/core/plugins/available.py +36 -0
- titan_cli/core/plugins/models.py +67 -0
- titan_cli/core/plugins/plugin_base.py +108 -0
- titan_cli/core/plugins/plugin_registry.py +163 -0
- titan_cli/core/secrets.py +141 -0
- titan_cli/core/workflows/__init__.py +22 -0
- titan_cli/core/workflows/models.py +88 -0
- titan_cli/core/workflows/project_step_source.py +86 -0
- titan_cli/core/workflows/workflow_exceptions.py +17 -0
- titan_cli/core/workflows/workflow_filter_service.py +137 -0
- titan_cli/core/workflows/workflow_registry.py +419 -0
- titan_cli/core/workflows/workflow_sources.py +307 -0
- titan_cli/engine/__init__.py +39 -0
- titan_cli/engine/builder.py +159 -0
- titan_cli/engine/context.py +82 -0
- titan_cli/engine/mock_context.py +176 -0
- titan_cli/engine/results.py +91 -0
- titan_cli/engine/steps/ai_assistant_step.py +185 -0
- titan_cli/engine/steps/command_step.py +93 -0
- titan_cli/engine/utils/__init__.py +3 -0
- titan_cli/engine/utils/venv.py +31 -0
- titan_cli/engine/workflow_executor.py +187 -0
- titan_cli/external_cli/__init__.py +0 -0
- titan_cli/external_cli/configs.py +17 -0
- titan_cli/external_cli/launcher.py +65 -0
- titan_cli/messages.py +121 -0
- titan_cli/ui/tui/__init__.py +205 -0
- titan_cli/ui/tui/__previews__/statusbar_preview.py +88 -0
- titan_cli/ui/tui/app.py +113 -0
- titan_cli/ui/tui/icons.py +70 -0
- titan_cli/ui/tui/screens/__init__.py +24 -0
- titan_cli/ui/tui/screens/ai_config.py +498 -0
- titan_cli/ui/tui/screens/ai_config_wizard.py +882 -0
- titan_cli/ui/tui/screens/base.py +110 -0
- titan_cli/ui/tui/screens/cli_launcher.py +151 -0
- titan_cli/ui/tui/screens/global_setup_wizard.py +363 -0
- titan_cli/ui/tui/screens/main_menu.py +162 -0
- titan_cli/ui/tui/screens/plugin_config_wizard.py +550 -0
- titan_cli/ui/tui/screens/plugin_management.py +377 -0
- titan_cli/ui/tui/screens/project_setup_wizard.py +686 -0
- titan_cli/ui/tui/screens/workflow_execution.py +592 -0
- titan_cli/ui/tui/screens/workflows.py +249 -0
- titan_cli/ui/tui/textual_components.py +537 -0
- titan_cli/ui/tui/textual_workflow_executor.py +405 -0
- titan_cli/ui/tui/theme.py +102 -0
- titan_cli/ui/tui/widgets/__init__.py +40 -0
- titan_cli/ui/tui/widgets/button.py +108 -0
- titan_cli/ui/tui/widgets/header.py +116 -0
- titan_cli/ui/tui/widgets/panel.py +81 -0
- titan_cli/ui/tui/widgets/status_bar.py +115 -0
- titan_cli/ui/tui/widgets/table.py +77 -0
- titan_cli/ui/tui/widgets/text.py +177 -0
- titan_cli/utils/__init__.py +0 -0
- titan_cli/utils/autoupdate.py +155 -0
- titan_cli-0.1.0.dist-info/METADATA +149 -0
- titan_cli-0.1.0.dist-info/RECORD +146 -0
- titan_cli-0.1.0.dist-info/WHEEL +4 -0
- titan_cli-0.1.0.dist-info/entry_points.txt +9 -0
- titan_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- titan_plugin_git/__init__.py +1 -0
- titan_plugin_git/clients/__init__.py +8 -0
- titan_plugin_git/clients/git_client.py +772 -0
- titan_plugin_git/exceptions.py +40 -0
- titan_plugin_git/messages.py +112 -0
- titan_plugin_git/models.py +39 -0
- titan_plugin_git/plugin.py +118 -0
- titan_plugin_git/steps/__init__.py +1 -0
- titan_plugin_git/steps/ai_commit_message_step.py +171 -0
- titan_plugin_git/steps/branch_steps.py +104 -0
- titan_plugin_git/steps/commit_step.py +80 -0
- titan_plugin_git/steps/push_step.py +63 -0
- titan_plugin_git/steps/status_step.py +59 -0
- titan_plugin_git/workflows/__previews__/__init__.py +1 -0
- titan_plugin_git/workflows/__previews__/commit_ai_preview.py +124 -0
- titan_plugin_git/workflows/commit-ai.yaml +28 -0
- titan_plugin_github/__init__.py +11 -0
- titan_plugin_github/agents/__init__.py +6 -0
- titan_plugin_github/agents/config_loader.py +130 -0
- titan_plugin_github/agents/issue_generator.py +353 -0
- titan_plugin_github/agents/pr_agent.py +528 -0
- titan_plugin_github/clients/__init__.py +8 -0
- titan_plugin_github/clients/github_client.py +1105 -0
- titan_plugin_github/config/__init__.py +0 -0
- titan_plugin_github/config/pr_agent.toml +85 -0
- titan_plugin_github/exceptions.py +28 -0
- titan_plugin_github/messages.py +88 -0
- titan_plugin_github/models.py +330 -0
- titan_plugin_github/plugin.py +131 -0
- titan_plugin_github/steps/__init__.py +12 -0
- titan_plugin_github/steps/ai_pr_step.py +172 -0
- titan_plugin_github/steps/create_pr_step.py +86 -0
- titan_plugin_github/steps/github_prompt_steps.py +171 -0
- titan_plugin_github/steps/issue_steps.py +143 -0
- titan_plugin_github/steps/preview_step.py +40 -0
- titan_plugin_github/utils.py +82 -0
- titan_plugin_github/workflows/__previews__/__init__.py +1 -0
- titan_plugin_github/workflows/__previews__/create_pr_ai_preview.py +140 -0
- titan_plugin_github/workflows/create-issue-ai.yaml +32 -0
- titan_plugin_github/workflows/create-pr-ai.yaml +49 -0
- titan_plugin_jira/__init__.py +8 -0
- titan_plugin_jira/agents/__init__.py +6 -0
- titan_plugin_jira/agents/config_loader.py +154 -0
- titan_plugin_jira/agents/jira_agent.py +553 -0
- titan_plugin_jira/agents/prompts.py +364 -0
- titan_plugin_jira/agents/response_parser.py +435 -0
- titan_plugin_jira/agents/token_tracker.py +223 -0
- titan_plugin_jira/agents/validators.py +246 -0
- titan_plugin_jira/clients/jira_client.py +745 -0
- titan_plugin_jira/config/jira_agent.toml +92 -0
- titan_plugin_jira/config/templates/issue_analysis.md.j2 +78 -0
- titan_plugin_jira/exceptions.py +37 -0
- titan_plugin_jira/formatters/__init__.py +6 -0
- titan_plugin_jira/formatters/markdown_formatter.py +245 -0
- titan_plugin_jira/messages.py +115 -0
- titan_plugin_jira/models.py +89 -0
- titan_plugin_jira/plugin.py +264 -0
- titan_plugin_jira/steps/ai_analyze_issue_step.py +105 -0
- titan_plugin_jira/steps/get_issue_step.py +82 -0
- titan_plugin_jira/steps/prompt_select_issue_step.py +80 -0
- titan_plugin_jira/steps/search_saved_query_step.py +238 -0
- titan_plugin_jira/utils/__init__.py +13 -0
- titan_plugin_jira/utils/issue_sorter.py +140 -0
- titan_plugin_jira/utils/saved_queries.py +150 -0
- titan_plugin_jira/workflows/analyze-jira-issues.yaml +34 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin Management Screen
|
|
3
|
+
|
|
4
|
+
Screen for managing installed plugins:
|
|
5
|
+
- Enable/disable plugins
|
|
6
|
+
- Configure plugin settings
|
|
7
|
+
- View plugin status
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from textual.app import ComposeResult
|
|
11
|
+
from textual.widgets import OptionList, Static
|
|
12
|
+
from textual.widgets.option_list import Option
|
|
13
|
+
from textual.containers import Container, Horizontal, VerticalScroll
|
|
14
|
+
from textual.binding import Binding
|
|
15
|
+
|
|
16
|
+
from titan_cli.ui.tui.icons import Icons
|
|
17
|
+
from titan_cli.ui.tui.widgets import (
|
|
18
|
+
Button,
|
|
19
|
+
Text,
|
|
20
|
+
DimText,
|
|
21
|
+
BoldText,
|
|
22
|
+
BoldPrimaryText,
|
|
23
|
+
)
|
|
24
|
+
from .base import BaseScreen
|
|
25
|
+
from .plugin_config_wizard import PluginConfigWizardScreen
|
|
26
|
+
import tomli
|
|
27
|
+
import tomli_w
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PluginManagementScreen(BaseScreen):
|
|
32
|
+
"""
|
|
33
|
+
Plugin management screen for enabling/disabling and configuring plugins.
|
|
34
|
+
|
|
35
|
+
Displays all installed plugins with their current status and allows:
|
|
36
|
+
- Toggle enable/disable state
|
|
37
|
+
- Configure plugin settings via wizard
|
|
38
|
+
- View plugin information
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
BINDINGS = [
|
|
42
|
+
("escape", "go_back", "Back"),
|
|
43
|
+
("q", "go_back", "Back"),
|
|
44
|
+
Binding("e", "toggle_plugin", "Enable/Disable"),
|
|
45
|
+
Binding("c", "configure_plugin", "Configure"),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
CSS = """
|
|
49
|
+
PluginManagementScreen {
|
|
50
|
+
align: center middle;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#plugin-container {
|
|
54
|
+
width: 100%;
|
|
55
|
+
height: 1fr;
|
|
56
|
+
background: $surface-lighten-1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#plugin-container Horizontal {
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: 1fr;
|
|
62
|
+
padding: 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#left-panel {
|
|
66
|
+
width: 20%;
|
|
67
|
+
height: 100%;
|
|
68
|
+
border: round $primary;
|
|
69
|
+
border-title-align: center;
|
|
70
|
+
background: $surface-lighten-1;
|
|
71
|
+
padding: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#left-panel OptionList {
|
|
75
|
+
height: 100%;
|
|
76
|
+
width: 100%;
|
|
77
|
+
padding: 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#left-panel OptionList > .option-list--option {
|
|
81
|
+
padding: 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#right-panel {
|
|
85
|
+
width: 80%;
|
|
86
|
+
height: 1fr;
|
|
87
|
+
border: round $primary;
|
|
88
|
+
border-title-align: center;
|
|
89
|
+
background: $surface-lighten-1;
|
|
90
|
+
padding: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#plugin-details {
|
|
94
|
+
height: 100%;
|
|
95
|
+
width: 100%;
|
|
96
|
+
padding: 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#details-content {
|
|
100
|
+
height: auto;
|
|
101
|
+
width: 100%;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#details-content Text {
|
|
105
|
+
height: 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#details-content > * {
|
|
109
|
+
margin: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#details-content Horizontal {
|
|
113
|
+
height: auto;
|
|
114
|
+
width: 100%;
|
|
115
|
+
layout: horizontal;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#details-content Horizontal > * {
|
|
119
|
+
height: auto;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.button-container {
|
|
123
|
+
height: auto;
|
|
124
|
+
min-height: 5;
|
|
125
|
+
width: 100%;
|
|
126
|
+
padding: 1 1 2 1;
|
|
127
|
+
margin-top: 1;
|
|
128
|
+
background: $surface-lighten-1;
|
|
129
|
+
align: right middle;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.button-container Button {
|
|
133
|
+
margin-left: 1;
|
|
134
|
+
}
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, config):
|
|
138
|
+
super().__init__(
|
|
139
|
+
config,
|
|
140
|
+
title=f"{Icons.PLUGIN} Plugin Management",
|
|
141
|
+
show_back=True
|
|
142
|
+
)
|
|
143
|
+
self.selected_plugin = None
|
|
144
|
+
self.installed_plugins = []
|
|
145
|
+
|
|
146
|
+
def compose_content(self) -> ComposeResult:
|
|
147
|
+
"""Compose the plugin management screen."""
|
|
148
|
+
with Container(id="plugin-container"):
|
|
149
|
+
with Horizontal():
|
|
150
|
+
# Left panel: Plugin list
|
|
151
|
+
left_panel = Container(id="left-panel")
|
|
152
|
+
left_panel.border_title = "Installed Plugins"
|
|
153
|
+
with left_panel:
|
|
154
|
+
yield OptionList(id="plugin-list")
|
|
155
|
+
|
|
156
|
+
# Right panel: Plugin details and actions
|
|
157
|
+
right_panel = Container(id="right-panel")
|
|
158
|
+
right_panel.border_title = "Plugin Details"
|
|
159
|
+
with right_panel:
|
|
160
|
+
with VerticalScroll(id="plugin-details"):
|
|
161
|
+
yield Container(id="details-content")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def on_mount(self) -> None:
|
|
165
|
+
"""Initialize the screen with plugin list."""
|
|
166
|
+
self._load_plugins()
|
|
167
|
+
|
|
168
|
+
def _load_plugins(self) -> None:
|
|
169
|
+
"""Load and display installed plugins."""
|
|
170
|
+
self.installed_plugins = self.config.registry.list_installed()
|
|
171
|
+
|
|
172
|
+
plugin_list = self.query_one("#plugin-list", OptionList)
|
|
173
|
+
plugin_list.clear_options()
|
|
174
|
+
|
|
175
|
+
if not self.installed_plugins:
|
|
176
|
+
plugin_list.add_option(Option("No plugins installed", id="none", disabled=True))
|
|
177
|
+
self._show_no_plugin_selected()
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
# Add plugin options
|
|
181
|
+
for plugin_name in self.installed_plugins:
|
|
182
|
+
is_enabled = self.config.is_plugin_enabled(plugin_name)
|
|
183
|
+
status_icon = Icons.SUCCESS if is_enabled else Icons.ERROR
|
|
184
|
+
status_text = "Enabled" if is_enabled else "Disabled"
|
|
185
|
+
|
|
186
|
+
plugin_list.add_option(
|
|
187
|
+
Option(
|
|
188
|
+
f"{status_icon} {plugin_name} - {status_text}",
|
|
189
|
+
id=plugin_name
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Select first plugin by default
|
|
194
|
+
if self.installed_plugins:
|
|
195
|
+
plugin_list.highlighted = 0
|
|
196
|
+
self.selected_plugin = self.installed_plugins[0]
|
|
197
|
+
self._show_plugin_details(self.selected_plugin)
|
|
198
|
+
|
|
199
|
+
def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
|
|
200
|
+
"""Handle plugin selection (Enter key)."""
|
|
201
|
+
if event.option.id == "none":
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
self.selected_plugin = event.option.id
|
|
205
|
+
self._show_plugin_details(self.selected_plugin)
|
|
206
|
+
|
|
207
|
+
def on_option_list_option_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
|
208
|
+
"""Handle plugin highlight change (arrow keys navigation)."""
|
|
209
|
+
if event.option.id == "none":
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
self.selected_plugin = event.option.id
|
|
213
|
+
self._show_plugin_details(self.selected_plugin)
|
|
214
|
+
|
|
215
|
+
def _show_no_plugin_selected(self) -> None:
|
|
216
|
+
"""Display message when no plugin is selected."""
|
|
217
|
+
details = self.query_one("#details-content", Container)
|
|
218
|
+
details.remove_children()
|
|
219
|
+
|
|
220
|
+
details.mount(DimText("No plugins installed."))
|
|
221
|
+
details.mount(DimText("Plugins are automatically discovered from installed packages."))
|
|
222
|
+
|
|
223
|
+
def _show_plugin_details(self, plugin_name: str) -> None:
|
|
224
|
+
"""Display details for the selected plugin."""
|
|
225
|
+
if not plugin_name or plugin_name == "none":
|
|
226
|
+
self._show_no_plugin_selected()
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
# Get plugin info
|
|
230
|
+
is_enabled = self.config.is_plugin_enabled(plugin_name)
|
|
231
|
+
plugin = self.config.registry._plugins.get(plugin_name)
|
|
232
|
+
|
|
233
|
+
# Clear and rebuild details
|
|
234
|
+
details = self.query_one("#details-content", Container)
|
|
235
|
+
details.remove_children()
|
|
236
|
+
|
|
237
|
+
# Plugin name
|
|
238
|
+
details.mount(BoldPrimaryText(plugin_name))
|
|
239
|
+
details.mount(Text(""))
|
|
240
|
+
|
|
241
|
+
# Status
|
|
242
|
+
if is_enabled:
|
|
243
|
+
details.mount(Static("[bold]Status:[/bold] [green]Enabled[/green]"))
|
|
244
|
+
else:
|
|
245
|
+
details.mount(Static("[bold]Status:[/bold] [red]Disabled[/red]"))
|
|
246
|
+
|
|
247
|
+
# Plugin metadata
|
|
248
|
+
if plugin:
|
|
249
|
+
if hasattr(plugin, '__doc__') and plugin.__doc__:
|
|
250
|
+
details.mount(Text("")) # Spacer
|
|
251
|
+
details.mount(BoldText("Description:"))
|
|
252
|
+
# Clean docstring: remove indentation from each line
|
|
253
|
+
lines = plugin.__doc__.strip().split('\n')
|
|
254
|
+
clean_lines = [line.strip() for line in lines if line.strip()]
|
|
255
|
+
clean_desc = '\n'.join(clean_lines)
|
|
256
|
+
details.mount(DimText(clean_desc))
|
|
257
|
+
details.mount(Text(""))
|
|
258
|
+
|
|
259
|
+
if hasattr(plugin, 'version'):
|
|
260
|
+
details.mount(Static(f"[bold]Version:[/bold] {plugin.version}"))
|
|
261
|
+
|
|
262
|
+
# Check if plugin has configuration schema
|
|
263
|
+
has_config = False
|
|
264
|
+
if plugin and hasattr(plugin, 'get_config_schema'):
|
|
265
|
+
try:
|
|
266
|
+
schema = plugin.get_config_schema()
|
|
267
|
+
if schema and schema.get('properties'):
|
|
268
|
+
has_config = True
|
|
269
|
+
except Exception:
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
details.mount(Text("")) # Spacer
|
|
273
|
+
if has_config:
|
|
274
|
+
details.mount(DimText("✓ This plugin supports configuration"))
|
|
275
|
+
else:
|
|
276
|
+
details.mount(DimText("✗ This plugin has no configuration options"))
|
|
277
|
+
|
|
278
|
+
# Show current configuration if enabled
|
|
279
|
+
if is_enabled and self.config.config and self.config.config.plugins:
|
|
280
|
+
plugin_cfg = self.config.config.plugins.get(plugin_name)
|
|
281
|
+
if plugin_cfg and plugin_cfg.config:
|
|
282
|
+
details.mount(Text("")) # Spacer
|
|
283
|
+
details.mount(BoldText("Current Configuration:"))
|
|
284
|
+
for key, value in plugin_cfg.config.items():
|
|
285
|
+
# Don't show secrets
|
|
286
|
+
if any(secret in key.lower() for secret in ['token', 'password', 'secret', 'api_key']):
|
|
287
|
+
details.mount(DimText(f" {key}: ••••••••"))
|
|
288
|
+
else:
|
|
289
|
+
details.mount(DimText(f" {key}: {value}"))
|
|
290
|
+
|
|
291
|
+
# Actions
|
|
292
|
+
details.mount(Text("")) # Spacer
|
|
293
|
+
details.mount(BoldText("Actions:"))
|
|
294
|
+
action_verb = "disable" if is_enabled else "enable"
|
|
295
|
+
details.mount(DimText(f" Press e to {action_verb} this plugin"))
|
|
296
|
+
details.mount(DimText(" Press c to configure this plugin"))
|
|
297
|
+
|
|
298
|
+
# Buttons
|
|
299
|
+
details.mount(Text("")) # Spacer
|
|
300
|
+
button_container = Horizontal(
|
|
301
|
+
Button("Enable/Disable", variant="default", id="toggle-button"),
|
|
302
|
+
Button("Configure", variant="primary", id="configure-button"),
|
|
303
|
+
classes="button-container"
|
|
304
|
+
)
|
|
305
|
+
details.mount(button_container)
|
|
306
|
+
|
|
307
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
308
|
+
"""Handle button presses."""
|
|
309
|
+
if event.button.id == "toggle-button":
|
|
310
|
+
self.action_toggle_plugin()
|
|
311
|
+
elif event.button.id == "configure-button":
|
|
312
|
+
self.action_configure_plugin()
|
|
313
|
+
|
|
314
|
+
def action_toggle_plugin(self) -> None:
|
|
315
|
+
"""Toggle enable/disable state of selected plugin."""
|
|
316
|
+
if not self.selected_plugin:
|
|
317
|
+
self.app.notify("Please select a plugin", severity="warning")
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
|
|
322
|
+
project_cfg_path = self.config.project_config_path
|
|
323
|
+
if not project_cfg_path or not project_cfg_path.exists():
|
|
324
|
+
self.app.notify("No project configuration found", severity="error")
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
# Load current config
|
|
328
|
+
with open(project_cfg_path, "rb") as f:
|
|
329
|
+
project_cfg_dict = tomli.load(f)
|
|
330
|
+
|
|
331
|
+
# Ensure plugin entry exists
|
|
332
|
+
plugins_table = project_cfg_dict.setdefault("plugins", {})
|
|
333
|
+
plugin_table = plugins_table.setdefault(self.selected_plugin, {})
|
|
334
|
+
|
|
335
|
+
# Toggle enabled state
|
|
336
|
+
current_state = plugin_table.get("enabled", True)
|
|
337
|
+
new_state = not current_state
|
|
338
|
+
plugin_table["enabled"] = new_state
|
|
339
|
+
|
|
340
|
+
# Save config
|
|
341
|
+
with open(project_cfg_path, "wb") as f:
|
|
342
|
+
tomli_w.dump(project_cfg_dict, f)
|
|
343
|
+
|
|
344
|
+
# Reload config
|
|
345
|
+
self.config.load()
|
|
346
|
+
|
|
347
|
+
# Refresh display
|
|
348
|
+
self._load_plugins()
|
|
349
|
+
|
|
350
|
+
action = "enabled" if new_state else "disabled"
|
|
351
|
+
self.app.notify(f"Plugin '{self.selected_plugin}' {action}", severity="information")
|
|
352
|
+
|
|
353
|
+
except Exception as e:
|
|
354
|
+
self.app.notify(f"Failed to toggle plugin: {e}", severity="error")
|
|
355
|
+
|
|
356
|
+
def action_configure_plugin(self) -> None:
|
|
357
|
+
"""Open configuration wizard for selected plugin."""
|
|
358
|
+
if not self.selected_plugin:
|
|
359
|
+
self.app.notify("Please select a plugin", severity="warning")
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
# Check if plugin has config schema
|
|
363
|
+
plugin = self.config.registry._plugins.get(self.selected_plugin)
|
|
364
|
+
if not plugin or not hasattr(plugin, 'get_config_schema'):
|
|
365
|
+
self.app.notify("This plugin has no configuration options", severity="warning")
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
# Open configuration wizard
|
|
369
|
+
def on_wizard_close(result):
|
|
370
|
+
"""Handle wizard completion."""
|
|
371
|
+
if result:
|
|
372
|
+
# Reload config and refresh display
|
|
373
|
+
self.config.load()
|
|
374
|
+
self._load_plugins()
|
|
375
|
+
|
|
376
|
+
wizard = PluginConfigWizardScreen(self.config, self.selected_plugin)
|
|
377
|
+
self.app.push_screen(wizard, on_wizard_close)
|