titan-cli 0.1.5__py3-none-any.whl → 0.1.6__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 (54) hide show
  1. titan_cli/core/workflows/project_step_source.py +52 -7
  2. titan_cli/core/workflows/workflow_filter_service.py +6 -4
  3. titan_cli/engine/__init__.py +5 -1
  4. titan_cli/engine/results.py +31 -1
  5. titan_cli/engine/steps/ai_assistant_step.py +18 -18
  6. titan_cli/engine/workflow_executor.py +7 -2
  7. titan_cli/ui/tui/screens/plugin_config_wizard.py +16 -0
  8. titan_cli/ui/tui/screens/workflow_execution.py +22 -24
  9. titan_cli/ui/tui/screens/workflows.py +8 -4
  10. titan_cli/ui/tui/textual_components.py +293 -189
  11. titan_cli/ui/tui/textual_workflow_executor.py +30 -2
  12. titan_cli/ui/tui/theme.py +34 -5
  13. titan_cli/ui/tui/widgets/__init__.py +15 -0
  14. titan_cli/ui/tui/widgets/multiline_input.py +32 -0
  15. titan_cli/ui/tui/widgets/prompt_choice.py +138 -0
  16. titan_cli/ui/tui/widgets/prompt_input.py +74 -0
  17. titan_cli/ui/tui/widgets/prompt_selection_list.py +150 -0
  18. titan_cli/ui/tui/widgets/prompt_textarea.py +87 -0
  19. titan_cli/ui/tui/widgets/styled_option_list.py +107 -0
  20. titan_cli/ui/tui/widgets/text.py +51 -130
  21. {titan_cli-0.1.5.dist-info → titan_cli-0.1.6.dist-info}/METADATA +5 -10
  22. {titan_cli-0.1.5.dist-info → titan_cli-0.1.6.dist-info}/RECORD +54 -41
  23. {titan_cli-0.1.5.dist-info → titan_cli-0.1.6.dist-info}/WHEEL +1 -1
  24. titan_plugin_git/clients/git_client.py +59 -2
  25. titan_plugin_git/plugin.py +10 -0
  26. titan_plugin_git/steps/ai_commit_message_step.py +8 -8
  27. titan_plugin_git/steps/branch_steps.py +6 -6
  28. titan_plugin_git/steps/checkout_step.py +66 -0
  29. titan_plugin_git/steps/commit_step.py +3 -3
  30. titan_plugin_git/steps/create_branch_step.py +131 -0
  31. titan_plugin_git/steps/diff_summary_step.py +11 -13
  32. titan_plugin_git/steps/pull_step.py +70 -0
  33. titan_plugin_git/steps/push_step.py +3 -3
  34. titan_plugin_git/steps/restore_original_branch_step.py +97 -0
  35. titan_plugin_git/steps/save_current_branch_step.py +82 -0
  36. titan_plugin_git/steps/status_step.py +23 -13
  37. titan_plugin_git/workflows/commit-ai.yaml +4 -3
  38. titan_plugin_github/steps/ai_pr_step.py +90 -22
  39. titan_plugin_github/steps/create_pr_step.py +8 -8
  40. titan_plugin_github/steps/github_prompt_steps.py +13 -13
  41. titan_plugin_github/steps/issue_steps.py +14 -15
  42. titan_plugin_github/steps/preview_step.py +8 -8
  43. titan_plugin_github/workflows/create-pr-ai.yaml +1 -11
  44. titan_plugin_jira/messages.py +12 -0
  45. titan_plugin_jira/plugin.py +4 -0
  46. titan_plugin_jira/steps/ai_analyze_issue_step.py +6 -6
  47. titan_plugin_jira/steps/get_issue_step.py +7 -7
  48. titan_plugin_jira/steps/list_versions_step.py +133 -0
  49. titan_plugin_jira/steps/prompt_select_issue_step.py +8 -9
  50. titan_plugin_jira/steps/search_jql_step.py +191 -0
  51. titan_plugin_jira/steps/search_saved_query_step.py +13 -13
  52. titan_plugin_jira/utils/__init__.py +1 -1
  53. {titan_cli-0.1.5.dist-info/licenses → titan_cli-0.1.6.dist-info}/LICENSE +0 -0
  54. {titan_cli-0.1.5.dist-info → titan_cli-0.1.6.dist-info}/entry_points.txt +0 -0
@@ -7,193 +7,39 @@ Steps can import widgets directly from titan_cli.ui.tui.widgets and mount them u
7
7
  """
8
8
 
9
9
  import threading
10
- from typing import Optional, Callable
10
+ from typing import Optional, List, Any
11
11
  from contextlib import contextmanager
12
12
  from textual.widget import Widget
13
- from textual.widgets import Input, LoadingIndicator, Static, Markdown, TextArea
13
+ from textual.widgets import LoadingIndicator, Static, Markdown
14
14
  from textual.containers import Container
15
- from textual.message import Message
16
-
17
-
18
- class PromptInput(Widget):
19
- """Widget wrapper for Input that handles submission events."""
20
-
21
- # Allow this widget and its children to receive focus
22
- can_focus = True
23
- can_focus_children = True
24
-
25
- DEFAULT_CSS = """
26
- PromptInput {
27
- width: 100%;
28
- height: auto;
29
- padding: 1;
30
- margin: 1 0;
31
- background: $surface-lighten-1;
32
- border: round $accent;
33
- }
34
-
35
- PromptInput > Static {
36
- width: 100%;
37
- height: auto;
38
- margin-bottom: 1;
39
- }
40
-
41
- PromptInput > Input {
42
- width: 100%;
43
- }
44
- """
45
-
46
- def __init__(self, question: str, default: str, placeholder: str, on_submit: Callable[[str], None], **kwargs):
47
- super().__init__(**kwargs)
48
- self.question = question
49
- self.default = default
50
- self.placeholder = placeholder
51
- self.on_submit_callback = on_submit
52
-
53
- def compose(self):
54
- from textual.widgets import Static
55
- yield Static(f"[bold cyan]{self.question}[/bold cyan]")
56
- yield Input(
57
- value=self.default,
58
- placeholder=self.placeholder,
59
- id="prompt-input"
60
- )
61
-
62
- def on_mount(self):
63
- """Focus input when mounted and scroll into view."""
64
- # Use call_after_refresh to ensure widget tree is ready
65
- self.call_after_refresh(self._focus_input)
66
-
67
- def _focus_input(self):
68
- """Focus the input widget and scroll into view."""
69
- try:
70
- input_widget = self.query_one(Input)
71
- # Use app.set_focus() to force focus change from steps-panel
72
- self.app.set_focus(input_widget)
73
- # Scroll to make this widget visible
74
- self.scroll_visible(animate=False)
75
- except Exception:
76
- pass
77
-
78
- def on_input_submitted(self, event: Input.Submitted) -> None:
79
- """Handle input submission."""
80
- value = event.value
81
- self.on_submit_callback(value)
82
-
83
-
84
- class MultilineInput(TextArea):
85
- """Custom TextArea that handles Enter for submission and Shift+Enter for new lines."""
86
-
87
- class Submitted(Message):
88
- """Message sent when the input is submitted."""
89
- def __init__(self, sender: Widget, value: str):
90
- super().__init__()
91
- self.sender = sender
92
- self.value = value
93
-
94
- def _on_key(self, event) -> None:
95
- """Intercept key events before TextArea processes them."""
96
- from textual.events import Key
97
-
98
- # Check if it's Enter without shift
99
- if isinstance(event, Key) and event.key == "enter":
100
- # Submit the input
101
- self.post_message(self.Submitted(self, self.text))
102
- event.prevent_default()
103
- event.stop()
104
- return
105
-
106
- # For all other keys, let TextArea handle it
107
- super()._on_key(event)
108
-
109
-
110
- class PromptTextArea(Widget):
111
- """Widget wrapper for MultilineInput that handles multiline input submission."""
112
-
113
- can_focus = True
114
- can_focus_children = True
115
-
116
- DEFAULT_CSS = """
117
- PromptTextArea {
118
- width: 100%;
119
- height: auto;
120
- padding: 1;
121
- margin: 1 0;
122
- background: $surface-lighten-1;
123
- border: round $accent;
124
- }
125
-
126
- PromptTextArea > Static {
127
- width: 100%;
128
- height: auto;
129
- margin-bottom: 1;
130
- }
131
-
132
- PromptTextArea > MultilineInput {
133
- width: 100%;
134
- height: auto;
135
- }
136
-
137
- PromptTextArea .hint-text {
138
- width: 100%;
139
- height: auto;
140
- margin-top: 1;
141
- color: $text-muted;
142
- }
143
- """
144
-
145
- def __init__(self, question: str, default: str, on_submit: Callable[[str], None], **kwargs):
146
- super().__init__(**kwargs)
147
- self.question = question
148
- self.default = default
149
- self.on_submit_callback = on_submit
150
-
151
- def compose(self):
152
- from textual.widgets import Static
153
- yield Static(f"[bold cyan]{self.question}[/bold cyan]")
154
- yield MultilineInput(
155
- text=self.default,
156
- id="prompt-textarea",
157
- soft_wrap=True
158
- )
159
- yield Static("[dim]Press Enter to submit, Shift+Enter for new line[/dim]", classes="hint-text")
160
-
161
- def on_mount(self):
162
- """Focus textarea when mounted and scroll into view."""
163
- self.call_after_refresh(self._focus_textarea)
164
-
165
- def _focus_textarea(self):
166
- """Focus the textarea widget and scroll into view."""
167
- try:
168
- textarea = self.query_one(MultilineInput)
169
- self.app.set_focus(textarea)
170
- self.scroll_visible(animate=False)
171
- except Exception:
172
- pass
173
-
174
- def on_multiline_input_submitted(self, message: MultilineInput.Submitted):
175
- """Handle submission from MultilineInput."""
176
- self.on_submit_callback(message.value)
15
+ from titan_cli.ui.tui.widgets import Panel, PromptInput, PromptTextArea, PromptSelectionList, SelectionOption, PromptChoice, ChoiceOption
177
16
 
178
17
 
179
18
  class TextualComponents:
180
19
  """
181
20
  Textual UI utilities for workflow steps.
182
21
 
183
- Steps import widgets directly (Panel, DimText, etc.) and use these utilities to:
184
- - Mount widgets to the output panel
185
- - Append simple text with markup
22
+ All text styling uses the theme system for consistent colors across themes.
23
+
24
+ Steps can use these utilities to:
25
+ - Display panels with consistent styling
26
+ - Append text with theme-based styling
186
27
  - Request user input interactively
28
+ - Mount custom widgets to the output panel
187
29
 
188
30
  Example:
189
- from titan_cli.ui.tui.widgets import Panel, DimText
190
-
191
31
  def my_step(ctx):
192
- # Mount a panel widget
193
- ctx.textual.mount(Panel("Warning message", panel_type="warning"))
32
+ # Show a panel
33
+ ctx.textual.panel("Warning message", panel_type="warning")
194
34
 
195
- # Append inline text
196
- ctx.textual.text("Analyzing changes...")
35
+ # Append styled text (uses theme system)
36
+ ctx.textual.dim_text("Fetching data...")
37
+ ctx.textual.success_text("Operation completed!")
38
+ ctx.textual.error_text("Failed to connect")
39
+ ctx.textual.bold_primary_text("AI Analysis Results")
40
+
41
+ # Append plain text
42
+ ctx.textual.text("Processing...")
197
43
 
198
44
  # Ask for input
199
45
  response = ctx.textual.ask_confirm("Continue?", default=True)
@@ -213,7 +59,7 @@ class TextualComponents:
213
59
 
214
60
  def begin_step(self, step_name: str) -> None:
215
61
  """
216
- Begin a new step by creating a StepContainer.
62
+ Begin a new step by creating a StepContainer and auto-scrolling to it.
217
63
 
218
64
  Args:
219
65
  step_name: Name of the step
@@ -224,6 +70,8 @@ class TextualComponents:
224
70
  container = StepContainer(step_name=step_name)
225
71
  self.output_widget.mount(container)
226
72
  self._active_step_container = container
73
+ # Auto-scroll to show the new step
74
+ self.output_widget._scroll_to_end()
227
75
 
228
76
  try:
229
77
  self.app.call_from_thread(_create_container)
@@ -273,33 +121,46 @@ class TextualComponents:
273
121
  # App is closing or worker was cancelled
274
122
  pass
275
123
 
276
- def text(self, text: str, markup: str = "") -> None:
124
+ def scroll_to_end(self) -> None:
277
125
  """
278
- Append inline text with optional Rich markup.
126
+ Scroll to the end of the output widget.
127
+
128
+ Useful for ensuring user sees newly added content in steps with lots of output.
129
+
130
+ Example:
131
+ ctx.textual.markdown(large_content)
132
+ ctx.textual.scroll_to_end() # Ensure user sees what comes next
133
+ """
134
+ def _scroll():
135
+ self.output_widget._scroll_to_end()
136
+
137
+ try:
138
+ self.app.call_from_thread(_scroll)
139
+ except Exception:
140
+ pass
141
+
142
+ def text(self, text: str) -> None:
143
+ """
144
+ Append plain text without styling.
145
+
146
+ For styled text, use specific methods: dim_text(), success_text(), etc.
279
147
 
280
148
  Args:
281
149
  text: Text to append
282
- markup: Optional Rich markup style (e.g., "cyan", "bold green")
283
150
 
284
151
  Example:
285
- ctx.textual.text("Analyzing changes...", markup="cyan")
286
- ctx.textual.text("Done!")
152
+ ctx.textual.text("Processing...")
153
+ ctx.textual.text("") # Empty line
287
154
  """
288
155
  def _append():
289
156
  # If there's an active step container, append to it; otherwise to output widget
290
157
  if self._active_step_container:
291
158
  from textual.widgets import Static
292
- if markup:
293
- widget = Static(f"[{markup}]{text}[/{markup}]")
294
- else:
295
- widget = Static(text)
159
+ widget = Static(text)
296
160
  widget.styles.height = "auto"
297
161
  self._active_step_container.mount(widget)
298
162
  else:
299
- if markup:
300
- self.output_widget.append_output(f"[{markup}]{text}[/{markup}]")
301
- else:
302
- self.output_widget.append_output(text)
163
+ self.output_widget.append_output(text)
303
164
 
304
165
  # call_from_thread already blocks until the function completes
305
166
  try:
@@ -331,8 +192,7 @@ class TextualComponents:
331
192
  # Mount to active step container if it exists, otherwise to output widget
332
193
  target = self._active_step_container if self._active_step_container else self.output_widget
333
194
  target.mount(md_widget)
334
- # Trigger autoscroll after mounting
335
- self.output_widget._scroll_to_end()
195
+ # Note: Screen handles auto-scroll when step completes, not here
336
196
 
337
197
  # call_from_thread already blocks until the function completes
338
198
  try:
@@ -341,6 +201,126 @@ class TextualComponents:
341
201
  # App is closing or worker was cancelled
342
202
  pass
343
203
 
204
+ def panel(self, text: str, panel_type: str = "info") -> None:
205
+ """
206
+ Show a panel with consistent styling.
207
+
208
+ Args:
209
+ text: Text to display in the panel
210
+ panel_type: Type of panel - "info", "success", "warning", or "error"
211
+
212
+ Example:
213
+ ctx.textual.panel("Operation completed successfully!", panel_type="success")
214
+ ctx.textual.panel("Warning: This action cannot be undone", panel_type="warning")
215
+ """
216
+ panel_widget = Panel(text=text, panel_type=panel_type)
217
+ self.mount(panel_widget)
218
+
219
+ def dim_text(self, text: str) -> None:
220
+ """
221
+ Append dim/muted text (uses theme system).
222
+
223
+ Args:
224
+ text: Text to display
225
+
226
+ Example:
227
+ ctx.textual.dim_text("Fetching versions for project: ECAPP")
228
+ """
229
+ from titan_cli.ui.tui.widgets import DimText
230
+ widget = DimText(text)
231
+ widget.styles.height = "auto"
232
+ self.mount(widget)
233
+
234
+ def success_text(self, text: str) -> None:
235
+ """
236
+ Append success text (green, uses theme system).
237
+
238
+ Args:
239
+ text: Text to display
240
+
241
+ Example:
242
+ ctx.textual.success_text("Commit created: abc1234")
243
+ """
244
+ from titan_cli.ui.tui.widgets import SuccessText
245
+ widget = SuccessText(text)
246
+ widget.styles.height = "auto"
247
+ self.mount(widget)
248
+
249
+ def error_text(self, text: str) -> None:
250
+ """
251
+ Append error text (red, uses theme system).
252
+
253
+ Args:
254
+ text: Text to display
255
+
256
+ Example:
257
+ ctx.textual.error_text("Failed to connect to API")
258
+ """
259
+ from titan_cli.ui.tui.widgets import ErrorText
260
+ widget = ErrorText(text)
261
+ widget.styles.height = "auto"
262
+ self.mount(widget)
263
+
264
+ def warning_text(self, text: str) -> None:
265
+ """
266
+ Append warning text (yellow, uses theme system).
267
+
268
+ Args:
269
+ text: Text to display
270
+
271
+ Example:
272
+ ctx.textual.warning_text("This action will overwrite existing files")
273
+ """
274
+ from titan_cli.ui.tui.widgets import WarningText
275
+ widget = WarningText(text)
276
+ widget.styles.height = "auto"
277
+ self.mount(widget)
278
+
279
+ def primary_text(self, text: str) -> None:
280
+ """
281
+ Append primary colored text (uses theme system).
282
+
283
+ Args:
284
+ text: Text to display
285
+
286
+ Example:
287
+ ctx.textual.primary_text("Processing items...")
288
+ """
289
+ from titan_cli.ui.tui.widgets import PrimaryText
290
+ widget = PrimaryText(text)
291
+ widget.styles.height = "auto"
292
+ self.mount(widget)
293
+
294
+ def bold_text(self, text: str) -> None:
295
+ """
296
+ Append bold text.
297
+
298
+ Args:
299
+ text: Text to display
300
+
301
+ Example:
302
+ ctx.textual.bold_text("Important: Read carefully")
303
+ """
304
+ from titan_cli.ui.tui.widgets import BoldText
305
+ widget = BoldText(text)
306
+ widget.styles.height = "auto"
307
+ self.mount(widget)
308
+
309
+ def bold_primary_text(self, text: str) -> None:
310
+ """
311
+ Append bold text with primary theme color.
312
+
313
+ Args:
314
+ text: Text to display
315
+
316
+ Example:
317
+ ctx.textual.bold_primary_text("AI Analysis Results")
318
+ """
319
+ from titan_cli.ui.tui.widgets import BoldPrimaryText
320
+ widget = BoldPrimaryText(text)
321
+ widget.styles.height = "auto"
322
+ self.mount(widget)
323
+
344
324
  def ask_text(self, question: str, default: str = "") -> Optional[str]:
345
325
  """
346
326
  Ask user for text input (blocks until user responds).
@@ -496,6 +476,130 @@ class TextualComponents:
496
476
  # Invalid response, use default
497
477
  return default
498
478
 
479
+ def ask_multiselect(
480
+ self,
481
+ question: str,
482
+ options: List[SelectionOption],
483
+ ) -> List[Any]:
484
+ """
485
+ Ask user to select multiple options from a list using checkboxes.
486
+
487
+ Args:
488
+ question: Question to display
489
+ options: List of SelectionOption instances
490
+
491
+ Returns:
492
+ List of selected values (the 'value' field from SelectionOption)
493
+
494
+ Example:
495
+ from titan_cli.ui.tui.widgets import SelectionOption
496
+
497
+ options = [
498
+ SelectionOption(value="option1", label="First Option", selected=True),
499
+ SelectionOption(value="option2", label="Second Option", selected=True),
500
+ SelectionOption(value="option3", label="Third Option", selected=False),
501
+ ]
502
+
503
+ selected = ctx.textual.ask_multiselect(
504
+ "Select options to include:",
505
+ options
506
+ )
507
+ # selected might be ["option1", "option2"] if user didn't change anything
508
+ """
509
+ result_container = {"result": None, "ready": threading.Event()}
510
+
511
+ def on_submit(selected_values: List[Any]):
512
+ result_container["result"] = selected_values
513
+ result_container["ready"].set()
514
+
515
+ # Create and mount the selection widget
516
+ selection_widget = PromptSelectionList(
517
+ question=question,
518
+ options=options,
519
+ on_submit=on_submit
520
+ )
521
+
522
+ self.mount(selection_widget)
523
+
524
+ # Wait for user to submit
525
+ result_container["ready"].wait()
526
+
527
+ # Remove the widget
528
+ def _remove():
529
+ try:
530
+ selection_widget.remove()
531
+ except Exception:
532
+ pass
533
+
534
+ try:
535
+ self.app.call_from_thread(_remove)
536
+ except Exception:
537
+ pass
538
+
539
+ return result_container["result"]
540
+
541
+ def ask_choice(
542
+ self,
543
+ question: str,
544
+ options: List[ChoiceOption],
545
+ ) -> Any:
546
+ """
547
+ Ask user to select one option from multiple choices using buttons.
548
+
549
+ Args:
550
+ question: Question to display
551
+ options: List of ChoiceOption instances
552
+
553
+ Returns:
554
+ The selected value (the 'value' field from ChoiceOption)
555
+
556
+ Example:
557
+ from titan_cli.ui.tui.widgets import ChoiceOption
558
+
559
+ options = [
560
+ ChoiceOption(value="use", label="Use as-is", variant="primary"),
561
+ ChoiceOption(value="edit", label="Edit", variant="default"),
562
+ ChoiceOption(value="reject", label="Reject", variant="error"),
563
+ ]
564
+
565
+ choice = ctx.textual.ask_choice(
566
+ "What would you like to do with this PR description?",
567
+ options
568
+ )
569
+ # choice might be "use", "edit", or "reject"
570
+ """
571
+ result_container = {"result": None, "ready": threading.Event()}
572
+
573
+ def on_select(selected_value: Any):
574
+ result_container["result"] = selected_value
575
+ result_container["ready"].set()
576
+
577
+ # Create and mount the choice widget
578
+ choice_widget = PromptChoice(
579
+ question=question,
580
+ options=options,
581
+ on_select=on_select
582
+ )
583
+
584
+ self.mount(choice_widget)
585
+
586
+ # Wait for user to select
587
+ result_container["ready"].wait()
588
+
589
+ # Remove the widget
590
+ def _remove():
591
+ try:
592
+ choice_widget.remove()
593
+ except Exception:
594
+ pass
595
+
596
+ try:
597
+ self.app.call_from_thread(_remove)
598
+ except Exception:
599
+ pass
600
+
601
+ return result_container["result"]
602
+
499
603
  @contextmanager
500
604
  def loading(self, message: str = "Loading..."):
501
605
  """
@@ -14,7 +14,7 @@ from titan_cli.core.workflows.workflow_registry import WorkflowRegistry
14
14
  from titan_cli.core.plugins.plugin_registry import PluginRegistry
15
15
  from titan_cli.core.workflows.models import WorkflowStepModel
16
16
  from titan_cli.engine.context import WorkflowContext
17
- from titan_cli.engine.results import WorkflowResult, Success, Error, is_error, is_skip
17
+ from titan_cli.engine.results import WorkflowResult, Success, Error, is_error, is_skip, is_exit
18
18
  from titan_cli.engine.steps.command_step import execute_command_step as execute_external_command_step
19
19
  from titan_cli.engine.steps.ai_assistant_step import execute_ai_assistant_step
20
20
 
@@ -238,7 +238,35 @@ class TextualWorkflowExecutor:
238
238
  step_result = Error(f"An unexpected error occurred in step '{step_name}': {e}", e)
239
239
 
240
240
  # Handle step result
241
- if is_error(step_result):
241
+ if is_exit(step_result):
242
+ # Exit workflow immediately (not an error)
243
+ if step_result.metadata:
244
+ ctx.data.update(step_result.metadata)
245
+
246
+ # Post completion message
247
+ self._post_message_sync(
248
+ self.StepCompleted(
249
+ step_index=step_index,
250
+ step_id=step_id,
251
+ step_name=step_name
252
+ )
253
+ )
254
+
255
+ # Calculate is_nested before exiting workflow
256
+ # (check if there are parent workflows in the stack)
257
+ is_nested = len(ctx._workflow_stack) > 1 # >1 because current workflow is still in stack
258
+
259
+ # Post workflow completed message and exit
260
+ self._post_message_sync(
261
+ self.WorkflowCompleted(
262
+ workflow_name=workflow.name,
263
+ message=step_result.message,
264
+ is_nested=is_nested
265
+ )
266
+ )
267
+ return Success(step_result.message, step_result.metadata)
268
+
269
+ elif is_error(step_result):
242
270
  self._post_message_sync(
243
271
  self.StepFailed(
244
272
  step_index=step_index,
titan_cli/ui/tui/theme.py CHANGED
@@ -17,8 +17,8 @@ $success: #50fa7b; /* Green */
17
17
  $info: #8be9fd; /* Cyan */
18
18
 
19
19
  /* Backgrounds */
20
- $surface: #282a36;
21
- $surface-lighten-1: #343746;
20
+ $surface: #282a36;
21
+ $surface-lighten-1: #343746;
22
22
  $surface-lighten-2: #44475a;
23
23
 
24
24
  /* Text Colors */
@@ -27,9 +27,9 @@ $text-muted: #6272a4; /* Comment */
27
27
  $text-disabled: #44475a; /* Disabled */
28
28
 
29
29
  /* Banner gradient colors */
30
- $banner-start: #6272a4;
31
- $banner-mid: #bd93f9;
32
- $banner-end: #ff79c6;
30
+ $banner-start: #6272a4;
31
+ $banner-mid: #bd93f9;
32
+ $banner-end: #ff79c6;
33
33
 
34
34
  /* Base widget styles */
35
35
  .title {
@@ -70,6 +70,35 @@ $banner-end: #ff79c6;
70
70
  color: $info;
71
71
  }
72
72
 
73
+ /* Text widget styles (from widgets/text.py) */
74
+ .dim, DimText, DimItalicText {
75
+ color: $text-muted;
76
+ }
77
+
78
+ .bold, BoldText, BoldPrimaryText {
79
+ text-style: bold;
80
+ }
81
+
82
+ .italic, ItalicText, DimItalicText {
83
+ text-style: italic;
84
+ }
85
+
86
+ .primary, PrimaryText, BoldPrimaryText {
87
+ color: $primary;
88
+ }
89
+
90
+ .success, SuccessText {
91
+ color: $success;
92
+ }
93
+
94
+ .error, ErrorText {
95
+ color: $error;
96
+ }
97
+
98
+ .warning, WarningText {
99
+ color: $warning;
100
+ }
101
+
73
102
  /* Global scrollbar styles - applies to all widgets */
74
103
  * {
75
104
  scrollbar-background: $surface;
@@ -9,6 +9,12 @@ from .panel import Panel
9
9
  from .table import Table
10
10
  from .button import Button
11
11
  from .step_container import StepContainer
12
+ from .multiline_input import MultilineInput
13
+ from .prompt_input import PromptInput
14
+ from .prompt_textarea import PromptTextArea
15
+ from .prompt_selection_list import PromptSelectionList, SelectionOption
16
+ from .prompt_choice import PromptChoice, ChoiceOption
17
+ from .styled_option_list import StyledOptionList, StyledOption
12
18
  from .text import (
13
19
  Text,
14
20
  DimText,
@@ -29,6 +35,15 @@ __all__ = [
29
35
  "Table",
30
36
  "Button",
31
37
  "StepContainer",
38
+ "MultilineInput",
39
+ "PromptInput",
40
+ "PromptTextArea",
41
+ "PromptSelectionList",
42
+ "SelectionOption",
43
+ "PromptChoice",
44
+ "ChoiceOption",
45
+ "StyledOptionList",
46
+ "StyledOption",
32
47
  "Text",
33
48
  "DimText",
34
49
  "BoldText",