titan-cli 0.1.4__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 (61) hide show
  1. titan_cli/core/config.py +3 -1
  2. titan_cli/core/workflows/__init__.py +2 -1
  3. titan_cli/core/workflows/project_step_source.py +95 -32
  4. titan_cli/core/workflows/workflow_filter_service.py +16 -8
  5. titan_cli/core/workflows/workflow_registry.py +12 -1
  6. titan_cli/core/workflows/workflow_sources.py +1 -1
  7. titan_cli/engine/__init__.py +5 -1
  8. titan_cli/engine/results.py +31 -1
  9. titan_cli/engine/steps/ai_assistant_step.py +47 -12
  10. titan_cli/engine/workflow_executor.py +13 -3
  11. titan_cli/ui/tui/screens/plugin_config_wizard.py +16 -0
  12. titan_cli/ui/tui/screens/workflow_execution.py +28 -50
  13. titan_cli/ui/tui/screens/workflows.py +8 -4
  14. titan_cli/ui/tui/textual_components.py +342 -185
  15. titan_cli/ui/tui/textual_workflow_executor.py +39 -3
  16. titan_cli/ui/tui/theme.py +34 -5
  17. titan_cli/ui/tui/widgets/__init__.py +17 -0
  18. titan_cli/ui/tui/widgets/multiline_input.py +32 -0
  19. titan_cli/ui/tui/widgets/prompt_choice.py +138 -0
  20. titan_cli/ui/tui/widgets/prompt_input.py +74 -0
  21. titan_cli/ui/tui/widgets/prompt_selection_list.py +150 -0
  22. titan_cli/ui/tui/widgets/prompt_textarea.py +87 -0
  23. titan_cli/ui/tui/widgets/step_container.py +70 -0
  24. titan_cli/ui/tui/widgets/styled_option_list.py +107 -0
  25. titan_cli/ui/tui/widgets/text.py +51 -130
  26. {titan_cli-0.1.4.dist-info → titan_cli-0.1.6.dist-info}/METADATA +3 -5
  27. {titan_cli-0.1.4.dist-info → titan_cli-0.1.6.dist-info}/RECORD +61 -46
  28. titan_plugin_git/clients/git_client.py +140 -5
  29. titan_plugin_git/plugin.py +13 -0
  30. titan_plugin_git/steps/ai_commit_message_step.py +39 -34
  31. titan_plugin_git/steps/branch_steps.py +18 -37
  32. titan_plugin_git/steps/checkout_step.py +66 -0
  33. titan_plugin_git/steps/commit_step.py +18 -22
  34. titan_plugin_git/steps/create_branch_step.py +131 -0
  35. titan_plugin_git/steps/diff_summary_step.py +180 -0
  36. titan_plugin_git/steps/pull_step.py +70 -0
  37. titan_plugin_git/steps/push_step.py +27 -11
  38. titan_plugin_git/steps/restore_original_branch_step.py +97 -0
  39. titan_plugin_git/steps/save_current_branch_step.py +82 -0
  40. titan_plugin_git/steps/status_step.py +32 -25
  41. titan_plugin_git/workflows/commit-ai.yaml +9 -3
  42. titan_plugin_github/agents/pr_agent.py +15 -2
  43. titan_plugin_github/steps/ai_pr_step.py +99 -40
  44. titan_plugin_github/steps/create_pr_step.py +18 -8
  45. titan_plugin_github/steps/github_prompt_steps.py +53 -1
  46. titan_plugin_github/steps/issue_steps.py +31 -18
  47. titan_plugin_github/steps/preview_step.py +15 -4
  48. titan_plugin_github/utils.py +5 -4
  49. titan_plugin_github/workflows/create-pr-ai.yaml +6 -11
  50. titan_plugin_jira/messages.py +12 -0
  51. titan_plugin_jira/plugin.py +4 -0
  52. titan_plugin_jira/steps/ai_analyze_issue_step.py +12 -7
  53. titan_plugin_jira/steps/get_issue_step.py +17 -13
  54. titan_plugin_jira/steps/list_versions_step.py +133 -0
  55. titan_plugin_jira/steps/prompt_select_issue_step.py +20 -8
  56. titan_plugin_jira/steps/search_jql_step.py +191 -0
  57. titan_plugin_jira/steps/search_saved_query_step.py +26 -24
  58. titan_plugin_jira/utils/__init__.py +1 -1
  59. {titan_cli-0.1.4.dist-info → titan_cli-0.1.6.dist-info}/LICENSE +0 -0
  60. {titan_cli-0.1.4.dist-info → titan_cli-0.1.6.dist-info}/WHEEL +0 -0
  61. {titan_cli-0.1.4.dist-info → titan_cli-0.1.6.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,191 @@
1
+ """
2
+ Search JIRA issues using custom JQL query
3
+ """
4
+
5
+ from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
6
+ from titan_cli.ui.tui.widgets import Table
7
+ from ..exceptions import JiraAPIError
8
+ from ..messages import msg
9
+ from ..utils import IssueSorter
10
+
11
+
12
+ def search_jql_step(ctx: WorkflowContext) -> WorkflowResult:
13
+ """
14
+ Search JIRA issues using a custom JQL query.
15
+
16
+ This is a generic search step that accepts raw JQL and allows variable substitution.
17
+ Use this when you need a specific query that isn't covered by saved queries.
18
+
19
+ Inputs (from ctx.data):
20
+ jql (str): JQL query string (supports variable substitution with ${var_name})
21
+ max_results (int, optional): Maximum number of results (default: 100)
22
+
23
+ Outputs (saved to ctx.data):
24
+ jira_issues (list): List of JiraTicket objects
25
+ jira_issue_count (int): Number of issues found
26
+
27
+ Returns:
28
+ Success: Issues found
29
+ Error: Search failed or JQL not provided
30
+
31
+ Variable substitution:
32
+ You can use ${variable_name} in the JQL and it will be replaced with values from ctx.data.
33
+ Example: "project = ${project_key} AND fixVersion = ${fix_version}"
34
+
35
+ Example usage in workflow:
36
+ ```yaml
37
+ - id: search_issues
38
+ plugin: jira
39
+ step: search_jql
40
+ params:
41
+ jql: "project = MYPROJECT AND status = 'In Progress' ORDER BY created DESC"
42
+ max_results: 50
43
+
44
+ # With variable substitution:
45
+ - id: search_release
46
+ plugin: jira
47
+ step: search_jql
48
+ params:
49
+ jql: "project = ${project_key} AND fixVersion = ${fix_version} ORDER BY created DESC"
50
+ ```
51
+ """
52
+ if not ctx.textual:
53
+ return Error("Textual UI context is not available for this step.")
54
+
55
+ # Begin step container
56
+ ctx.textual.begin_step("Search JIRA Issues")
57
+
58
+ if not ctx.jira:
59
+ ctx.textual.error_text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
60
+ ctx.textual.end_step("error")
61
+ return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
62
+
63
+ # Get JQL query
64
+ jql = ctx.get("jql")
65
+ if not jql:
66
+ error_msg = "JQL query is required but not provided"
67
+ ctx.textual.error_text(error_msg)
68
+ ctx.textual.dim_text("Provide 'jql' parameter in workflow step")
69
+ ctx.textual.end_step("error")
70
+ return Error(error_msg)
71
+
72
+ # Perform variable substitution: replace ${var} with ctx.data values
73
+ # This allows dynamic queries like "project = ${project_key}"
74
+ import re
75
+
76
+ def replace_var(match):
77
+ var_name = match.group(1)
78
+ value = ctx.get(var_name)
79
+ if value is None:
80
+ # Variable not found in context, keep original placeholder
81
+ return match.group(0)
82
+ return str(value)
83
+
84
+ # Replace ${variable} patterns
85
+ jql = re.sub(r'\$\{([^}]+)\}', replace_var, jql)
86
+
87
+ # Show which query is being executed
88
+ ctx.textual.text("")
89
+ ctx.textual.bold_text("Executing JQL Query:")
90
+ ctx.textual.dim_text(f" {jql}")
91
+ ctx.textual.text("")
92
+
93
+ # Get max results
94
+ max_results = ctx.get("max_results", 100)
95
+
96
+ try:
97
+ # Execute search with loading indicator
98
+ # Request ALL fields including custom fields
99
+ with ctx.textual.loading("Searching JIRA issues..."):
100
+ issues = ctx.jira.search_tickets(jql=jql, max_results=max_results, fields=["*all"])
101
+
102
+ if not issues:
103
+ ctx.textual.dim_text("No issues found")
104
+ ctx.textual.end_step("success")
105
+ return Success(
106
+ "No issues found",
107
+ metadata={
108
+ "jira_issues": [],
109
+ "issues": [], # Alias for compatibility
110
+ "jira_issue_count": 0
111
+ }
112
+ )
113
+
114
+ # Show results
115
+ ctx.textual.text("") # spacing
116
+ ctx.textual.success_text(f"Found {len(issues)} issues")
117
+ ctx.textual.text("")
118
+
119
+ # Show detailed table
120
+ ctx.textual.bold_text("Found Issues:")
121
+ ctx.textual.text("")
122
+
123
+ try:
124
+ # Sort issues intelligently
125
+ sorter = IssueSorter()
126
+ sorted_issues = sorter.sort(issues)
127
+
128
+ # Prepare table data with row numbers for selection
129
+ headers = ["#", "Key", "Status", "Summary", "Assignee", "Type", "Priority"]
130
+ rows = []
131
+ for i, issue in enumerate(sorted_issues, 1):
132
+ assignee = issue.assignee or "Unassigned"
133
+ status = issue.status or "Unknown"
134
+ priority = issue.priority or "Unknown"
135
+ issue_type = issue.issue_type or "Unknown"
136
+ summary = (issue.summary or "No summary")[:60]
137
+
138
+ rows.append([
139
+ str(i),
140
+ issue.key,
141
+ status,
142
+ summary,
143
+ assignee,
144
+ issue_type,
145
+ priority
146
+ ])
147
+
148
+ # Render table using textual widget
149
+ ctx.textual.mount(
150
+ Table(
151
+ headers=headers,
152
+ rows=rows,
153
+ title=f"Issues (sorted by {sorter.get_sort_description()})"
154
+ )
155
+ )
156
+
157
+ # Use sorted issues for downstream steps
158
+ issues = sorted_issues
159
+ except Exception as e:
160
+ # If table rendering fails, show error but continue with raw issue list
161
+ ctx.textual.error_text(f"Error rendering table: {e}")
162
+ ctx.textual.primary_text(f"Found {len(issues)} issues (showing raw data)")
163
+ for i, issue in enumerate(issues, 1):
164
+ ctx.textual.text(f"{i}. {issue.key} - {getattr(issue, 'summary', 'N/A')}")
165
+ ctx.textual.text("")
166
+
167
+ ctx.textual.end_step("success")
168
+ return Success(
169
+ f"Found {len(issues)} issues",
170
+ metadata={
171
+ "jira_issues": issues,
172
+ "issues": issues, # Alias for compatibility
173
+ "jira_issue_count": len(issues)
174
+ }
175
+ )
176
+
177
+ except JiraAPIError as e:
178
+ error_msg = f"JIRA search failed: {e}"
179
+ ctx.textual.error_text(error_msg)
180
+ ctx.textual.end_step("error")
181
+ return Error(error_msg)
182
+ except Exception as e:
183
+ import traceback
184
+ error_detail = traceback.format_exc()
185
+ error_msg = f"Unexpected error: {e}\n\nTraceback:\n{error_detail}"
186
+ ctx.textual.error_text(error_msg)
187
+ ctx.textual.end_step("error")
188
+ return Error(error_msg)
189
+
190
+
191
+ __all__ = ["search_jql_step"]
@@ -3,7 +3,7 @@ Search JIRA issues using saved query from utils registry
3
3
  """
4
4
 
5
5
  from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
6
- from titan_cli.ui.tui.widgets import Panel, Table
6
+ from titan_cli.ui.tui.widgets import Table
7
7
  from ..exceptions import JiraAPIError
8
8
  from ..messages import msg
9
9
  from ..utils import SAVED_QUERIES, IssueSorter
@@ -57,14 +57,19 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
57
57
  if not ctx.textual:
58
58
  return Error("Textual UI context is not available for this step.")
59
59
 
60
+ # Begin step container
61
+ ctx.textual.begin_step("Search Open Issues")
62
+
60
63
  if not ctx.jira:
61
- ctx.textual.mount(Panel(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, panel_type="error"))
64
+ ctx.textual.error_text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
65
+ ctx.textual.end_step("error")
62
66
  return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
63
67
 
64
68
  # Get query name
65
69
  query_name = ctx.get("query_name")
66
70
  if not query_name:
67
- ctx.textual.mount(Panel(msg.Steps.Search.QUERY_NAME_REQUIRED, panel_type="error"))
71
+ ctx.textual.error_text(msg.Steps.Search.QUERY_NAME_REQUIRED)
72
+ ctx.textual.end_step("error")
68
73
  return Error(msg.Steps.Search.QUERY_NAME_REQUIRED)
69
74
 
70
75
  # Get all predefined queries from utils
@@ -102,7 +107,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
102
107
  error_msg += "\n\n" + msg.Steps.Search.ADD_CUSTOM_HINT + "\n"
103
108
  error_msg += msg.Steps.Search.CUSTOM_QUERY_EXAMPLE
104
109
 
105
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
110
+ ctx.textual.error_text(error_msg)
111
+ ctx.textual.end_step("error")
106
112
  return Error(error_msg)
107
113
 
108
114
  jql = all_queries[query_name]
@@ -119,7 +125,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
119
125
 
120
126
  if not project:
121
127
  error_msg = msg.Steps.Search.PROJECT_REQUIRED.format(query_name=query_name, jql=jql)
122
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
128
+ ctx.textual.error_text(error_msg)
129
+ ctx.textual.end_step("error")
123
130
  return Error(error_msg)
124
131
 
125
132
  jql = jql.format(project=project)
@@ -129,8 +136,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
129
136
  source_label = "Custom" if is_custom else "Predefined"
130
137
 
131
138
  ctx.textual.text("")
132
- ctx.textual.text(f"Using {source_label} Query: {query_name}", markup="bold")
133
- ctx.textual.text(f" JQL: {jql}", markup="dim")
139
+ ctx.textual.bold_text(f"Using {source_label} Query: {query_name}")
140
+ ctx.textual.dim_text(f" JQL: {jql}")
134
141
  ctx.textual.text("")
135
142
 
136
143
  # Get max results
@@ -142,12 +149,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
142
149
  issues = ctx.jira.search_tickets(jql=jql, max_results=max_results)
143
150
 
144
151
  if not issues:
145
- ctx.textual.mount(
146
- Panel(
147
- text=f"No issues found for query: {query_name}",
148
- panel_type="info"
149
- )
150
- )
152
+ ctx.textual.dim_text(f"No issues found for query: {query_name}")
153
+ ctx.textual.end_step("success")
151
154
  return Success(
152
155
  "No issues found",
153
156
  metadata={
@@ -158,16 +161,12 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
158
161
  )
159
162
 
160
163
  # Show results
161
- ctx.textual.mount(
162
- Panel(
163
- text=f"Found {len(issues)} issues",
164
- panel_type="success"
165
- )
166
- )
164
+ ctx.textual.text("") # spacing
165
+ ctx.textual.success_text(f"Found {len(issues)} issues")
167
166
  ctx.textual.text("")
168
167
 
169
168
  # Show detailed table
170
- ctx.textual.text("Found Issues:", markup="bold")
169
+ ctx.textual.bold_text("Found Issues:")
171
170
  ctx.textual.text("")
172
171
 
173
172
  try:
@@ -208,12 +207,13 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
208
207
  issues = sorted_issues
209
208
  except Exception as e:
210
209
  # If table rendering fails, show error but continue with raw issue list
211
- ctx.textual.text(f"Error rendering table: {e}", markup="red")
212
- ctx.textual.text(f"Found {len(issues)} issues (showing raw data)", markup="cyan")
210
+ ctx.textual.error_text(f"Error rendering table: {e}")
211
+ ctx.textual.primary_text(f"Found {len(issues)} issues (showing raw data)")
213
212
  for i, issue in enumerate(issues, 1):
214
213
  ctx.textual.text(f"{i}. {issue.key} - {getattr(issue, 'summary', 'N/A')}")
215
214
  ctx.textual.text("")
216
215
 
216
+ ctx.textual.end_step("success")
217
217
  return Success(
218
218
  f"Found {len(issues)} issues using query: {query_name}",
219
219
  metadata={
@@ -225,13 +225,15 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
225
225
 
226
226
  except JiraAPIError as e:
227
227
  error_msg = f"JIRA search failed: {e}"
228
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
228
+ ctx.textual.error_text(error_msg)
229
+ ctx.textual.end_step("error")
229
230
  return Error(error_msg)
230
231
  except Exception as e:
231
232
  import traceback
232
233
  error_detail = traceback.format_exc()
233
234
  error_msg = f"Unexpected error: {e}\n\nTraceback:\n{error_detail}"
234
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
235
+ ctx.textual.error_text(error_msg)
236
+ ctx.textual.end_step("error")
235
237
  return Error(error_msg)
236
238
 
237
239
 
@@ -9,5 +9,5 @@ __all__ = [
9
9
  "SavedQueries",
10
10
  "SAVED_QUERIES",
11
11
  "IssueSorter",
12
- "IssueSortConfig"
12
+ "IssueSortConfig",
13
13
  ]