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
File without changes
@@ -0,0 +1,85 @@
1
+ # PR Agent Configuration
2
+ # AI agent for analyzing code changes and generating PR descriptions
3
+
4
+ [agent]
5
+ name = "PRAgent"
6
+ description = "AI agent specialized in PR analysis and generation"
7
+ version = "1.0.0"
8
+
9
+ [agent.capabilities]
10
+ # What this agent can do
11
+ code_analysis = true
12
+ pr_generation = true
13
+ commit_analysis = true
14
+ architecture_review = true
15
+ template_following = true
16
+
17
+ [agent.prompts]
18
+ # System prompts for different tasks
19
+
20
+ [agent.prompts.pr_description]
21
+ system = """You are an expert software engineer specialized in code review and documentation.
22
+ Your task is to analyze git changes and generate professional pull request descriptions.
23
+
24
+ Guidelines:
25
+ 1. Be concise but comprehensive
26
+ 2. Follow conventional commit formats for titles
27
+ 3. Use markdown formatting
28
+ 4. Highlight breaking changes
29
+ 5. Focus on WHY, not just WHAT
30
+ 6. Follow PR templates strictly when provided
31
+ 7. Adapt detail level based on PR size (small/medium/large/very large)
32
+
33
+ Response format:
34
+ TITLE: <conventional commit title max 72 chars>
35
+
36
+ DESCRIPTION:
37
+ <structured description following template or standard format>
38
+ """
39
+
40
+ [agent.prompts.commit_message]
41
+ system = """You are an expert software engineer specialized in writing clear commit messages.
42
+ Your task is to analyze code changes and generate atomic commit messages.
43
+
44
+ Guidelines:
45
+ 1. Follow conventional commits format: type(scope): description
46
+ 2. Be specific and descriptive
47
+ 3. Focus on WHY the change was made
48
+ 4. Keep title under 72 characters
49
+ 5. Group related changes logically
50
+
51
+ Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build
52
+ """
53
+
54
+ [agent.prompts.architecture_review]
55
+ system = """You are a senior software architect reviewing code changes.
56
+ Your task is to identify architectural patterns, potential issues, and improvements.
57
+
58
+ Guidelines:
59
+ 1. Identify design patterns used
60
+ 2. Highlight potential issues (coupling, complexity, etc.)
61
+ 3. Suggest architectural improvements
62
+ 4. Consider scalability and maintainability
63
+ 5. Be constructive and actionable
64
+ """
65
+
66
+ [agent.limits]
67
+ # Diff analysis limits
68
+ max_diff_size = 8000 # Characters
69
+ max_files_in_diff = 50
70
+ max_commits_to_analyze = 15
71
+
72
+ [agent.features]
73
+ # Feature flags
74
+ enable_template_detection = true
75
+ enable_dynamic_sizing = true
76
+ enable_user_confirmation = true
77
+ enable_fallback_prompts = true
78
+ enable_debug_output = false # Set to true for debugging
79
+
80
+ [agent.metadata]
81
+ # Agent metadata
82
+ author = "Titan CLI Team"
83
+ created = "2024-12-11"
84
+ updated = "2024-12-11"
85
+ tags = ["pr-generation", "code-analysis", "git", "ai", "github"]
@@ -0,0 +1,28 @@
1
+ # plugins/titan-plugin-github/titan_plugin_github/exceptions.py
2
+ class GitHubError(Exception):
3
+ """Base exception for all GitHub related errors."""
4
+ pass
5
+
6
+ class GitHubAuthenticationError(GitHubError):
7
+ """Raised when GitHub authentication fails (e.g., gh CLI not authenticated)."""
8
+ pass
9
+
10
+ class GitHubAPIError(GitHubError):
11
+ """Raised when a GitHub API call or gh CLI command fails."""
12
+ pass
13
+
14
+ class PRNotFoundError(GitHubAPIError):
15
+ """Raised when a specified Pull Request is not found."""
16
+ pass
17
+
18
+ class ReviewNotFoundError(GitHubAPIError):
19
+ """Raised when a specified Review is not found."""
20
+ pass
21
+
22
+ class GitHubPermissionError(GitHubAPIError):
23
+ """Raised when the authenticated user does not have sufficient permissions."""
24
+ pass
25
+
26
+ class GitHubConfigurationError(GitHubError):
27
+ """Raised when the GitHub plugin is misconfigured."""
28
+ pass
@@ -0,0 +1,88 @@
1
+ # plugins/titan-plugin-github/titan_plugin_github/messages.py
2
+ class Messages:
3
+ class Prompts:
4
+ """Prompts specific to the GitHub plugin"""
5
+ ENTER_PR_TITLE: str = "Enter Pull Request title:"
6
+ ENTER_PR_BODY: str = "Enter PR body/description (press Meta+Enter or Esc then Enter to finish):"
7
+ ENTER_ISSUE_BODY: str = "Enter issue body/description (press Meta+Enter or Esc then Enter to finish):"
8
+ ASSIGN_TO_SELF: str = "Assign this issue to yourself?"
9
+ SELECT_LABELS: str = "Select labels for this issue:"
10
+ ENTER_PR_BODY_INFO: str = "Enter a description for your pull request. When you are finished, press Meta+Enter (or Esc followed by Enter)."
11
+
12
+ class GitHub:
13
+ """GitHub operations messages"""
14
+ # Client errors
15
+ CLI_NOT_FOUND: str = "GitHub CLI ('gh') not found. Please install it and ensure it's in your PATH."
16
+ NOT_AUTHENTICATED: str = "GitHub CLI is not authenticated. Run: gh auth login"
17
+ CONFIG_REPO_MISSING: str = "GitHub repository owner and name must be configured in [plugins.github.config]."
18
+ API_ERROR: str = "GitHub API error: {error_msg}"
19
+ PERMISSION_ERROR: str = "Permission denied for GitHub operation: {error_msg}"
20
+ UNEXPECTED_ERROR: str = "An unexpected GitHub error occurred: {error}"
21
+
22
+ # Pull Requests
23
+ PR_NOT_FOUND: str = "Pull Request #{pr_number} not found."
24
+ PR_CREATING: str = "Creating pull request..."
25
+ PR_CREATED: str = "PR #{number} created: {url}"
26
+ PR_UPDATED: str = "PR #{number} updated"
27
+ PR_MERGED: str = "PR #{number} merged"
28
+ PR_CLOSED: str = "PR #{number} closed"
29
+ PR_FAILED: str = "Failed to create PR: {error}"
30
+ PR_CREATION_FAILED: str = "Failed to create pull request: {error}"
31
+ FAILED_TO_PARSE_PR_NUMBER: str = "Failed to parse PR number from URL: {url}"
32
+
33
+ # Merge
34
+ INVALID_MERGE_METHOD: str = "Invalid merge method: {method}. Must be one of: {valid_methods}"
35
+
36
+ # Reviews
37
+ REVIEW_NOT_FOUND: str = "Review ID #{review_id} for Pull Request #{pr_number} not found."
38
+ REVIEW_CREATING: str = "Creating review..."
39
+ REVIEW_CREATED: str = "Review submitted"
40
+ REVIEW_FAILED: str = "Failed to submit review: {error}"
41
+
42
+ # Comments
43
+ COMMENT_CREATING: str = "Adding comment..."
44
+ COMMENT_CREATED: str = "Comment added"
45
+ COMMENT_FAILED: str = "Failed to add comment: {error}"
46
+
47
+ # Repository
48
+ REPO_NOT_FOUND: str = "Repository not found"
49
+ REPO_ACCESS_DENIED: str = "Access denied to repository"
50
+
51
+ # Authentication
52
+ AUTH_MISSING: str = "GitHub token not found. Set GITHUB_TOKEN environment variable."
53
+ AUTH_INVALID: str = "Invalid GitHub token"
54
+
55
+ class AI:
56
+ AI_NOT_CONFIGURED: str = "AI not configured. Run 'titan ai configure' to enable AI features."
57
+ GITHUB_CLIENT_NOT_AVAILABLE: str = "GitHub client is not available in the workflow context."
58
+ GIT_CLIENT_NOT_AVAILABLE: str = "Git client is not available in the workflow context."
59
+ MISSING_PR_HEAD_BRANCH: str = "Missing pr_head_branch in context"
60
+ ANALYZING_BRANCH_DIFF: str = "Analyzing branch diff: {head_branch} vs {base_branch}..."
61
+ FAILED_TO_GET_BRANCH_DIFF: str = "Failed to get branch diff: {e}"
62
+ NO_CHANGES_FOUND: str = "No changes found between branches"
63
+ COMMITS_TRUNCATED: str = "\n ... and {count} more commits"
64
+ NO_DIFF_AVAILABLE: str = "No diff available"
65
+ DIFF_TRUNCATED: str = "\n\n... (diff truncated for brevity)"
66
+ PR_SIZE_INFO: str = "PR Size: {pr_size} ({files_changed} files, {diff_lines} lines) → Max description: {max_chars} chars"
67
+ FAILED_TO_READ_PR_TEMPLATE: str = "Failed to read PR template: {e}"
68
+ GENERATING_PR_DESCRIPTION: str = "Generating PR description with AI..."
69
+ AI_RESPONSE_FORMAT_INCORRECT: str = "AI response format incorrect. Expected 'TITLE:' and 'DESCRIPTION:' sections.\nGot: {response_preview}..."
70
+ AI_GENERATED_TRUNCATING: str = "AI generated {actual_len} chars, truncating to {max_chars}"
71
+ AI_GENERATED_EMPTY_SHORT: str = "AI generated an empty or very short description."
72
+ FULL_AI_RESPONSE: str = "Full AI response:"
73
+ AI_GENERATED_INCOMPLETE: str = "AI generated an empty or incomplete PR description"
74
+ AI_GENERATED_COMMIT_MESSAGE: str = "AI Generated Commit Message"
75
+ COMMIT_MESSAGE_LABEL: str = "Commit Message:"
76
+ CONFIRM_USE_AI_COMMIT: str = "Use this AI-generated commit message?"
77
+ AI_COMMIT_REJECTED: str = "AI-generated commit message rejected"
78
+ AI_GENERATED_PR_TITLE: str = "AI Generated PR:"
79
+ TITLE_LABEL: str = "Title:"
80
+ TITLE_TOO_LONG_WARNING: str = "Title is {length} chars (recommended: ≤72)"
81
+ DESCRIPTION_LABEL: str = "Description:"
82
+ CONFIRM_USE_AI_PR: str = "Use this AI-generated PR?"
83
+ AI_SUGGESTION_REJECTED: str = "AI suggestion rejected. Will prompt for manual input."
84
+ AI_GENERATED_PR_DESCRIPTION_SUCCESS: str = "AI generated PR description"
85
+ AI_GENERATION_FAILED: str = "AI generation failed: {e}"
86
+ FALLBACK_TO_MANUAL: str = "Falling back to manual PR creation..."
87
+
88
+ msg = Messages()
@@ -0,0 +1,330 @@
1
+ # plugins/titan-plugin-github/titan_plugin_github/models.py
2
+ from dataclasses import dataclass, field
3
+ from typing import List, Optional, Dict, Any
4
+
5
+ # Import PRSizeEstimation from utils
6
+
7
+
8
+ @dataclass
9
+ class User:
10
+ """GitHub user representation"""
11
+ login: str
12
+ name: Optional[str] = None
13
+ email: Optional[str] = None
14
+ avatar_url: Optional[str] = None
15
+
16
+ @classmethod
17
+ def from_dict(cls, data: dict) -> 'User':
18
+ """
19
+ Create User from API response
20
+
21
+ Args:
22
+ data: User data from GitHub API
23
+
24
+ Returns:
25
+ User instance
26
+
27
+ Examples:
28
+ >>> data = {"login": "john", "name": "John Doe"}
29
+ >>> user = User.from_dict(data)
30
+ """
31
+ if not data:
32
+ return cls(login="unknown")
33
+
34
+ return cls(
35
+ login=data.get("login", "unknown"),
36
+ name=data.get("name"),
37
+ email=data.get("email"),
38
+ avatar_url=data.get("avatar_url")
39
+ )
40
+
41
+
42
+ @dataclass
43
+ class ReviewComment:
44
+ """GitHub PR review comment"""
45
+ id: int
46
+ path: str
47
+ line: int
48
+ body: str
49
+ user: User
50
+ created_at: str
51
+ side: str = "RIGHT" # RIGHT or LEFT
52
+
53
+ @classmethod
54
+ def from_dict(cls, data: dict) -> 'ReviewComment':
55
+ """Create ReviewComment from API response"""
56
+ return cls(
57
+ id=data.get("id", 0),
58
+ path=data.get("path", ""),
59
+ line=data.get("line", 0),
60
+ body=data.get("body", ""),
61
+ user=User.from_dict(data.get("user", {})),
62
+ created_at=data.get("created_at", ""),
63
+ side=data.get("side", "RIGHT")
64
+ )
65
+
66
+
67
+ @dataclass
68
+ class Review:
69
+ """GitHub PR review"""
70
+ id: int
71
+ user: User
72
+ body: str
73
+ state: str # PENDING, APPROVED, CHANGES_REQUESTED, COMMENTED
74
+ submitted_at: Optional[str] = None
75
+ commit_id: Optional[str] = None
76
+
77
+ @classmethod
78
+ def from_dict(cls, data: dict) -> 'Review':
79
+ """Create Review from API response"""
80
+ return cls(
81
+ id=data.get("id", 0),
82
+ user=User.from_dict(data.get("user", {})),
83
+ body=data.get("body", ""),
84
+ state=data.get("state", "PENDING"),
85
+ submitted_at=data.get("submitted_at"),
86
+ commit_id=data.get("commit_id")
87
+ )
88
+
89
+
90
+ @dataclass
91
+ class PullRequest:
92
+ """
93
+ GitHub Pull Request representation
94
+
95
+ Attributes:
96
+ number: PR number
97
+ title: PR title
98
+ body: PR description
99
+ state: open, closed, merged
100
+ author: PR author
101
+ base_ref: Base branch (e.g., develop)
102
+ head_ref: Head branch (e.g., feature/xyz)
103
+ additions: Lines added
104
+ deletions: Lines deleted
105
+ changed_files: Number of files changed
106
+ mergeable: Can be merged
107
+ draft: Is draft PR
108
+ created_at: ISO date string
109
+ updated_at: ISO date string
110
+ merged_at: ISO date string (if merged)
111
+ reviews: List of reviews
112
+ labels: List of label names
113
+ """
114
+ number: int
115
+ title: str
116
+ body: str
117
+ state: str
118
+ author: User
119
+ base_ref: str
120
+ head_ref: str
121
+ additions: int = 0
122
+ deletions: int = 0
123
+ changed_files: int = 0
124
+ mergeable: bool = True
125
+ draft: bool = False
126
+ created_at: Optional[str] = None
127
+ updated_at: Optional[str] = None
128
+ merged_at: Optional[str] = None
129
+ reviews: List[Review] = field(default_factory=list)
130
+ labels: List[str] = field(default_factory=list)
131
+
132
+ @classmethod
133
+ def from_dict(cls, data: dict) -> 'PullRequest':
134
+ """
135
+ Create PullRequest from GitHub API response
136
+
137
+ Args:
138
+ data: PR data from GitHub API (gh pr view --json format)
139
+
140
+ Returns:
141
+ PullRequest instance
142
+
143
+ Examples:
144
+ >>> data = gh_api_response
145
+ >>> pr = PullRequest.from_dict(data)
146
+ """
147
+ # Parse author
148
+ author_data = data.get("author", {})
149
+ author = User.from_dict(author_data)
150
+
151
+ # Parse reviews
152
+ reviews_data = data.get("reviews", [])
153
+ reviews = [Review.from_dict(r) for r in reviews_data]
154
+
155
+ # Parse labels
156
+ labels_data = data.get("labels", [])
157
+ labels = [label.get("name", "") for label in labels_data]
158
+
159
+ return cls(
160
+ number=data.get("number", 0),
161
+ title=data.get("title", ""),
162
+ body=data.get("body", ""),
163
+ state=data.get("state", "OPEN"),
164
+ author=author,
165
+ base_ref=data.get("baseRefName", ""),
166
+ head_ref=data.get("headRefName", ""),
167
+ additions=data.get("additions", 0),
168
+ deletions=data.get("deletions", 0),
169
+ changed_files=data.get("changedFiles", 0),
170
+ mergeable=data.get("mergeable", "MERGEABLE") == "MERGEABLE",
171
+ draft=data.get("isDraft", False),
172
+ created_at=data.get("createdAt"),
173
+ updated_at=data.get("updatedAt"),
174
+ merged_at=data.get("mergedAt"),
175
+ reviews=reviews,
176
+ labels=labels
177
+ )
178
+
179
+ def get_status_emoji(self) -> str:
180
+ """Get emoji for PR state"""
181
+ if self.state == "MERGED":
182
+ return "🟣"
183
+ elif self.state == "CLOSED":
184
+ return "🔴"
185
+ elif self.draft:
186
+ return "📝"
187
+ elif self.state == "OPEN":
188
+ return "🟢"
189
+ return "⚪"
190
+
191
+ def get_review_status(self) -> str:
192
+ """Get review status summary"""
193
+ if not self.reviews:
194
+ return "No reviews"
195
+
196
+ approved = sum(1 for r in self.reviews if r.state == "APPROVED")
197
+ changes = sum(1 for r in self.reviews if r.state == "CHANGES_REQUESTED")
198
+
199
+ if approved > 0 and changes == 0:
200
+ return f"✅ {approved} approved"
201
+ elif changes > 0:
202
+ return f"❌ {changes} changes requested"
203
+ else:
204
+ return f"💬 {len(self.reviews)} comments"
205
+
206
+
207
+ @dataclass
208
+ class PRSearchResult:
209
+ """Result of searching pull requests"""
210
+ prs: List[PullRequest]
211
+ total: int
212
+
213
+ @classmethod
214
+ def from_list(cls, data: List[dict]) -> 'PRSearchResult':
215
+ """
216
+ Create PRSearchResult from list of PR data
217
+
218
+ Args:
219
+ data: List of PR dictionaries from GitHub API
220
+
221
+ Returns:
222
+ PRSearchResult instance
223
+ """
224
+ prs = [PullRequest.from_dict(pr_data) for pr_data in data]
225
+ return cls(prs=prs, total=len(prs))
226
+
227
+
228
+ @dataclass
229
+ class PRComment:
230
+ """
231
+ Pull request comment (review comment or issue comment)
232
+
233
+ Attributes:
234
+ id: Comment ID
235
+ body: Comment text
236
+ user: User who created the comment
237
+ created_at: Creation timestamp
238
+ path: File path (for review comments)
239
+ line: Line number (for review comments)
240
+ diff_hunk: Diff context (for review comments)
241
+ pull_request_review_id: Review ID (for review comments)
242
+ in_reply_to_id: ID of parent comment (if it's a reply)
243
+ is_review_comment: True if inline review comment, False if issue comment
244
+ """
245
+ id: int
246
+ body: str
247
+ user: User
248
+ created_at: str
249
+ path: Optional[str] = None
250
+ line: Optional[int] = None
251
+ diff_hunk: Optional[str] = None
252
+ pull_request_review_id: Optional[int] = None
253
+ in_reply_to_id: Optional[int] = None
254
+ is_review_comment: bool = True
255
+
256
+ @classmethod
257
+ def from_dict(cls, data: Dict[str, Any], is_review: bool = True) -> 'PRComment':
258
+ """Create from API response"""
259
+ user_data = data.get("user", {})
260
+ user = User(
261
+ login=user_data.get("login", ""),
262
+ name=user_data.get("name"),
263
+ email=user_data.get("email"),
264
+ avatar_url=user_data.get("avatar_url")
265
+ )
266
+
267
+ return cls(
268
+ id=data.get("id", 0),
269
+ body=data.get("body", ""),
270
+ user=user,
271
+ created_at=data.get("created_at", ""),
272
+ path=data.get("path"),
273
+ line=data.get("line"),
274
+ diff_hunk=data.get("diff_hunk"),
275
+ pull_request_review_id=data.get("pull_request_review_id"),
276
+ in_reply_to_id=data.get("in_reply_to_id"),
277
+ is_review_comment=is_review
278
+ )
279
+
280
+
281
+ @dataclass
282
+ class PRMergeResult:
283
+ """
284
+ Result of merging a pull request
285
+
286
+ Attributes:
287
+ merged: Whether the PR was successfully merged
288
+ sha: Commit SHA of the merge (if successful)
289
+ message: Success or error message
290
+ """
291
+ merged: bool
292
+ sha: Optional[str] = None
293
+ message: str = ""
294
+
295
+
296
+ @dataclass
297
+ class Issue:
298
+ """
299
+ GitHub Issue representation.
300
+ """
301
+ number: int
302
+ title: str
303
+ body: str
304
+ state: str
305
+ author: User
306
+ labels: List[str] = field(default_factory=list)
307
+ created_at: Optional[str] = None
308
+ updated_at: Optional[str] = None
309
+
310
+ @classmethod
311
+ def from_dict(cls, data: dict) -> 'Issue':
312
+ """
313
+ Create Issue from GitHub API response.
314
+ """
315
+ author_data = data.get("author", {})
316
+ author = User.from_dict(author_data)
317
+
318
+ labels_data = data.get("labels", [])
319
+ labels = [label.get("name", "") for label in labels_data]
320
+
321
+ return cls(
322
+ number=data.get("number", 0),
323
+ title=data.get("title", ""),
324
+ body=data.get("body", ""),
325
+ state=data.get("state", "OPEN"),
326
+ author=author,
327
+ labels=labels,
328
+ created_at=data.get("createdAt"),
329
+ updated_at=data.get("updatedAt"),
330
+ )
@@ -0,0 +1,131 @@
1
+ # plugins/titan-plugin-github/titan_plugin_github/plugin.py
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ from titan_cli.core.plugins.plugin_base import TitanPlugin
6
+ from titan_cli.core.config import TitanConfig
7
+ from titan_cli.core.secrets import SecretManager
8
+ from titan_cli.core.plugins.models import GitHubPluginConfig
9
+ from .clients.github_client import GitHubClient, GitHubError
10
+
11
+
12
+ class GitHubPlugin(TitanPlugin):
13
+ """
14
+ Titan CLI Plugin for GitHub operations.
15
+ """
16
+
17
+ @property
18
+ def name(self) -> str:
19
+ return "github"
20
+
21
+ @property
22
+ def description(self) -> str:
23
+ return "Provides GitHub integration for PRs, issues, and more."
24
+
25
+ @property
26
+ def dependencies(self) -> list[str]:
27
+ return ["git"]
28
+
29
+ def initialize(self, config: TitanConfig, secrets: SecretManager) -> None:
30
+ """
31
+ Initializes the GitHubClient.
32
+ """
33
+ # Get plugin-specific configuration data
34
+ plugin_config_data = self._get_plugin_config(config)
35
+
36
+ # Validate configuration using Pydantic model
37
+ validated_config = GitHubPluginConfig(**plugin_config_data)
38
+
39
+ repo_owner = validated_config.repo_owner
40
+ repo_name = validated_config.repo_name
41
+
42
+ # Get the git client from the registry
43
+ git_plugin = config.registry.get_plugin("git")
44
+ if not git_plugin or not git_plugin.is_available():
45
+ raise GitHubError("The 'git' plugin is a required dependency and is not available.")
46
+ git_client = git_plugin.get_client()
47
+
48
+ # Attempt to auto-detect if not explicitly configured
49
+ if not repo_owner or not repo_name:
50
+ detected_owner, detected_name = git_client.get_github_repo_info()
51
+ if detected_owner and detected_name:
52
+ repo_owner = repo_owner or detected_owner
53
+ repo_name = repo_name or detected_name
54
+
55
+ # If still missing, raise an error
56
+ if not repo_owner or not repo_name:
57
+ raise GitHubError("GitHub repository owner and name must be configured or auto-detected from git remote.")
58
+
59
+ # Initialize client with validated configuration and git_client
60
+ self._client = GitHubClient(
61
+ config=validated_config,
62
+ secrets=secrets,
63
+ git_client=git_client,
64
+ repo_owner=repo_owner, # Pass detected/configured owner
65
+ repo_name=repo_name # Pass detected/configured name
66
+ )
67
+
68
+ def _get_plugin_config(self, config: TitanConfig) -> dict:
69
+ """
70
+ Extract plugin-specific configuration.
71
+
72
+ Args:
73
+ config: TitanConfig instance
74
+
75
+ Returns:
76
+ Plugin config dict (empty if not configured)
77
+ """
78
+ if "github" not in config.config.plugins:
79
+ return {}
80
+
81
+ plugin_entry = config.config.plugins["github"]
82
+ return plugin_entry.config if hasattr(plugin_entry, 'config') else {}
83
+
84
+ def get_config_schema(self) -> dict:
85
+ """Returns the JSON schema for the plugin's configuration."""
86
+ return GitHubPluginConfig.model_json_schema()
87
+
88
+ def is_available(self) -> bool:
89
+ """
90
+ Checks if the GitHub CLI is installed and available.
91
+ """
92
+ import shutil
93
+ return shutil.which("gh") is not None and hasattr(self, '_client') and self._client is not None
94
+
95
+ @property
96
+ def workflows_path(self) -> Optional[Path]:
97
+ """
98
+ Returns the path to the workflows directory for this plugin.
99
+ """
100
+ return Path(__file__).parent / "workflows"
101
+
102
+ def get_client(self) -> GitHubClient:
103
+ """
104
+ Returns the initialized GitHubClient instance.
105
+ """
106
+ # Ensure the client is initialized, potentially adding a check here
107
+ if not hasattr(self, '_client') or self._client is None:
108
+ raise GitHubError("GitHubPlugin not initialized. GitHub client may not be available.")
109
+ return self._client
110
+
111
+ def get_steps(self) -> dict:
112
+ """
113
+ Returns a dictionary of available workflow steps.
114
+ """
115
+ from .steps.create_pr_step import create_pr_step
116
+ from .steps.github_prompt_steps import prompt_for_pr_title_step, prompt_for_pr_body_step, prompt_for_issue_body_step, prompt_for_self_assign_step, prompt_for_labels_step
117
+ from .steps.ai_pr_step import ai_suggest_pr_description_step
118
+ from .steps.issue_steps import ai_suggest_issue_title_and_body_step, create_issue_steps
119
+ from .steps.preview_step import preview_and_confirm_issue_step
120
+ return {
121
+ "create_pr": create_pr_step,
122
+ "prompt_for_pr_title": prompt_for_pr_title_step,
123
+ "prompt_for_pr_body": prompt_for_pr_body_step,
124
+ "prompt_for_issue_body_step": prompt_for_issue_body_step,
125
+ "prompt_for_self_assign": prompt_for_self_assign_step,
126
+ "prompt_for_labels": prompt_for_labels_step,
127
+ "ai_suggest_pr_description": ai_suggest_pr_description_step,
128
+ "ai_suggest_issue_title_and_body": ai_suggest_issue_title_and_body_step,
129
+ "create_issue": create_issue_steps,
130
+ "preview_and_confirm_issue": preview_and_confirm_issue_step,
131
+ }
@@ -0,0 +1,12 @@
1
+ # plugins/titan-plugin-github/titan_plugin_github/steps/__init__.py
2
+ """
3
+ Workflow steps for GitHub operations
4
+ """
5
+
6
+ from .create_pr_step import create_pr_step
7
+ from .ai_pr_step import ai_suggest_pr_description_step
8
+
9
+ __all__ = [
10
+ "create_pr_step",
11
+ "ai_suggest_pr_description_step",
12
+ ]