briar-cli 1.1.1__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 (179) hide show
  1. briar/__init__.py +8 -0
  2. briar/__main__.py +11 -0
  3. briar/_registry.py +36 -0
  4. briar/agent/__init__.py +16 -0
  5. briar/agent/_llm.py +90 -0
  6. briar/agent/_llms/__init__.py +40 -0
  7. briar/agent/_llms/anthropic_llm.py +185 -0
  8. briar/agent/_llms/bedrock.py +154 -0
  9. briar/agent/_llms/gemini.py +152 -0
  10. briar/agent/_llms/openai_llm.py +129 -0
  11. briar/agent/runner.py +364 -0
  12. briar/agent/tools.py +355 -0
  13. briar/auth/__init__.py +42 -0
  14. briar/auth/_acquirer.py +121 -0
  15. briar/auth/_acquirers/__init__.py +62 -0
  16. briar/auth/_acquirers/aws_sso.py +185 -0
  17. briar/auth/_acquirers/aws_static.py +54 -0
  18. briar/auth/_acquirers/bitbucket.py +60 -0
  19. briar/auth/_acquirers/github_device.py +129 -0
  20. briar/auth/_acquirers/github_pat.py +44 -0
  21. briar/auth/_acquirers/infisical.py +80 -0
  22. briar/auth/_acquirers/jira_session.py +102 -0
  23. briar/auth/_acquirers/jira_token.py +61 -0
  24. briar/auth/_acquirers/linear.py +46 -0
  25. briar/auth/_prompt.py +155 -0
  26. briar/cli.py +149 -0
  27. briar/commands/__init__.py +48 -0
  28. briar/commands/agent.py +692 -0
  29. briar/commands/auth.py +281 -0
  30. briar/commands/base.py +46 -0
  31. briar/commands/context.py +143 -0
  32. briar/commands/dashboard.py +61 -0
  33. briar/commands/extract.py +79 -0
  34. briar/commands/iac.py +44 -0
  35. briar/commands/runbook.py +110 -0
  36. briar/commands/secrets.py +165 -0
  37. briar/commands/version.py +17 -0
  38. briar/credentials/__init__.py +48 -0
  39. briar/credentials/_bootstrap.py +93 -0
  40. briar/credentials/_bootstraps/__init__.py +80 -0
  41. briar/credentials/_bootstraps/infisical.py +115 -0
  42. briar/credentials/_store.py +63 -0
  43. briar/credentials/aws_secrets.py +102 -0
  44. briar/credentials/envfile.py +171 -0
  45. briar/credentials/infisical.py +183 -0
  46. briar/credentials/ssm.py +83 -0
  47. briar/credentials/vault.py +109 -0
  48. briar/dashboard/__init__.py +13 -0
  49. briar/dashboard/collectors.py +1354 -0
  50. briar/dashboard/server.py +154 -0
  51. briar/dashboard/templates/index.html +678 -0
  52. briar/decorators.py +48 -0
  53. briar/env_vars.py +92 -0
  54. briar/error_policy.py +273 -0
  55. briar/errors.py +48 -0
  56. briar/extract/__init__.py +52 -0
  57. briar/extract/_cloud.py +157 -0
  58. briar/extract/_clouds/__init__.py +39 -0
  59. briar/extract/_clouds/aws.py +190 -0
  60. briar/extract/_clouds/azure.py +152 -0
  61. briar/extract/_clouds/gcp.py +135 -0
  62. briar/extract/_gh.py +159 -0
  63. briar/extract/_provider.py +218 -0
  64. briar/extract/_providers/__init__.py +60 -0
  65. briar/extract/_providers/bitbucket.py +276 -0
  66. briar/extract/_providers/github.py +277 -0
  67. briar/extract/_tracker.py +124 -0
  68. briar/extract/_trackers/__init__.py +44 -0
  69. briar/extract/_trackers/_jira_auth.py +258 -0
  70. briar/extract/_trackers/bitbucket.py +131 -0
  71. briar/extract/_trackers/github_issues.py +139 -0
  72. briar/extract/_trackers/jira.py +191 -0
  73. briar/extract/_trackers/linear.py +172 -0
  74. briar/extract/_user_filter.py +150 -0
  75. briar/extract/active_tickets.py +71 -0
  76. briar/extract/active_work.py +87 -0
  77. briar/extract/aws_infra.py +79 -0
  78. briar/extract/aws_services/__init__.py +31 -0
  79. briar/extract/aws_services/base.py +25 -0
  80. briar/extract/aws_services/ecs.py +43 -0
  81. briar/extract/aws_services/lambda_.py +38 -0
  82. briar/extract/aws_services/logs.py +39 -0
  83. briar/extract/aws_services/rds.py +35 -0
  84. briar/extract/aws_services/sqs.py +25 -0
  85. briar/extract/base.py +290 -0
  86. briar/extract/code_hotspots.py +134 -0
  87. briar/extract/codebase_conventions.py +72 -0
  88. briar/extract/composer.py +85 -0
  89. briar/extract/github_deployments.py +106 -0
  90. briar/extract/language_detectors/__init__.py +24 -0
  91. briar/extract/language_detectors/base.py +29 -0
  92. briar/extract/language_detectors/go.py +26 -0
  93. briar/extract/language_detectors/node.py +42 -0
  94. briar/extract/language_detectors/python.py +41 -0
  95. briar/extract/pr_archaeology.py +131 -0
  96. briar/extract/pr_review_context.py +123 -0
  97. briar/extract/reviewer_profile.py +141 -0
  98. briar/extract/ticket_archaeology.py +115 -0
  99. briar/extract/ticket_context.py +95 -0
  100. briar/formatting/__init__.py +67 -0
  101. briar/formatting/base.py +25 -0
  102. briar/formatting/csv.py +34 -0
  103. briar/formatting/json.py +19 -0
  104. briar/formatting/quiet.py +29 -0
  105. briar/formatting/table.py +97 -0
  106. briar/formatting/yaml.py +35 -0
  107. briar/iac/__init__.py +18 -0
  108. briar/iac/config_file.py +114 -0
  109. briar/iac/models.py +232 -0
  110. briar/iac/reference_map.py +33 -0
  111. briar/iac/runbook/__init__.py +32 -0
  112. briar/iac/runbook/executor.py +365 -0
  113. briar/iac/runbook/models.py +156 -0
  114. briar/iac/runbook/scheduler.py +187 -0
  115. briar/iac/scaffold/__init__.py +25 -0
  116. briar/iac/scaffold/_composer.py +308 -0
  117. briar/iac/scaffold/_knowledge.py +119 -0
  118. briar/iac/scaffold/archetypes/__init__.py +26 -0
  119. briar/iac/scaffold/archetypes/base.py +85 -0
  120. briar/iac/scaffold/archetypes/engineer.py +64 -0
  121. briar/iac/scaffold/archetypes/pr_ci_fixer.py +100 -0
  122. briar/iac/scaffold/archetypes/pr_conflict_resolver.py +83 -0
  123. briar/iac/scaffold/archetypes/pr_fixer.py +62 -0
  124. briar/iac/scaffold/archetypes/triager.py +50 -0
  125. briar/iac/scaffold/base.py +19 -0
  126. briar/iac/scaffold/implementation.py +59 -0
  127. briar/iac/scaffold/pr_fixes.py +52 -0
  128. briar/iac/scaffold/rules/__init__.py +64 -0
  129. briar/iac/scaffold/rules/base.py +121 -0
  130. briar/iac/scaffold/rules/commit_as_human.md +22 -0
  131. briar/iac/scaffold/rules/minimum_correct_fix.md +20 -0
  132. briar/iac/scaffold/rules/no_force_push.md +19 -0
  133. briar/iac/scaffold/rules/no_new_pr_creation.md +16 -0
  134. briar/iac/scaffold/rules/no_workflow_file_edits.md +21 -0
  135. briar/iac/scaffold/rules/read_all_comments_first.md +20 -0
  136. briar/iac/scaffold/rules/skip_approved_green_prs.md +17 -0
  137. briar/iac/scaffold/shapes/__init__.py +38 -0
  138. briar/iac/scaffold/shapes/base.py +17 -0
  139. briar/iac/scaffold/shapes/one_shot.py +48 -0
  140. briar/iac/scaffold/shapes/plan_approve_act.py +134 -0
  141. briar/iac/scaffold/shapes/triage.py +49 -0
  142. briar/iac/scaffold/sources/__init__.py +26 -0
  143. briar/iac/scaffold/sources/aws.py +69 -0
  144. briar/iac/scaffold/sources/base.py +61 -0
  145. briar/iac/scaffold/sources/bitbucket.py +164 -0
  146. briar/iac/scaffold/sources/github.py +155 -0
  147. briar/iac/scaffold/sources/jira.py +147 -0
  148. briar/iac/scaffold/triggers/__init__.py +23 -0
  149. briar/iac/scaffold/triggers/base.py +24 -0
  150. briar/iac/scaffold/triggers/bitbucket_webhook.py +54 -0
  151. briar/iac/scaffold/triggers/github_webhook.py +52 -0
  152. briar/iac/scaffold/triggers/manual.py +16 -0
  153. briar/iac/scaffold/triggers/schedule_cron.py +37 -0
  154. briar/log_context.py +73 -0
  155. briar/logging.py +68 -0
  156. briar/messaging/__init__.py +66 -0
  157. briar/messaging/_writer.py +90 -0
  158. briar/messaging/bitbucket_pr_comment.py +93 -0
  159. briar/messaging/github_pr_comment.py +90 -0
  160. briar/messaging/jira_comment.py +63 -0
  161. briar/messaging/jira_transition.py +71 -0
  162. briar/messaging/slack_channel.py +73 -0
  163. briar/messaging/telegram_chat.py +68 -0
  164. briar/notify/__init__.py +44 -0
  165. briar/notify/_sink.py +27 -0
  166. briar/notify/email.py +59 -0
  167. briar/notify/pagerduty.py +67 -0
  168. briar/notify/slack.py +49 -0
  169. briar/notify/telegram.py +48 -0
  170. briar/pagination.py +37 -0
  171. briar/settings.py +3 -0
  172. briar/storage/__init__.py +73 -0
  173. briar/storage/base.py +137 -0
  174. briar/storage/file.py +111 -0
  175. briar/storage/postgres.py +353 -0
  176. briar_cli-1.1.1.dist-info/METADATA +1031 -0
  177. briar_cli-1.1.1.dist-info/RECORD +179 -0
  178. briar_cli-1.1.1.dist-info/WHEEL +4 -0
  179. briar_cli-1.1.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,61 @@
1
+ """SourceTemplate contract — Strategy pattern shared by every source kind.
2
+
3
+ A source template knows:
4
+ - its ``kind`` (e.g. ``"github"``, ``"jira"``, ``"aws"``)
5
+ - its ``family`` (``"tracker"`` or ``"cloud"``) — used by the scaffold to
6
+ decide whether the agent should be given action tools for it
7
+ - how to build a `Source` dict (``build_source``)
8
+ - how to build the matching action `Tool` dicts (``build_tools``) — for a
9
+ cloud source this is typically empty (read-only context); for a tracker
10
+ it's a list of comment/transition tools
11
+
12
+ Implementations live in sibling modules and self-register into
13
+ `SOURCE_TEMPLATES` via the package `__init__`."""
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ from abc import ABC, abstractmethod
19
+ from typing import Any, ClassVar, Dict, List, Literal
20
+
21
+
22
+ class SourceTemplate(ABC):
23
+ """Abstract base. Subclasses set the class attributes + implement
24
+ `build_source`. `build_tools` defaults to an empty list (read-only
25
+ source); override for trackers."""
26
+
27
+ kind: ClassVar[str] = ""
28
+ family: ClassVar[Literal["tracker", "cloud", ""]] = ""
29
+ auth_secret_arg: ClassVar[str] = ""
30
+ default_provider_for_oauth: ClassVar[str] = ""
31
+
32
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
33
+ """Subclasses add their own flags (e.g. `--jira-secret-id`)."""
34
+
35
+ @abstractmethod
36
+ def build_source(
37
+ self,
38
+ args: argparse.Namespace,
39
+ key_prefix: str,
40
+ ) -> Dict[str, Any]:
41
+ """Emit the source dict the scaffold composer will include."""
42
+
43
+ def build_tools(
44
+ self,
45
+ args: argparse.Namespace,
46
+ key_prefix: str,
47
+ ) -> List[Dict[str, Any]]:
48
+ """Default: source contributes no action tools. Override for
49
+ trackers to expose comment/transition/etc. tools."""
50
+ return []
51
+
52
+ def target(self, args: argparse.Namespace) -> str:
53
+ """Human-readable identifier for this source (e.g. ``owner/repo``).
54
+
55
+ Used by ``ScaffoldComposer`` to interpolate ``{target}`` into the
56
+ archetype's role/goal/backstory. The default returns ``""`` because
57
+ cloud sources (AWS) don't carry a single target string. Tracker
58
+ sources (GitHub, Bitbucket, Jira) override this to return their
59
+ repo / project identifier; the scaffold template picks the first
60
+ non-empty result among the selected sources."""
61
+ return ""
@@ -0,0 +1,164 @@
1
+ """Bitbucket source template.
2
+
3
+ Family: `tracker`. Reads issues + pull requests from a single
4
+ ``<workspace>/<repo_slug>`` Bitbucket Cloud repository. Brings the same
5
+ three action tools as the GitHub source — `bitbucket.comment_on_issue`,
6
+ `bitbucket.open_pr`, `bitbucket.commit_files` — so an archetype's
7
+ substring `tool_filter` matches on `commit`, `comment_on_issue`, and
8
+ `open_pr` works unchanged.
9
+
10
+ Auth: stored Bitbucket app-password (basic auth: username + app
11
+ password) by default, or an OAuth connection via
12
+ ``--auth-mode oauth`` (the downstream runtime resolves the binding to
13
+ the operator's Atlassian/Bitbucket OAuth grant).
14
+
15
+ User filters (`--bitbucket-authors-allow` / `-block`,
16
+ `--bitbucket-assignees-allow` / `-block`) restrict which issues the
17
+ agent sees. Filters compose: ``allow ∩ ¬block``."""
18
+
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ from typing import Any, Dict, List
23
+
24
+ from briar.iac.scaffold.sources.base import SourceTemplate
25
+
26
+
27
+ class SourceBitbucket(SourceTemplate):
28
+ kind = "bitbucket"
29
+ family = "tracker"
30
+ default_provider_for_oauth = "bitbucket"
31
+
32
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
33
+ parser.add_argument(
34
+ "--bitbucket-workspace",
35
+ help="Bitbucket workspace slug (the part before `/` in a repo URL)",
36
+ )
37
+ parser.add_argument(
38
+ "--bitbucket-repo",
39
+ help="Bitbucket repository slug (the part after `/`)",
40
+ )
41
+ parser.add_argument(
42
+ "--bitbucket-secret-id",
43
+ help="Secret UUID holding a Bitbucket app-password " "(username + app_password stored together); required with --auth-mode pat",
44
+ )
45
+ parser.add_argument(
46
+ "--bitbucket-authors-allow",
47
+ action="append",
48
+ default=[],
49
+ help="only include issues whose reporter is in this list (repeatable)",
50
+ )
51
+ parser.add_argument(
52
+ "--bitbucket-authors-block",
53
+ action="append",
54
+ default=[],
55
+ help="exclude issues whose reporter is in this list (repeatable)",
56
+ )
57
+ parser.add_argument(
58
+ "--bitbucket-assignees-allow",
59
+ action="append",
60
+ default=[],
61
+ help="only include issues with an assignee in this list (repeatable)",
62
+ )
63
+ parser.add_argument(
64
+ "--bitbucket-assignees-block",
65
+ action="append",
66
+ default=[],
67
+ help="exclude issues with an assignee in this list (repeatable)",
68
+ )
69
+
70
+ def build_source(
71
+ self,
72
+ args: argparse.Namespace,
73
+ key_prefix: str,
74
+ ) -> Dict[str, Any]:
75
+ ns = vars(args)
76
+ workspace = ns.get("bitbucket_workspace")
77
+ repo = ns.get("bitbucket_repo")
78
+ if not workspace or not repo:
79
+ raise SystemExit("--source bitbucket requires --bitbucket-workspace AND --bitbucket-repo")
80
+
81
+ config: Dict[str, Any] = {
82
+ "workspace": workspace,
83
+ "repo": f"{workspace}/{repo}",
84
+ "include": "open",
85
+ }
86
+ for key, values in self._user_filters(args).items():
87
+ if values:
88
+ config[key] = values
89
+
90
+ return {
91
+ "key": f"{key_prefix}-bb-issues",
92
+ "name": f"{key_prefix}-bb-issues",
93
+ "kind": "bitbucket",
94
+ "config": config,
95
+ **self._auth(args),
96
+ }
97
+
98
+ def build_tools(
99
+ self,
100
+ args: argparse.Namespace,
101
+ key_prefix: str,
102
+ ) -> List[Dict[str, Any]]:
103
+ auth = self._auth(args)
104
+ return [
105
+ {
106
+ "key": f"{key_prefix}-bb-comment",
107
+ "name": f"{key_prefix}-bb-comment",
108
+ "description": "Post a comment on a Bitbucket issue/PR",
109
+ "implementation_ref": "bitbucket.comment_on_issue",
110
+ "side_effect": "mutate",
111
+ **auth,
112
+ },
113
+ {
114
+ "key": f"{key_prefix}-bb-open-pr",
115
+ "name": f"{key_prefix}-bb-open-pr",
116
+ "description": "Open a draft pull request",
117
+ "implementation_ref": "bitbucket.open_pr",
118
+ "side_effect": "mutate",
119
+ **auth,
120
+ },
121
+ {
122
+ "key": f"{key_prefix}-bb-commit",
123
+ "name": f"{key_prefix}-bb-commit",
124
+ "description": "Commit files to a branch",
125
+ "implementation_ref": "bitbucket.commit_files",
126
+ "side_effect": "mutate",
127
+ **auth,
128
+ },
129
+ ]
130
+
131
+ def target(self, args: argparse.Namespace) -> str:
132
+ ns = vars(args)
133
+ workspace = ns.get("bitbucket_workspace") or ""
134
+ repo = ns.get("bitbucket_repo") or ""
135
+ if not workspace or not repo:
136
+ return ""
137
+ return f"{workspace}/{repo}"
138
+
139
+ @staticmethod
140
+ def _auth(args: argparse.Namespace) -> Dict[str, Any]:
141
+ ns = vars(args)
142
+ mode = ns.get("auth_mode") or "oauth"
143
+ if mode == "pat":
144
+ secret_id = ns.get("bitbucket_secret_id")
145
+ if not secret_id:
146
+ raise SystemExit("--source bitbucket with --auth-mode pat requires " "--bitbucket-secret-id <secret-uuid>")
147
+ return {"credentials_ref": secret_id, "credential_binding": None}
148
+ return {
149
+ "credentials_ref": None,
150
+ "credential_binding": {
151
+ "kind": "oauth_connection",
152
+ "provider": "bitbucket",
153
+ },
154
+ }
155
+
156
+ @staticmethod
157
+ def _user_filters(args: argparse.Namespace) -> Dict[str, List[str]]:
158
+ ns = vars(args)
159
+ return {
160
+ "authors_allow": list(ns.get("bitbucket_authors_allow") or []),
161
+ "authors_block": list(ns.get("bitbucket_authors_block") or []),
162
+ "assignees_allow": list(ns.get("bitbucket_assignees_allow") or []),
163
+ "assignees_block": list(ns.get("bitbucket_assignees_block") or []),
164
+ }
@@ -0,0 +1,155 @@
1
+ """GitHub source template.
2
+
3
+ Family: `tracker`. Reads issues from `<owner>/<repo>`. Brings action
4
+ tools for commenting on issues, opening PRs, and committing files.
5
+
6
+ User filters (`--github-authors-allow`, `--github-authors-block`,
7
+ `--github-assignees-allow`, `--github-assignees-block`) restrict which
8
+ issues the agent sees. Filters compose: allow ∩ ¬block."""
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ from typing import Any, Dict, List
14
+
15
+ from briar.iac.scaffold.sources.base import SourceTemplate
16
+
17
+
18
+ class SourceGithub(SourceTemplate):
19
+ kind = "github"
20
+ family = "tracker"
21
+ default_provider_for_oauth = "github"
22
+
23
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
24
+ # Identity flags for the GitHub source. Registered here (not on
25
+ # the scaffold templates) so a Bitbucket-only or AWS-only scaffold
26
+ # doesn't have GitHub-shaped required flags. Validation that they
27
+ # are set happens in `build_source` when `--source github` is
28
+ # actually selected.
29
+ parser.add_argument(
30
+ "--owner",
31
+ help="GitHub org/user (required when --source includes github)",
32
+ )
33
+ parser.add_argument(
34
+ "--repo",
35
+ help="GitHub repo name (required when --source includes github)",
36
+ )
37
+ parser.add_argument(
38
+ "--github-authors-allow",
39
+ action="append",
40
+ default=[],
41
+ help="only include issues whose creator is in this list (repeatable)",
42
+ )
43
+ parser.add_argument(
44
+ "--github-authors-block",
45
+ action="append",
46
+ default=[],
47
+ help="exclude issues whose creator is in this list (repeatable)",
48
+ )
49
+ parser.add_argument(
50
+ "--github-assignees-allow",
51
+ action="append",
52
+ default=[],
53
+ help="only include issues with an assignee in this list (repeatable)",
54
+ )
55
+ parser.add_argument(
56
+ "--github-assignees-block",
57
+ action="append",
58
+ default=[],
59
+ help="exclude issues with an assignee in this list (repeatable)",
60
+ )
61
+
62
+ def build_source(
63
+ self,
64
+ args: argparse.Namespace,
65
+ key_prefix: str,
66
+ ) -> Dict[str, Any]:
67
+ ns = vars(args)
68
+ owner = ns.get("owner")
69
+ repo = ns.get("repo")
70
+ if not owner or not repo:
71
+ raise SystemExit("--source github requires --owner AND --repo")
72
+ config: Dict[str, Any] = {
73
+ "owner": owner,
74
+ "repo": f"{owner}/{repo}",
75
+ "include": "open",
76
+ }
77
+ for key, values in self._user_filters(args).items():
78
+ if values:
79
+ config[key] = values
80
+
81
+ return {
82
+ "key": f"{key_prefix}-gh-issues",
83
+ "name": f"{key_prefix}-gh-issues",
84
+ "kind": "github",
85
+ "config": config,
86
+ **self._auth(args),
87
+ }
88
+
89
+ def build_tools(
90
+ self,
91
+ args: argparse.Namespace,
92
+ key_prefix: str,
93
+ ) -> List[Dict[str, Any]]:
94
+ auth = self._auth(args)
95
+ return [
96
+ {
97
+ "key": f"{key_prefix}-gh-comment",
98
+ "name": f"{key_prefix}-gh-comment",
99
+ "description": "Post a comment on a GitHub issue/PR",
100
+ "implementation_ref": "github.comment_on_issue",
101
+ "side_effect": "mutate",
102
+ **auth,
103
+ },
104
+ {
105
+ "key": f"{key_prefix}-gh-open-pr",
106
+ "name": f"{key_prefix}-gh-open-pr",
107
+ "description": "Open a draft pull request",
108
+ "implementation_ref": "github.open_pr",
109
+ "side_effect": "mutate",
110
+ **auth,
111
+ },
112
+ {
113
+ "key": f"{key_prefix}-gh-commit",
114
+ "name": f"{key_prefix}-gh-commit",
115
+ "description": "Commit files to a branch",
116
+ "implementation_ref": "github.commit_files",
117
+ "side_effect": "mutate",
118
+ **auth,
119
+ },
120
+ ]
121
+
122
+ def target(self, args: argparse.Namespace) -> str:
123
+ ns = vars(args)
124
+ owner = ns.get("owner") or ""
125
+ repo = ns.get("repo") or ""
126
+ if not owner or not repo:
127
+ return ""
128
+ return f"{owner}/{repo}"
129
+
130
+ @staticmethod
131
+ def _auth(args: argparse.Namespace) -> Dict[str, Any]:
132
+ ns = vars(args)
133
+ mode = ns.get("auth_mode") or "oauth"
134
+ if mode == "pat":
135
+ secret_id = ns.get("github_secret_id")
136
+ if not secret_id:
137
+ raise SystemExit("--source github with --auth-mode pat requires " "--github-secret-id <secret-uuid>")
138
+ return {"credentials_ref": secret_id, "credential_binding": None}
139
+ return {
140
+ "credentials_ref": None,
141
+ "credential_binding": {
142
+ "kind": "oauth_connection",
143
+ "provider": "github",
144
+ },
145
+ }
146
+
147
+ @staticmethod
148
+ def _user_filters(args: argparse.Namespace) -> Dict[str, List[str]]:
149
+ ns = vars(args)
150
+ return {
151
+ "authors_allow": list(ns.get("github_authors_allow") or []),
152
+ "authors_block": list(ns.get("github_authors_block") or []),
153
+ "assignees_allow": list(ns.get("github_assignees_allow") or []),
154
+ "assignees_block": list(ns.get("github_assignees_block") or []),
155
+ }
@@ -0,0 +1,147 @@
1
+ """Jira source template.
2
+
3
+ Family: `tracker`. Reads issues from one or more Jira projects.
4
+ Brings action tools: `jira.comment`, `jira.transition`, `jira.update_issue`.
5
+
6
+ Auth: Atlassian OAuth connection by default, or a stored Atlassian PAT
7
+ via `--jira-secret-id`."""
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ from typing import Any, Dict, List
13
+
14
+ from briar.iac.scaffold.sources.base import SourceTemplate
15
+
16
+
17
+ class SourceJira(SourceTemplate):
18
+ kind = "jira"
19
+ family = "tracker"
20
+ default_provider_for_oauth = "atlassian"
21
+
22
+ _FILTER_FIELDS = (
23
+ ("authors_allow", "jira_authors_allow"),
24
+ ("authors_block", "jira_authors_block"),
25
+ ("assignees_allow", "jira_assignees_allow"),
26
+ ("assignees_block", "jira_assignees_block"),
27
+ )
28
+
29
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
30
+ parser.add_argument(
31
+ "--jira-project",
32
+ action="append",
33
+ default=[],
34
+ help="Jira project key to include (repeatable; defaults to all)",
35
+ )
36
+ parser.add_argument(
37
+ "--jira-jql",
38
+ help="Optional JQL filter applied on top of the project list",
39
+ )
40
+ parser.add_argument(
41
+ "--jira-secret-id",
42
+ help="Secret UUID holding an Atlassian PAT (skip OAuth)",
43
+ )
44
+ parser.add_argument(
45
+ "--jira-authors-allow",
46
+ action="append",
47
+ default=[],
48
+ help="reporter allowlist (repeatable; folds into JQL)",
49
+ )
50
+ parser.add_argument(
51
+ "--jira-authors-block",
52
+ action="append",
53
+ default=[],
54
+ help="reporter blocklist (repeatable; folds into JQL)",
55
+ )
56
+ parser.add_argument(
57
+ "--jira-assignees-allow",
58
+ action="append",
59
+ default=[],
60
+ help="assignee allowlist (repeatable; folds into JQL)",
61
+ )
62
+ parser.add_argument(
63
+ "--jira-assignees-block",
64
+ action="append",
65
+ default=[],
66
+ help="assignee blocklist (repeatable; folds into JQL)",
67
+ )
68
+
69
+ def build_source(
70
+ self,
71
+ args: argparse.Namespace,
72
+ key_prefix: str,
73
+ ) -> Dict[str, Any]:
74
+ ns = vars(args)
75
+ config: Dict[str, Any] = {"include": "open"}
76
+ projects = ns.get("jira_project") or []
77
+ if projects:
78
+ config["projects"] = projects
79
+ jql = ns.get("jira_jql")
80
+ if jql:
81
+ config["jql"] = jql
82
+ for field, attr in self._FILTER_FIELDS:
83
+ values = list(ns.get(attr) or [])
84
+ if values:
85
+ config[field] = values
86
+
87
+ return {
88
+ "key": f"{key_prefix}-jira",
89
+ "name": f"{key_prefix}-jira",
90
+ "kind": "jira",
91
+ "config": config,
92
+ **self._auth(args),
93
+ }
94
+
95
+ def build_tools(
96
+ self,
97
+ args: argparse.Namespace,
98
+ key_prefix: str,
99
+ ) -> List[Dict[str, Any]]:
100
+ auth = self._auth(args)
101
+ return [
102
+ {
103
+ "key": f"{key_prefix}-jira-comment",
104
+ "name": f"{key_prefix}-jira-comment",
105
+ "description": "Add a comment to a Jira issue",
106
+ "implementation_ref": "jira.comment",
107
+ "side_effect": "mutate",
108
+ **auth,
109
+ },
110
+ {
111
+ "key": f"{key_prefix}-jira-transition",
112
+ "name": f"{key_prefix}-jira-transition",
113
+ "description": "Transition a Jira issue to a new status",
114
+ "implementation_ref": "jira.transition",
115
+ "side_effect": "mutate",
116
+ **auth,
117
+ },
118
+ {
119
+ "key": f"{key_prefix}-jira-update",
120
+ "name": f"{key_prefix}-jira-update",
121
+ "description": "Update fields on a Jira issue",
122
+ "implementation_ref": "jira.update_issue",
123
+ "side_effect": "mutate",
124
+ **auth,
125
+ },
126
+ ]
127
+
128
+ def target(self, args: argparse.Namespace) -> str:
129
+ ns = vars(args)
130
+ projects = ns.get("jira_project") or []
131
+ if not projects:
132
+ return ""
133
+ return projects[0]
134
+
135
+ @staticmethod
136
+ def _auth(args: argparse.Namespace) -> Dict[str, Any]:
137
+ ns = vars(args)
138
+ secret_id = ns.get("jira_secret_id")
139
+ if secret_id:
140
+ return {"credentials_ref": secret_id, "credential_binding": None}
141
+ return {
142
+ "credentials_ref": None,
143
+ "credential_binding": {
144
+ "kind": "oauth_connection",
145
+ "provider": "atlassian",
146
+ },
147
+ }
@@ -0,0 +1,23 @@
1
+ """Trigger kind registry. One file per kind; this module is the
2
+ assembly point."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Dict
7
+
8
+ from briar._registry import build_registry
9
+ from briar.iac.scaffold.triggers.base import TriggerTemplate
10
+ from briar.iac.scaffold.triggers.bitbucket_webhook import TriggerBitbucketWebhook
11
+ from briar.iac.scaffold.triggers.github_webhook import TriggerGithubWebhook
12
+ from briar.iac.scaffold.triggers.manual import TriggerManual
13
+ from briar.iac.scaffold.triggers.schedule_cron import TriggerScheduleCron
14
+
15
+
16
+ TRIGGER_TEMPLATES: Dict[str, TriggerTemplate] = build_registry(
17
+ (TriggerGithubWebhook(), TriggerBitbucketWebhook(), TriggerScheduleCron(), TriggerManual()),
18
+ kind="scaffold trigger",
19
+ name_attr="kind",
20
+ )
21
+
22
+
23
+ __all__ = ["TriggerTemplate", "TRIGGER_TEMPLATES"]
@@ -0,0 +1,24 @@
1
+ """`TriggerTemplate` contract — Strategy shared by every trigger kind."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any, ClassVar, Dict
8
+
9
+
10
+ class TriggerTemplate(ABC):
11
+ """Subclasses set `kind` + implement `build_trigger`.
12
+
13
+ `build_trigger` returns the trigger dict or `{}` to signal "no
14
+ trigger row — invocation is manual". Callers check truthiness."""
15
+
16
+ kind: ClassVar[str] = ""
17
+ description: ClassVar[str] = ""
18
+
19
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
20
+ """Default: no extra flags."""
21
+
22
+ @abstractmethod
23
+ def build_trigger(self, args: argparse.Namespace, key_prefix: str, workflow_key: str) -> Dict[str, Any]:
24
+ """Emit the trigger dict, or `{}` when invocation is manual."""
@@ -0,0 +1,54 @@
1
+ """Bitbucket webhook trigger — fires on `issue:created` / `issue:updated`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from typing import Any, Dict
7
+
8
+ from briar.iac.scaffold.triggers.base import TriggerTemplate
9
+
10
+
11
+ class TriggerBitbucketWebhook(TriggerTemplate):
12
+ kind = "bitbucket_webhook"
13
+ description = "Fires on Bitbucket webhook events (issue:created, …)"
14
+
15
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
16
+ parser.add_argument(
17
+ "--bitbucket-webhook-events",
18
+ action="append",
19
+ default=[],
20
+ help="Bitbucket event names (default: issue:created, issue:updated)",
21
+ )
22
+ parser.add_argument(
23
+ "--bitbucket-webhook-labels",
24
+ action="append",
25
+ default=["briar"],
26
+ help="Restrict to issues with any of these labels (default: briar)",
27
+ )
28
+
29
+ def build_trigger(
30
+ self,
31
+ args: argparse.Namespace,
32
+ key_prefix: str,
33
+ workflow_key: str,
34
+ ) -> Dict[str, Any]:
35
+ ns = vars(args)
36
+ events = ns.get("bitbucket_webhook_events") or ["issue:created", "issue:updated"]
37
+ labels = ns.get("bitbucket_webhook_labels") or ["briar"]
38
+ return {
39
+ "key": f"{key_prefix}-trigger",
40
+ "name": f"{key_prefix}-trigger",
41
+ "kind": "bitbucket_webhook",
42
+ "workflow_key": workflow_key,
43
+ "filter_rules": {
44
+ "events": events,
45
+ "labels_any": labels,
46
+ },
47
+ "payload_to_context_mapping": {
48
+ "issue_number": "$.issue.id",
49
+ "issue_title": "$.issue.title",
50
+ "issue_body": "$.issue.content.raw",
51
+ "repo": "$.repository.full_name",
52
+ },
53
+ "is_enabled": True,
54
+ }
@@ -0,0 +1,52 @@
1
+ """GitHub webhook trigger — fires on `issues.opened` / `issues.labeled`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from typing import Any, Dict
7
+
8
+ from briar.iac.scaffold.triggers.base import TriggerTemplate
9
+
10
+
11
+ class TriggerGithubWebhook(TriggerTemplate):
12
+ kind = "github_webhook"
13
+ description = "Fires on GitHub webhook events (issues.opened, …)"
14
+
15
+ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
16
+ parser.add_argument(
17
+ "--webhook-events",
18
+ action="append",
19
+ default=[],
20
+ help="Github event names (default: issues.opened, issues.labeled)",
21
+ )
22
+ parser.add_argument(
23
+ "--webhook-labels",
24
+ action="append",
25
+ default=["briar"],
26
+ help="Restrict to issues with any of these labels (default: briar)",
27
+ )
28
+
29
+ def build_trigger(
30
+ self,
31
+ args: argparse.Namespace,
32
+ key_prefix: str,
33
+ workflow_key: str,
34
+ ) -> Dict[str, Any]:
35
+ events = args.webhook_events or ["issues.opened", "issues.labeled"]
36
+ return {
37
+ "key": f"{key_prefix}-trigger",
38
+ "name": f"{key_prefix}-trigger",
39
+ "kind": "github_webhook",
40
+ "workflow_key": workflow_key,
41
+ "filter_rules": {
42
+ "events": events,
43
+ "labels_any": args.webhook_labels,
44
+ },
45
+ "payload_to_context_mapping": {
46
+ "issue_number": "$.issue.number",
47
+ "issue_title": "$.issue.title",
48
+ "issue_body": "$.issue.body",
49
+ "repo": "$.repository.full_name",
50
+ },
51
+ "is_enabled": True,
52
+ }