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.
Files changed (146) hide show
  1. titan_cli/__init__.py +3 -0
  2. titan_cli/__main__.py +4 -0
  3. titan_cli/ai/__init__.py +0 -0
  4. titan_cli/ai/agents/__init__.py +15 -0
  5. titan_cli/ai/agents/base.py +152 -0
  6. titan_cli/ai/client.py +170 -0
  7. titan_cli/ai/constants.py +56 -0
  8. titan_cli/ai/exceptions.py +48 -0
  9. titan_cli/ai/models.py +34 -0
  10. titan_cli/ai/oauth_helper.py +120 -0
  11. titan_cli/ai/providers/__init__.py +9 -0
  12. titan_cli/ai/providers/anthropic.py +117 -0
  13. titan_cli/ai/providers/base.py +75 -0
  14. titan_cli/ai/providers/gemini.py +278 -0
  15. titan_cli/cli.py +59 -0
  16. titan_cli/clients/__init__.py +1 -0
  17. titan_cli/clients/gcloud_client.py +52 -0
  18. titan_cli/core/__init__.py +3 -0
  19. titan_cli/core/config.py +274 -0
  20. titan_cli/core/discovery.py +51 -0
  21. titan_cli/core/errors.py +81 -0
  22. titan_cli/core/models.py +52 -0
  23. titan_cli/core/plugins/available.py +36 -0
  24. titan_cli/core/plugins/models.py +67 -0
  25. titan_cli/core/plugins/plugin_base.py +108 -0
  26. titan_cli/core/plugins/plugin_registry.py +163 -0
  27. titan_cli/core/secrets.py +141 -0
  28. titan_cli/core/workflows/__init__.py +22 -0
  29. titan_cli/core/workflows/models.py +88 -0
  30. titan_cli/core/workflows/project_step_source.py +86 -0
  31. titan_cli/core/workflows/workflow_exceptions.py +17 -0
  32. titan_cli/core/workflows/workflow_filter_service.py +137 -0
  33. titan_cli/core/workflows/workflow_registry.py +419 -0
  34. titan_cli/core/workflows/workflow_sources.py +307 -0
  35. titan_cli/engine/__init__.py +39 -0
  36. titan_cli/engine/builder.py +159 -0
  37. titan_cli/engine/context.py +82 -0
  38. titan_cli/engine/mock_context.py +176 -0
  39. titan_cli/engine/results.py +91 -0
  40. titan_cli/engine/steps/ai_assistant_step.py +185 -0
  41. titan_cli/engine/steps/command_step.py +93 -0
  42. titan_cli/engine/utils/__init__.py +3 -0
  43. titan_cli/engine/utils/venv.py +31 -0
  44. titan_cli/engine/workflow_executor.py +187 -0
  45. titan_cli/external_cli/__init__.py +0 -0
  46. titan_cli/external_cli/configs.py +17 -0
  47. titan_cli/external_cli/launcher.py +65 -0
  48. titan_cli/messages.py +121 -0
  49. titan_cli/ui/tui/__init__.py +205 -0
  50. titan_cli/ui/tui/__previews__/statusbar_preview.py +88 -0
  51. titan_cli/ui/tui/app.py +113 -0
  52. titan_cli/ui/tui/icons.py +70 -0
  53. titan_cli/ui/tui/screens/__init__.py +24 -0
  54. titan_cli/ui/tui/screens/ai_config.py +498 -0
  55. titan_cli/ui/tui/screens/ai_config_wizard.py +882 -0
  56. titan_cli/ui/tui/screens/base.py +110 -0
  57. titan_cli/ui/tui/screens/cli_launcher.py +151 -0
  58. titan_cli/ui/tui/screens/global_setup_wizard.py +363 -0
  59. titan_cli/ui/tui/screens/main_menu.py +162 -0
  60. titan_cli/ui/tui/screens/plugin_config_wizard.py +550 -0
  61. titan_cli/ui/tui/screens/plugin_management.py +377 -0
  62. titan_cli/ui/tui/screens/project_setup_wizard.py +686 -0
  63. titan_cli/ui/tui/screens/workflow_execution.py +592 -0
  64. titan_cli/ui/tui/screens/workflows.py +249 -0
  65. titan_cli/ui/tui/textual_components.py +537 -0
  66. titan_cli/ui/tui/textual_workflow_executor.py +405 -0
  67. titan_cli/ui/tui/theme.py +102 -0
  68. titan_cli/ui/tui/widgets/__init__.py +40 -0
  69. titan_cli/ui/tui/widgets/button.py +108 -0
  70. titan_cli/ui/tui/widgets/header.py +116 -0
  71. titan_cli/ui/tui/widgets/panel.py +81 -0
  72. titan_cli/ui/tui/widgets/status_bar.py +115 -0
  73. titan_cli/ui/tui/widgets/table.py +77 -0
  74. titan_cli/ui/tui/widgets/text.py +177 -0
  75. titan_cli/utils/__init__.py +0 -0
  76. titan_cli/utils/autoupdate.py +155 -0
  77. titan_cli-0.1.0.dist-info/METADATA +149 -0
  78. titan_cli-0.1.0.dist-info/RECORD +146 -0
  79. titan_cli-0.1.0.dist-info/WHEEL +4 -0
  80. titan_cli-0.1.0.dist-info/entry_points.txt +9 -0
  81. titan_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
  82. titan_plugin_git/__init__.py +1 -0
  83. titan_plugin_git/clients/__init__.py +8 -0
  84. titan_plugin_git/clients/git_client.py +772 -0
  85. titan_plugin_git/exceptions.py +40 -0
  86. titan_plugin_git/messages.py +112 -0
  87. titan_plugin_git/models.py +39 -0
  88. titan_plugin_git/plugin.py +118 -0
  89. titan_plugin_git/steps/__init__.py +1 -0
  90. titan_plugin_git/steps/ai_commit_message_step.py +171 -0
  91. titan_plugin_git/steps/branch_steps.py +104 -0
  92. titan_plugin_git/steps/commit_step.py +80 -0
  93. titan_plugin_git/steps/push_step.py +63 -0
  94. titan_plugin_git/steps/status_step.py +59 -0
  95. titan_plugin_git/workflows/__previews__/__init__.py +1 -0
  96. titan_plugin_git/workflows/__previews__/commit_ai_preview.py +124 -0
  97. titan_plugin_git/workflows/commit-ai.yaml +28 -0
  98. titan_plugin_github/__init__.py +11 -0
  99. titan_plugin_github/agents/__init__.py +6 -0
  100. titan_plugin_github/agents/config_loader.py +130 -0
  101. titan_plugin_github/agents/issue_generator.py +353 -0
  102. titan_plugin_github/agents/pr_agent.py +528 -0
  103. titan_plugin_github/clients/__init__.py +8 -0
  104. titan_plugin_github/clients/github_client.py +1105 -0
  105. titan_plugin_github/config/__init__.py +0 -0
  106. titan_plugin_github/config/pr_agent.toml +85 -0
  107. titan_plugin_github/exceptions.py +28 -0
  108. titan_plugin_github/messages.py +88 -0
  109. titan_plugin_github/models.py +330 -0
  110. titan_plugin_github/plugin.py +131 -0
  111. titan_plugin_github/steps/__init__.py +12 -0
  112. titan_plugin_github/steps/ai_pr_step.py +172 -0
  113. titan_plugin_github/steps/create_pr_step.py +86 -0
  114. titan_plugin_github/steps/github_prompt_steps.py +171 -0
  115. titan_plugin_github/steps/issue_steps.py +143 -0
  116. titan_plugin_github/steps/preview_step.py +40 -0
  117. titan_plugin_github/utils.py +82 -0
  118. titan_plugin_github/workflows/__previews__/__init__.py +1 -0
  119. titan_plugin_github/workflows/__previews__/create_pr_ai_preview.py +140 -0
  120. titan_plugin_github/workflows/create-issue-ai.yaml +32 -0
  121. titan_plugin_github/workflows/create-pr-ai.yaml +49 -0
  122. titan_plugin_jira/__init__.py +8 -0
  123. titan_plugin_jira/agents/__init__.py +6 -0
  124. titan_plugin_jira/agents/config_loader.py +154 -0
  125. titan_plugin_jira/agents/jira_agent.py +553 -0
  126. titan_plugin_jira/agents/prompts.py +364 -0
  127. titan_plugin_jira/agents/response_parser.py +435 -0
  128. titan_plugin_jira/agents/token_tracker.py +223 -0
  129. titan_plugin_jira/agents/validators.py +246 -0
  130. titan_plugin_jira/clients/jira_client.py +745 -0
  131. titan_plugin_jira/config/jira_agent.toml +92 -0
  132. titan_plugin_jira/config/templates/issue_analysis.md.j2 +78 -0
  133. titan_plugin_jira/exceptions.py +37 -0
  134. titan_plugin_jira/formatters/__init__.py +6 -0
  135. titan_plugin_jira/formatters/markdown_formatter.py +245 -0
  136. titan_plugin_jira/messages.py +115 -0
  137. titan_plugin_jira/models.py +89 -0
  138. titan_plugin_jira/plugin.py +264 -0
  139. titan_plugin_jira/steps/ai_analyze_issue_step.py +105 -0
  140. titan_plugin_jira/steps/get_issue_step.py +82 -0
  141. titan_plugin_jira/steps/prompt_select_issue_step.py +80 -0
  142. titan_plugin_jira/steps/search_saved_query_step.py +238 -0
  143. titan_plugin_jira/utils/__init__.py +13 -0
  144. titan_plugin_jira/utils/issue_sorter.py +140 -0
  145. titan_plugin_jira/utils/saved_queries.py +150 -0
  146. 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)