tide-commander 1.94.0 → 1.96.0
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.
- package/dist/assets/{BossLogsModal-b4O2O00j.js → BossLogsModal-BFy6Rxdn.js} +1 -1
- package/dist/assets/{BossSpawnModal--Cn40tMQ.js → BossSpawnModal-KQyJYj_B.js} +1 -1
- package/dist/assets/{ControlsModal-SNJ95Qn9.js → ControlsModal-W_ghuG_Q.js} +1 -1
- package/dist/assets/{DockerLogsModal-DOkOsLaK.js → DockerLogsModal-valTcuRy.js} +1 -1
- package/dist/assets/{EmbeddedEditor-BKLJKK3B.js → EmbeddedEditor-CIsFWo6A.js} +1 -1
- package/dist/assets/{GmailOAuthSetup-B8ao6401.js → GmailOAuthSetup-AcW1zAlk.js} +1 -1
- package/dist/assets/{GoogleOAuthSetup-By-gup6S.js → GoogleOAuthSetup-C9uZ46Oe.js} +1 -1
- package/dist/assets/{IframeModal-KzrFyAG0.js → IframeModal-CmWv7GXl.js} +1 -1
- package/dist/assets/{IntegrationsPanel-CXMEldir.js → IntegrationsPanel-CNvzosgx.js} +2 -2
- package/dist/assets/{LogViewerModal-C70SfK7U.js → LogViewerModal-imTE-619.js} +1 -1
- package/dist/assets/{MonitoringModal-CbZtFYh9.js → MonitoringModal-DLxBY738.js} +1 -1
- package/dist/assets/{PM2LogsModal-C8mo6Tqu.js → PM2LogsModal-S4n0D11V.js} +1 -1
- package/dist/assets/{RestoreArchivedAreaModal-BUB-mruP.js → RestoreArchivedAreaModal-B32sqCxN.js} +1 -1
- package/dist/assets/{Scene2DCanvas-zinDgDAK.js → Scene2DCanvas-bEu0UPUm.js} +1 -1
- package/dist/assets/{SceneManager-CAxi1UHT.js → SceneManager-Ce3X0awy.js} +1 -1
- package/dist/assets/{SkillsPanel-Bh_J9suc.js → SkillsPanel-Dj22nDU8.js} +1 -1
- package/dist/assets/{SlackMultiInstanceSetup-DZSpM5ew.js → SlackMultiInstanceSetup-J9NdRY0U.js} +1 -1
- package/dist/assets/{SpawnModal-BrcKWjDs.js → SpawnModal-F9nspdWd.js} +1 -1
- package/dist/assets/{SubordinateAssignmentModal-CBqmszjc.js → SubordinateAssignmentModal-DT31CkPr.js} +1 -1
- package/dist/assets/TriggerManagerPanel-CVJ9ozHz.js +9 -0
- package/dist/assets/{WorkflowEditorPanel-DE9448gZ.js → WorkflowEditorPanel-BYRi0cU9.js} +1 -1
- package/dist/assets/{index-DuDcnBNX.js → index-B9G4hrFR.js} +1 -1
- package/dist/assets/{index-DPvgEcBT.js → index-BPy8NE5B.js} +3 -3
- package/dist/assets/{index-Cbk6B5BY.js → index-BmjrPptb.js} +1 -1
- package/dist/assets/{index-DelPL0HI.js → index-ByfAbkqv.js} +1 -1
- package/dist/assets/{index-B4VnKOHB.js → index-C5wC_Xis.js} +1 -1
- package/dist/assets/{index-Dk2THz0j.js → index-CLr8DpLX.js} +2 -2
- package/dist/assets/{index-BkZ422b_.js → index-DJm7oMXk.js} +1 -1
- package/dist/assets/{index-CpLZo6IM.js → index-DkjWoHhy.js} +1 -1
- package/dist/assets/{index-C7EieXis.js → index-DqZhHROd.js} +1 -1
- package/dist/assets/{main-CteALl_G.js → main-9uTEp9Lr.js} +4 -4
- package/dist/assets/{web-CGYGq80f.js → web-CLuvtqCm.js} +1 -1
- package/dist/assets/{web-BZa7rL0p.js → web-D-a3phls.js} +1 -1
- package/dist/assets/{web-DtevCxzm.js → web-DM2bFWBN.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/src/packages/server/app.js +14 -0
- package/dist/src/packages/server/data/builtin-skills/bitbucket-pr-review.js +431 -0
- package/dist/src/packages/server/data/builtin-skills/bitbucket-reviewer.js +191 -0
- package/dist/src/packages/server/data/builtin-skills/create-building.js +521 -484
- package/dist/src/packages/server/data/builtin-skills/index.js +4 -0
- package/dist/src/packages/server/data/builtin-skills/trigger-designer.js +246 -71
- package/dist/src/packages/server/routes/bitbucket-author-loop.js +54 -0
- package/dist/src/packages/server/routes/buildings.js +298 -0
- package/dist/src/packages/server/routes/index.js +3 -1
- package/dist/src/packages/server/routes/trigger-routes.js +68 -15
- package/dist/src/packages/server/routes/webhook-dedupe.js +58 -0
- package/dist/src/packages/server/routes/webhook-signatures.js +67 -0
- package/dist/src/packages/server/services/building-service.js +400 -85
- package/dist/src/packages/server/services/trigger-service.js +5 -3
- package/dist/src/packages/server/websocket/handler.js +2 -4
- package/package.json +1 -1
- package/dist/assets/TriggerManagerPanel-BD7VnHYc.js +0 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ck as a}from"./main-
|
|
1
|
+
import{ck as a}from"./main-9uTEp9Lr.js";import{ImpactStyle as i,NotificationType as r}from"./index-CLr8DpLX.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class h extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t==null?void 0:t.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t==null?void 0:t.type);this.vibrateWithPattern(e)}async vibrate(t){const e=(t==null?void 0:t.duration)||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{h as HapticsWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ck as t}from"./main-
|
|
1
|
+
import{ck as t}from"./main-9uTEp9Lr.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class o extends t{constructor(){super(),this.handleVisibilityChange=()=>{const e={isActive:document.hidden!==!0};this.notifyListeners("appStateChange",e),document.hidden?this.notifyListeners("pause",null):this.notifyListeners("resume",null)},document.addEventListener("visibilitychange",this.handleVisibilityChange,!1)}exitApp(){throw this.unimplemented("Not implemented on web.")}async getInfo(){throw this.unimplemented("Not implemented on web.")}async getLaunchUrl(){return{url:""}}async getState(){return{isActive:document.hidden!==!0}}async minimizeApp(){throw this.unimplemented("Not implemented on web.")}async toggleBackButtonHandler(){throw this.unimplemented("Not implemented on web.")}async getAppLanguage(){return{value:navigator.language.split("-")[0].toLowerCase()}}}export{o as AppWeb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ck as s}from"./main-
|
|
1
|
+
import{ck as s}from"./main-9uTEp9Lr.js";import"./vendor-react--Eh9ivFN.js";import"./vendor-three-Chj50gSY.js";class l extends s{constructor(){super(...arguments),this.pending=[],this.deliveredNotifications=[],this.hasNotificationSupport=()=>{if(!("Notification"in window)||!Notification.requestPermission)return!1;if(Notification.permission!=="granted")try{new Notification("")}catch(i){if(i instanceof Error&&i.name==="TypeError")return!1}return!0}}async getDeliveredNotifications(){const i=[];for(const t of this.deliveredNotifications){const e={title:t.title,id:parseInt(t.tag),body:t.body};i.push(e)}return{notifications:i}}async removeDeliveredNotifications(i){for(const t of i.notifications){const e=this.deliveredNotifications.find(n=>n.tag===String(t.id));e==null||e.close(),this.deliveredNotifications=this.deliveredNotifications.filter(()=>!e)}}async removeAllDeliveredNotifications(){for(const i of this.deliveredNotifications)i.close();this.deliveredNotifications=[]}async createChannel(){throw this.unimplemented("Not implemented on web.")}async deleteChannel(){throw this.unimplemented("Not implemented on web.")}async listChannels(){throw this.unimplemented("Not implemented on web.")}async schedule(i){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");for(const t of i.notifications)this.sendNotification(t);return{notifications:i.notifications.map(t=>({id:t.id}))}}async getPending(){return{notifications:this.pending}}async registerActionTypes(){throw this.unimplemented("Not implemented on web.")}async cancel(i){this.pending=this.pending.filter(t=>!i.notifications.find(e=>e.id===t.id))}async areEnabled(){const{display:i}=await this.checkPermissions();return{value:i==="granted"}}async changeExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async checkExactNotificationSetting(){throw this.unimplemented("Not implemented on web.")}async requestPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(await Notification.requestPermission())}}async checkPermissions(){if(!this.hasNotificationSupport())throw this.unavailable("Notifications not supported in this browser.");return{display:this.transformNotificationPermission(Notification.permission)}}transformNotificationPermission(i){switch(i){case"granted":return"granted";case"denied":return"denied";default:return"prompt"}}sendPending(){var i;const t=[],e=new Date().getTime();for(const n of this.pending)!((i=n.schedule)===null||i===void 0)&&i.at&&n.schedule.at.getTime()<=e&&(this.buildNotification(n),t.push(n));this.pending=this.pending.filter(n=>!t.find(o=>o===n))}sendNotification(i){var t;if(!((t=i.schedule)===null||t===void 0)&&t.at){const e=i.schedule.at.getTime()-new Date().getTime();this.pending.push(i),setTimeout(()=>{this.sendPending()},e);return}this.buildNotification(i)}buildNotification(i){const t=new Notification(i.title,{body:i.body,tag:String(i.id)});return t.addEventListener("click",this.onClick.bind(this,i),!1),t.addEventListener("show",this.onShow.bind(this,i),!1),t.addEventListener("close",()=>{this.deliveredNotifications=this.deliveredNotifications.filter(()=>!this)},!1),this.deliveredNotifications.push(t),t}onClick(i){const t={actionId:"tap",notification:i};this.notifyListeners("localNotificationActionPerformed",t)}onShow(i){this.notifyListeners("localNotificationReceived",i)}}export{l as LocalNotificationsWeb};
|
package/dist/index.html
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png" />
|
|
23
23
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
|
|
24
24
|
<title>Tide Commander</title>
|
|
25
|
-
<script type="module" crossorigin src="/assets/main-
|
|
25
|
+
<script type="module" crossorigin src="/assets/main-9uTEp9Lr.js"></script>
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react--Eh9ivFN.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/vendor-three-Chj50gSY.js">
|
|
28
28
|
<link rel="stylesheet" crossorigin href="/assets/main-BfT_95fk.css">
|
|
@@ -53,6 +53,20 @@ export function createApp() {
|
|
|
53
53
|
const app = express();
|
|
54
54
|
// Middleware
|
|
55
55
|
app.use(cors());
|
|
56
|
+
// Webhook routes need the raw request bytes for HMAC signature verification.
|
|
57
|
+
// Bitbucket and GitHub compute the signature over the exact bytes they send,
|
|
58
|
+
// so re-serializing via JSON.stringify(req.body) can produce a different
|
|
59
|
+
// digest (key ordering / whitespace) and falsely reject valid deliveries.
|
|
60
|
+
// The path-scoped JSON parser below runs first; it parses the body AND
|
|
61
|
+
// stashes the raw buffer on req.rawBody. Once req._body is set, the global
|
|
62
|
+
// express.json that follows is a no-op for these requests, so the raw
|
|
63
|
+
// buffer is only retained for webhook deliveries — not every JSON request.
|
|
64
|
+
app.use('/api/triggers/webhook', express.json({
|
|
65
|
+
limit: '50mb',
|
|
66
|
+
verify: (req, _res, buf) => {
|
|
67
|
+
req.rawBody = buf;
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
56
70
|
app.use(express.json({ limit: '50mb' })); // Increased for audio uploads (STT)
|
|
57
71
|
// Request logging & timing
|
|
58
72
|
app.use((req, res, next) => {
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
export const bitbucketPRReview = {
|
|
2
|
+
slug: 'bitbucket-pr-review',
|
|
3
|
+
name: 'Bitbucket PR Review',
|
|
4
|
+
description: 'Read pull request diffs, post review comments (inline or summary), approve or request-changes on Bitbucket Cloud PRs using API token authentication. Use this skill when an automated reviewer agent needs to evaluate a PR and record a verdict.',
|
|
5
|
+
allowedTools: ['Bash(curl:*)', 'Read', 'Grep', 'Glob'],
|
|
6
|
+
content: `# Bitbucket Pull Request Reviewer
|
|
7
|
+
|
|
8
|
+
Read PR diffs, post review comments, and set verdict (approve / request-changes / clear) on Bitbucket Cloud pull requests via the REST API 2.0.
|
|
9
|
+
|
|
10
|
+
> **Note:** Bitbucket deprecated App Passwords in September 2025 (disabled June 9, 2026). This skill uses API tokens with scopes.
|
|
11
|
+
|
|
12
|
+
## Required Secrets
|
|
13
|
+
|
|
14
|
+
| Secret Key | Description |
|
|
15
|
+
|------------|-------------|
|
|
16
|
+
| \`BITBUCKET_EMAIL\` | Atlassian account email for the reviewer identity |
|
|
17
|
+
| \`BITBUCKET_TOKEN\` | Bitbucket API token with PR review scopes |
|
|
18
|
+
|
|
19
|
+
**Required scopes:**
|
|
20
|
+
- \`read:repository:bitbucket\` — fetch diff
|
|
21
|
+
- \`read:pullrequest:bitbucket\` — read PR + comments + participants
|
|
22
|
+
- \`write:pullrequest:bitbucket\` — comments, approve/unapprove, request-changes/unrequest-changes
|
|
23
|
+
|
|
24
|
+
Do **not** request \`write:repository:bitbucket\` — a reviewer must never push.
|
|
25
|
+
|
|
26
|
+
**Authentication:** HTTP Basic, built from the secret placeholders:
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
curl -s -u "{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}" ...
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## CRITICAL: All API Calls Must Use Streaming Exec
|
|
34
|
+
|
|
35
|
+
Secret placeholders \`{{BITBUCKET_EMAIL}}\` / \`{{BITBUCKET_TOKEN}}\` are **only** interpolated when commands run through the Streaming Exec API (\`/api/exec\`). Direct Bash curl calls will send the raw \`{{...}}\` placeholders and 401.
|
|
36
|
+
|
|
37
|
+
**Wrap every curl command:**
|
|
38
|
+
\`\`\`bash
|
|
39
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
40
|
+
-H "Content-Type: application/json" \\
|
|
41
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/diff"}'
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
If you get a 401, the most likely cause is calling curl directly instead of through \`/api/exec\`.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Common Variables
|
|
49
|
+
|
|
50
|
+
| Variable | Description | Example |
|
|
51
|
+
|----------|-------------|---------|
|
|
52
|
+
| \`WORKSPACE\` | Bitbucket workspace slug | \`mycompany\` |
|
|
53
|
+
| \`REPO_SLUG\` | Repository slug | \`wind\` |
|
|
54
|
+
| \`PR_ID\` | Pull request id | \`42\` |
|
|
55
|
+
|
|
56
|
+
These typically arrive in the trigger payload (extracted from \`repository.full_name\` and \`pullrequest.id\` by the webhook trigger).
|
|
57
|
+
|
|
58
|
+
\`\`\`bash
|
|
59
|
+
# Split workspace/repo from full_name="myws/wind"
|
|
60
|
+
WORKSPACE=$(echo "$REPO_FULL_NAME" | cut -d/ -f1)
|
|
61
|
+
REPO_SLUG=$(echo "$REPO_FULL_NAME" | cut -d/ -f2)
|
|
62
|
+
\`\`\`
|
|
63
|
+
|
|
64
|
+
Base URL: \`https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG\`
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Action: fetch_pr_diff
|
|
69
|
+
|
|
70
|
+
Retrieve the unified diff for a pull request.
|
|
71
|
+
|
|
72
|
+
**Endpoint:** \`GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/diff\`
|
|
73
|
+
|
|
74
|
+
**Input Schema:**
|
|
75
|
+
\`\`\`json
|
|
76
|
+
{
|
|
77
|
+
"type": "object",
|
|
78
|
+
"required": ["workspace", "repo_slug", "pull_request_id"],
|
|
79
|
+
"properties": {
|
|
80
|
+
"workspace": { "type": "string", "description": "Bitbucket workspace slug" },
|
|
81
|
+
"repo_slug": { "type": "string", "description": "Repository slug" },
|
|
82
|
+
"pull_request_id": { "type": "integer", "description": "PR id" },
|
|
83
|
+
"max_bytes": { "type": "integer", "default": 204800, "description": "Truncate diff at this many bytes (default 200 KB)" }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
**Call (200 KB cap, returns JSON \`{ diff, truncated, byte_count }\`):**
|
|
89
|
+
\`\`\`bash
|
|
90
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
91
|
+
-H "Content-Type: application/json" \\
|
|
92
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"set -euo pipefail; MAX=\${MAX_BYTES:-204800}; RAW=$(curl -s -H \\"Accept: text/plain\\" -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/diff\\"); LEN=\${#RAW}; if [ \\"$LEN\\" -gt \\"$MAX\\" ]; then DIFF=\${RAW:0:$MAX}; TRUNC=true; else DIFF=$RAW; TRUNC=false; fi; jq -nc --arg d \\"$DIFF\\" --argjson t $TRUNC --argjson l $LEN \\"{diff:\\$d, truncated:\\$t, byte_count:\\$l}\\""}'
|
|
93
|
+
\`\`\`
|
|
94
|
+
|
|
95
|
+
**Output (JSON):**
|
|
96
|
+
\`\`\`json
|
|
97
|
+
{ "diff": "<unified diff text, truncated to max_bytes>", "truncated": false, "byte_count": 12345 }
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
If \`truncated:true\`, include a note in the summary comment that the review was based on a partial diff and recommend splitting the PR.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Action: fetch_pr_diffstat
|
|
105
|
+
|
|
106
|
+
Retrieve per-file change statistics (additions/removals, file paths). Use as a pre-flight gate before \`fetch_pr_diff\` to skip oversized PRs early.
|
|
107
|
+
|
|
108
|
+
**Endpoint:** \`GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/diffstat\`
|
|
109
|
+
|
|
110
|
+
**Input Schema:**
|
|
111
|
+
\`\`\`json
|
|
112
|
+
{
|
|
113
|
+
"type": "object",
|
|
114
|
+
"required": ["workspace", "repo_slug", "pull_request_id"],
|
|
115
|
+
"properties": {
|
|
116
|
+
"workspace": { "type": "string" },
|
|
117
|
+
"repo_slug": { "type": "string" },
|
|
118
|
+
"pull_request_id": { "type": "integer" },
|
|
119
|
+
"pagelen": { "type": "integer", "default": 100, "description": "Page size (max 100)" }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
\`\`\`
|
|
123
|
+
|
|
124
|
+
**Call:**
|
|
125
|
+
\`\`\`bash
|
|
126
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
127
|
+
-H "Content-Type: application/json" \\
|
|
128
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/diffstat?pagelen=100\\""}'
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
**Output (Bitbucket paginated response):**
|
|
132
|
+
\`\`\`json
|
|
133
|
+
{
|
|
134
|
+
"values": [
|
|
135
|
+
{ "type": "diffstat", "status": "modified", "lines_added": 12, "lines_removed": 3,
|
|
136
|
+
"old": { "path": "src/foo.ts" }, "new": { "path": "src/foo.ts" } }
|
|
137
|
+
],
|
|
138
|
+
"page": 1, "pagelen": 100, "size": 1, "next": null
|
|
139
|
+
}
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
If the result spans multiple pages, follow \`next\` URLs to fetch the rest.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Action: post_inline_comment
|
|
147
|
+
|
|
148
|
+
Post a comment anchored to a specific line in a file in the PR diff.
|
|
149
|
+
|
|
150
|
+
**Endpoint:** \`POST /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments\`
|
|
151
|
+
|
|
152
|
+
**Input Schema:**
|
|
153
|
+
\`\`\`json
|
|
154
|
+
{
|
|
155
|
+
"type": "object",
|
|
156
|
+
"required": ["workspace", "repo_slug", "pull_request_id", "path", "line", "side", "text"],
|
|
157
|
+
"properties": {
|
|
158
|
+
"workspace": { "type": "string" },
|
|
159
|
+
"repo_slug": { "type": "string" },
|
|
160
|
+
"pull_request_id": { "type": "integer" },
|
|
161
|
+
"path": { "type": "string", "description": "File path inside the repo (e.g. src/foo.ts)" },
|
|
162
|
+
"line": { "type": "integer", "description": "Line number in the file (1-indexed)" },
|
|
163
|
+
"side": { "type": "string", "enum": ["to", "from"], "description": "'to' = line in destination/new file; 'from' = line in source/old file" },
|
|
164
|
+
"text": { "type": "string", "description": "Comment body (markdown)" }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
\`\`\`
|
|
168
|
+
|
|
169
|
+
**Body shape (the \`inline\` object distinguishes inline from summary comments):**
|
|
170
|
+
\`\`\`json
|
|
171
|
+
{
|
|
172
|
+
"content": { "raw": "<text>" },
|
|
173
|
+
"inline": { "path": "<path>", "to": <line> }
|
|
174
|
+
}
|
|
175
|
+
\`\`\`
|
|
176
|
+
Use \`"from"\` instead of \`"to"\` when commenting on a removed/old line.
|
|
177
|
+
|
|
178
|
+
**Call:**
|
|
179
|
+
\`\`\`bash
|
|
180
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
181
|
+
-H "Content-Type: application/json" \\
|
|
182
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -X POST -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" -H \\"Content-Type: application/json\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/comments\\" -d \\"{\\\\\\"content\\\\\\":{\\\\\\"raw\\\\\\":\\\\\\"$TEXT\\\\\\"},\\\\\\"inline\\\\\\":{\\\\\\"path\\\\\\":\\\\\\"$PATH\\\\\\",\\\\\\"$SIDE\\\\\\":$LINE}}\\""}'
|
|
183
|
+
\`\`\`
|
|
184
|
+
|
|
185
|
+
**Output:** Bitbucket comment object with \`id\`, \`content.raw\`, \`inline\`, \`user\`, \`created_on\`.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Action: post_summary_comment
|
|
190
|
+
|
|
191
|
+
Post a top-level (non-inline) comment summarizing the review.
|
|
192
|
+
|
|
193
|
+
**Endpoint:** \`POST /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments\` (same endpoint, body omits \`inline\`)
|
|
194
|
+
|
|
195
|
+
**Input Schema:**
|
|
196
|
+
\`\`\`json
|
|
197
|
+
{
|
|
198
|
+
"type": "object",
|
|
199
|
+
"required": ["workspace", "repo_slug", "pull_request_id", "text"],
|
|
200
|
+
"properties": {
|
|
201
|
+
"workspace": { "type": "string" },
|
|
202
|
+
"repo_slug": { "type": "string" },
|
|
203
|
+
"pull_request_id": { "type": "integer" },
|
|
204
|
+
"text": { "type": "string", "description": "Summary comment body (markdown)" }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
\`\`\`
|
|
208
|
+
|
|
209
|
+
**Body:**
|
|
210
|
+
\`\`\`json
|
|
211
|
+
{ "content": { "raw": "## Verdict\\n\\nLGTM — approving.\\n..." } }
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
**Call:**
|
|
215
|
+
\`\`\`bash
|
|
216
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
217
|
+
-H "Content-Type: application/json" \\
|
|
218
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -X POST -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" -H \\"Content-Type: application/json\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/comments\\" -d \\"{\\\\\\"content\\\\\\":{\\\\\\"raw\\\\\\":\\\\\\"$TEXT\\\\\\"}}\\""}'
|
|
219
|
+
\`\`\`
|
|
220
|
+
|
|
221
|
+
Tip: tag the verdict so re-reviews can find it via \`list_existing_comments\` (e.g. start the body with \`<!-- tide-commander-verdict -->\`).
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Action: approve
|
|
226
|
+
|
|
227
|
+
Set the calling user's participant state to \`approved\`.
|
|
228
|
+
|
|
229
|
+
**Endpoint:** \`POST /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/approve\`
|
|
230
|
+
|
|
231
|
+
**Input Schema:**
|
|
232
|
+
\`\`\`json
|
|
233
|
+
{
|
|
234
|
+
"type": "object",
|
|
235
|
+
"required": ["workspace", "repo_slug", "pull_request_id"],
|
|
236
|
+
"properties": {
|
|
237
|
+
"workspace": { "type": "string" },
|
|
238
|
+
"repo_slug": { "type": "string" },
|
|
239
|
+
"pull_request_id": { "type": "integer" }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
\`\`\`
|
|
243
|
+
|
|
244
|
+
**Call:**
|
|
245
|
+
\`\`\`bash
|
|
246
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
247
|
+
-H "Content-Type: application/json" \\
|
|
248
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -X POST -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/approve\\""}'
|
|
249
|
+
\`\`\`
|
|
250
|
+
|
|
251
|
+
**Output:** participant object with \`approved: true\`, \`state: "approved"\`.
|
|
252
|
+
|
|
253
|
+
If a prior pass called \`request_changes\`, also call \`unrequest_changes\` so the participant state isn't ambiguous.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Action: unapprove
|
|
258
|
+
|
|
259
|
+
Clear the calling user's \`approved\` state.
|
|
260
|
+
|
|
261
|
+
**Endpoint:** \`DELETE /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/approve\`
|
|
262
|
+
|
|
263
|
+
**Input Schema:** identical to \`approve\`.
|
|
264
|
+
|
|
265
|
+
**Call:**
|
|
266
|
+
\`\`\`bash
|
|
267
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
268
|
+
-H "Content-Type: application/json" \\
|
|
269
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -X DELETE -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/approve\\""}'
|
|
270
|
+
\`\`\`
|
|
271
|
+
|
|
272
|
+
**Output:** \`204 No Content\` on success.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Action: request_changes
|
|
277
|
+
|
|
278
|
+
Set the calling user's participant state to \`changes_requested\` (native Bitbucket Cloud endpoint — not emulated).
|
|
279
|
+
|
|
280
|
+
**Endpoint:** \`POST /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/request-changes\`
|
|
281
|
+
|
|
282
|
+
**Input Schema:**
|
|
283
|
+
\`\`\`json
|
|
284
|
+
{
|
|
285
|
+
"type": "object",
|
|
286
|
+
"required": ["workspace", "repo_slug", "pull_request_id"],
|
|
287
|
+
"properties": {
|
|
288
|
+
"workspace": { "type": "string" },
|
|
289
|
+
"repo_slug": { "type": "string" },
|
|
290
|
+
"pull_request_id": { "type": "integer" }
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
\`\`\`
|
|
294
|
+
|
|
295
|
+
**Call:**
|
|
296
|
+
\`\`\`bash
|
|
297
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
298
|
+
-H "Content-Type: application/json" \\
|
|
299
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -X POST -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/request-changes\\""}'
|
|
300
|
+
\`\`\`
|
|
301
|
+
|
|
302
|
+
**Output:** participant object with \`state: "changes_requested"\`.
|
|
303
|
+
|
|
304
|
+
If a prior pass called \`approve\`, also call \`unapprove\` so the participant state isn't ambiguous.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Action: unrequest_changes
|
|
309
|
+
|
|
310
|
+
Clear the calling user's \`changes_requested\` state.
|
|
311
|
+
|
|
312
|
+
**Endpoint:** \`DELETE /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/request-changes\`
|
|
313
|
+
|
|
314
|
+
**Input Schema:** identical to \`request_changes\`.
|
|
315
|
+
|
|
316
|
+
**Call:**
|
|
317
|
+
\`\`\`bash
|
|
318
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
319
|
+
-H "Content-Type: application/json" \\
|
|
320
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -X DELETE -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/request-changes\\""}'
|
|
321
|
+
\`\`\`
|
|
322
|
+
|
|
323
|
+
**Output:** \`204 No Content\` on success.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Action: list_existing_comments
|
|
328
|
+
|
|
329
|
+
List all comments on a PR. Use to detect prior reviewer verdict comments (idempotency for \`pullrequest:updated\` retries) and to avoid duplicate inline comments.
|
|
330
|
+
|
|
331
|
+
**Endpoint:** \`GET /2.0/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments?pagelen=100\`
|
|
332
|
+
|
|
333
|
+
**Input Schema:**
|
|
334
|
+
\`\`\`json
|
|
335
|
+
{
|
|
336
|
+
"type": "object",
|
|
337
|
+
"required": ["workspace", "repo_slug", "pull_request_id"],
|
|
338
|
+
"properties": {
|
|
339
|
+
"workspace": { "type": "string" },
|
|
340
|
+
"repo_slug": { "type": "string" },
|
|
341
|
+
"pull_request_id": { "type": "integer" },
|
|
342
|
+
"pagelen": { "type": "integer", "default": 100, "description": "Page size (max 100)" },
|
|
343
|
+
"follow_pagination": { "type": "boolean", "default": true, "description": "If true, follow 'next' URLs and concatenate all pages" }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
\`\`\`
|
|
347
|
+
|
|
348
|
+
**Call (single page):**
|
|
349
|
+
\`\`\`bash
|
|
350
|
+
curl -s -X POST http://localhost:5174/api/exec \\
|
|
351
|
+
-H "Content-Type: application/json" \\
|
|
352
|
+
-d '{"agentId":"YOUR_AGENT_ID","command":"curl -s -u \\"{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}\\" \\"https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/comments?pagelen=100\\""}'
|
|
353
|
+
\`\`\`
|
|
354
|
+
|
|
355
|
+
**Pagination loop (concatenate all pages into one JSON array):**
|
|
356
|
+
\`\`\`bash
|
|
357
|
+
URL="https://api.bitbucket.org/2.0/repositories/$WORKSPACE/$REPO_SLUG/pullrequests/$PR_ID/comments?pagelen=100"
|
|
358
|
+
ALL="[]"
|
|
359
|
+
while [ -n "$URL" ] && [ "$URL" != "null" ]; do
|
|
360
|
+
PAGE=$(curl -s -u "{{BITBUCKET_EMAIL}}:{{BITBUCKET_TOKEN}}" "$URL")
|
|
361
|
+
ALL=$(jq -c --argjson page "$PAGE" '. + $page.values' <<<"$ALL")
|
|
362
|
+
URL=$(jq -r '.next // empty' <<<"$PAGE")
|
|
363
|
+
done
|
|
364
|
+
echo "$ALL"
|
|
365
|
+
\`\`\`
|
|
366
|
+
|
|
367
|
+
**Output:** array of comment objects with \`id\`, \`content.raw\`, \`inline\` (for inline comments), \`user.account_id\`, \`created_on\`, \`updated_on\`, \`deleted\`.
|
|
368
|
+
|
|
369
|
+
To find a prior verdict comment, filter for \`content.raw\` starting with the \`<!-- tide-commander-verdict -->\` marker.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Verdict Workflow
|
|
374
|
+
|
|
375
|
+
Symmetric, two-state verdict — these always run together so the participant state stays consistent:
|
|
376
|
+
|
|
377
|
+
| Verdict | Calls |
|
|
378
|
+
|---------|-------|
|
|
379
|
+
| Approve | \`post_summary_comment\` + \`approve\` + (if prior pass requested changes) \`unrequest_changes\` |
|
|
380
|
+
| Request changes | \`post_summary_comment\` + \`request_changes\` + (if prior pass approved) \`unapprove\` |
|
|
381
|
+
|
|
382
|
+
On a \`pullrequest:updated\` event, first call \`list_existing_comments\` and look for the \`<!-- tide-commander-verdict -->\` marker to decide whether to update an existing verdict (delete + repost) vs add a new one.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Idempotency Rules
|
|
387
|
+
|
|
388
|
+
1. The webhook payload includes \`X-Request-UUID\`. Persist the last-handled UUID per PR and skip re-processing the same delivery.
|
|
389
|
+
2. Before posting a summary comment, call \`list_existing_comments\` and check for the \`<!-- tide-commander-verdict -->\` marker. If found, delete the prior comment first (\`DELETE …/comments/{comment_id}\`) before posting the new one.
|
|
390
|
+
3. Inline comments are harder to dedupe — match on \`{path, line, side}\` and skip if a comment from the reviewer account already exists at that anchor.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Error Handling
|
|
395
|
+
|
|
396
|
+
| HTTP | Meaning | Action |
|
|
397
|
+
|------|---------|--------|
|
|
398
|
+
| 401 | Unauthorized | Verify \`BITBUCKET_EMAIL\` / \`BITBUCKET_TOKEN\` are set; confirm the curl ran via \`/api/exec\` |
|
|
399
|
+
| 403 | Forbidden | Token is missing \`write:pullrequest:bitbucket\` (or \`read:pullrequest:bitbucket\` for reads) |
|
|
400
|
+
| 404 | Not Found | Wrong workspace/repo/PR id — re-extract from the trigger payload |
|
|
401
|
+
| 400 | Bad Request | JSON body malformed (commonly an unescaped quote in the comment body) |
|
|
402
|
+
| 429 | Rate limited | Bitbucket rate-limits per-user; back off and retry the failed action only |
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Safety Rules
|
|
407
|
+
|
|
408
|
+
1. **Never push code, never merge, never decline** — those are not reviewer responsibilities. Required scopes intentionally exclude \`write:repository:bitbucket\`.
|
|
409
|
+
2. **Always check for an existing verdict comment** before posting (idempotency).
|
|
410
|
+
3. **Never post on a PR authored by the reviewer account** — filter \`pullrequest.author\` against the \`BITBUCKET_EMAIL\` identity to avoid loops.
|
|
411
|
+
4. **Honor the diff-size gate** — if \`fetch_pr_diffstat\` shows a PR over the configured threshold, post a summary comment explaining the skip and **do not** approve or request changes.
|
|
412
|
+
5. **Do not include secret values in comment bodies or logs.**
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Quick Reference
|
|
417
|
+
|
|
418
|
+
| Action | Endpoint | Method |
|
|
419
|
+
|--------|----------|--------|
|
|
420
|
+
| fetch_pr_diff | \`/pullrequests/{id}/diff\` | GET |
|
|
421
|
+
| fetch_pr_diffstat | \`/pullrequests/{id}/diffstat\` | GET |
|
|
422
|
+
| post_inline_comment | \`/pullrequests/{id}/comments\` | POST (with \`inline\`) |
|
|
423
|
+
| post_summary_comment | \`/pullrequests/{id}/comments\` | POST (no \`inline\`) |
|
|
424
|
+
| approve | \`/pullrequests/{id}/approve\` | POST |
|
|
425
|
+
| unapprove | \`/pullrequests/{id}/approve\` | DELETE |
|
|
426
|
+
| request_changes | \`/pullrequests/{id}/request-changes\` | POST |
|
|
427
|
+
| unrequest_changes | \`/pullrequests/{id}/request-changes\` | DELETE |
|
|
428
|
+
| list_existing_comments | \`/pullrequests/{id}/comments?pagelen=100\` | GET |
|
|
429
|
+
|
|
430
|
+
Base URL: \`https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}\``,
|
|
431
|
+
};
|