mirror of
https://github.com/sst/opencode.git
synced 2025-08-18 20:20:19 +00:00
wip: github actions
This commit is contained in:
parent
10ae43a121
commit
3a7a2a838e
17 changed files with 1333 additions and 1146 deletions
8
.github/workflows/opencode.yml
vendored
8
.github/workflows/opencode.yml
vendored
|
@ -6,7 +6,11 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
opencode:
|
opencode:
|
||||||
if: startsWith(github.event.comment.body, 'hey opencode')
|
if: |
|
||||||
|
startsWith(github.event.comment.body, 'opencode') ||
|
||||||
|
startsWith(github.event.comment.body, 'hi opencode') ||
|
||||||
|
startsWith(github.event.comment.body, 'hey opencode') ||
|
||||||
|
contains(github.event.comment.body, '@opencode-agent')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
|
@ -17,7 +21,7 @@ jobs:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Run opencode
|
- name: Run opencode
|
||||||
uses: sst/opencode/sdks/github@github-v1
|
uses: sst/opencode/github@latest
|
||||||
env:
|
env:
|
||||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
with:
|
with:
|
||||||
|
|
61
bun.lock
61
bun.lock
|
@ -29,6 +29,8 @@
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@actions/core": "1.11.1",
|
||||||
|
"@actions/github": "6.0.1",
|
||||||
"@clack/prompts": "0.11.0",
|
"@clack/prompts": "0.11.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@modelcontextprotocol/sdk": "1.15.1",
|
"@modelcontextprotocol/sdk": "1.15.1",
|
||||||
|
@ -54,6 +56,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "2.2.10",
|
"@ai-sdk/amazon-bedrock": "2.2.10",
|
||||||
"@ai-sdk/anthropic": "1.2.12",
|
"@ai-sdk/anthropic": "1.2.12",
|
||||||
|
"@octokit/webhooks-types": "7.6.1",
|
||||||
"@standard-schema/spec": "1.0.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"@tsconfig/bun": "1.0.7",
|
"@tsconfig/bun": "1.0.7",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
@ -134,6 +137,16 @@
|
||||||
"zod": "3.25.49",
|
"zod": "3.25.49",
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
|
"@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
|
||||||
|
|
||||||
|
"@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="],
|
||||||
|
|
||||||
|
"@actions/github": ["@actions/github@6.0.1", "", { "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", "@octokit/plugin-paginate-rest": "^9.2.2", "@octokit/plugin-rest-endpoint-methods": "^10.4.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "undici": "^5.28.5" } }, "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw=="],
|
||||||
|
|
||||||
|
"@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
|
||||||
|
|
||||||
|
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
|
||||||
|
@ -512,6 +525,8 @@
|
||||||
|
|
||||||
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
|
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
|
||||||
|
|
||||||
|
"@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
|
||||||
|
|
||||||
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
|
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
|
||||||
|
|
||||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk"],
|
"@opencode-ai/sdk": ["@opencode-ai/sdk@workspace:packages/sdk"],
|
||||||
|
@ -1016,6 +1031,8 @@
|
||||||
|
|
||||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||||
|
|
||||||
|
"deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
|
||||||
|
|
||||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||||
|
|
||||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||||
|
@ -2158,6 +2175,8 @@
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
|
||||||
|
|
||||||
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
||||||
|
|
||||||
"turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
|
"turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
|
||||||
|
@ -2332,6 +2351,16 @@
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
||||||
|
@ -2604,6 +2633,28 @@
|
||||||
|
|
||||||
"yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
"yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/core/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/core/before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/request/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
||||||
|
|
||||||
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
|
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
|
||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
|
@ -2764,6 +2815,16 @@
|
||||||
|
|
||||||
"wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
|
"wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||||
|
|
||||||
|
"@actions/github/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||||
|
|
||||||
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||||
|
|
133
github/README.md
Normal file
133
github/README.md
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
# opencode GitHub Action
|
||||||
|
|
||||||
|
A GitHub Action that integrates [opencode](https://opencode.ai) directly into your GitHub workflow.
|
||||||
|
|
||||||
|
Start your comment with `hey opencode`, and opencode will take action via your GitHub Actions runner.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
#### Triage and explain issues
|
||||||
|
|
||||||
|
```
|
||||||
|
hey opencode, explain this issue
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fix or implement issues - opencode will create a PR with the changes.
|
||||||
|
|
||||||
|
```
|
||||||
|
hi opencode, fix this
|
||||||
|
```
|
||||||
|
|
||||||
|
- Review PRs and make changes
|
||||||
|
|
||||||
|
```
|
||||||
|
Delete the attachment from S3 when the note is removed @opencode-agent
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Run the following command in the terminal from your GitHub repo:
|
||||||
|
|
||||||
|
```
|
||||||
|
opencode github install
|
||||||
|
```
|
||||||
|
|
||||||
|
This will walk you through installing the GitHub app, configuring the workflow, and setting up secrets.
|
||||||
|
|
||||||
|
### Manual Setup
|
||||||
|
|
||||||
|
1. Install the GitHub app https://github.com/apps/opencode-agent. Make sure it is installed on the target repository.
|
||||||
|
2. Add the following workflow file to `.github/workflows/opencode.yml` in your repo. Set the appropriate `model` and required API keys in `env`.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
name: opencode
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
opencode:
|
||||||
|
if: |
|
||||||
|
startsWith(github.event.comment.body, 'opencode') ||
|
||||||
|
startsWith(github.event.comment.body, 'hi opencode') ||
|
||||||
|
startsWith(github.event.comment.body, 'hey opencode') ||
|
||||||
|
contains(github.event.comment.body, '@opencode-agent')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run opencode
|
||||||
|
uses: sst/opencode/github@latest
|
||||||
|
env:
|
||||||
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
with:
|
||||||
|
model: anthropic/claude-sonnet-4-20250514
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Store the API keys in secrets. In your organization or project **settings**, expand **Secrets and variables** on the left and select **Actions**. Add the required API keys.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
This is an early release. If you encounter issues or have feedback, please create an issue at https://github.com/sst/opencode/issues.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To test locally:
|
||||||
|
|
||||||
|
1. Navigate to a test repo (e.g. `hello-world`):
|
||||||
|
|
||||||
|
```
|
||||||
|
cd hello-world
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run:
|
||||||
|
|
||||||
|
```
|
||||||
|
MODEL=anthropic/claude-sonnet-4-20250514 \
|
||||||
|
ANTHROPIC_API_KEY=sk-ant-api03-1234567890 \
|
||||||
|
GITHUB_RUN_ID=dummy \
|
||||||
|
bun /path/to/opencode/packages/opencode/src/index.ts github run \
|
||||||
|
--token 'github_pat_1234567890' \
|
||||||
|
--event '{"eventName":"issue_comment",...}'
|
||||||
|
```
|
||||||
|
|
||||||
|
- `MODEL`: The model used by opencode. Same as the `MODEL` defined in the GitHub workflow.
|
||||||
|
- `ANTHROPIC_API_KEY`: Your model provider API key. Same as the keys defined in the GitHub workflow.
|
||||||
|
- `GITHUB_RUN_ID`: Dummy value to emulate GitHub action environment.
|
||||||
|
- `/path/to/opencode`: Path to your cloned opencode repo. `bun /path/to/opencode/packages/opencode/src/index.ts` runs your local version of `opencode`.
|
||||||
|
- `--token`: A GitHub persontal access token. This token is used to verify you have `admin` or `write` access to the test repo. Generate a token [here](https://github.com/settings/personal-access-tokens).
|
||||||
|
- `--event`: Mock GitHub event payload (see templates below).
|
||||||
|
|
||||||
|
#### Issue comment event
|
||||||
|
|
||||||
|
```
|
||||||
|
--event '{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4},"comment":{"id":1,"body":"hey opencode, summarize thread"}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace:
|
||||||
|
|
||||||
|
- `"owner":"sst"` with repo owner
|
||||||
|
- `"repo":"hello-world"` with repo name
|
||||||
|
- `"actor":"fwang"` with the GitHub username of commentor
|
||||||
|
- `"number":4` with the GitHub issue id
|
||||||
|
- `"body":"hey opencode, summarize thread"` with comment body
|
||||||
|
|
||||||
|
#### Issue comment with image attachment.
|
||||||
|
|
||||||
|
```
|
||||||
|
--event '{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4},"comment":{"id":1,"body":"hey opencode, what is in my image "}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the image URL `https://github.com/user-attachments/assets/xxxxxxxx` with a valid GitHub attachment (you can generate one by commenting with an image in any issue).
|
||||||
|
|
||||||
|
#### PR comment event
|
||||||
|
|
||||||
|
```
|
||||||
|
--event '{"eventName":"issue_comment","repo":{"owner":"sst","repo":"hello-world"},"actor":"fwang","payload":{"issue":{"number":4,"pull_request":{}},"comment":{"id":1,"body":"hey opencode, summarize thread"}}}'
|
||||||
|
```
|
29
github/action.yml
Normal file
29
github/action.yml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
name: "opencode GitHub Action"
|
||||||
|
description: "Run opencode in GitHub Actions workflows"
|
||||||
|
branding:
|
||||||
|
icon: "code"
|
||||||
|
color: "orange"
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
model:
|
||||||
|
description: "Model to use"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
share:
|
||||||
|
description: "Share the opencode session (defaults to true for public repos)"
|
||||||
|
required: false
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Install opencode
|
||||||
|
shell: bash
|
||||||
|
run: curl -fsSL https://opencode.ai/install | bash
|
||||||
|
|
||||||
|
- name: Run opencode
|
||||||
|
shell: bash
|
||||||
|
id: run_opencode
|
||||||
|
run: opencode github run
|
||||||
|
env:
|
||||||
|
MODEL: ${{ inputs.model }}
|
||||||
|
SHARE: ${{ inputs.share }}
|
|
@ -8,8 +8,8 @@ if [ -z "$latest_tag" ]; then
|
||||||
fi
|
fi
|
||||||
echo "Latest tag: $latest_tag"
|
echo "Latest tag: $latest_tag"
|
||||||
|
|
||||||
# Update github-v1 to latest
|
# Update latest tag
|
||||||
git tag -d github-v1
|
git tag -d latest
|
||||||
git push origin :refs/tags/github-v1
|
git push origin :refs/tags/latest
|
||||||
git tag -a github-v1 $latest_tag -m "Update github-v1 to $latest_tag"
|
git tag -a latest $latest_tag -m "Update latest to $latest_tag"
|
||||||
git push origin github-v1
|
git push origin latest
|
|
@ -17,6 +17,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "2.2.10",
|
"@ai-sdk/amazon-bedrock": "2.2.10",
|
||||||
"@ai-sdk/anthropic": "1.2.12",
|
"@ai-sdk/anthropic": "1.2.12",
|
||||||
|
"@octokit/webhooks-types": "7.6.1",
|
||||||
"@standard-schema/spec": "1.0.0",
|
"@standard-schema/spec": "1.0.0",
|
||||||
"@tsconfig/bun": "1.0.7",
|
"@tsconfig/bun": "1.0.7",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
@ -27,6 +28,8 @@
|
||||||
"zod-to-json-schema": "3.24.5"
|
"zod-to-json-schema": "3.24.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@actions/core": "1.11.1",
|
||||||
|
"@actions/github": "6.0.1",
|
||||||
"@clack/prompts": "0.11.0",
|
"@clack/prompts": "0.11.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@modelcontextprotocol/sdk": "1.15.1",
|
"@modelcontextprotocol/sdk": "1.15.1",
|
||||||
|
|
1094
packages/opencode/src/cli/cmd/github.ts
Normal file
1094
packages/opencode/src/cli/cmd/github.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,221 +0,0 @@
|
||||||
import { $ } from "bun"
|
|
||||||
import path from "path"
|
|
||||||
import { exec } from "child_process"
|
|
||||||
import * as prompts from "@clack/prompts"
|
|
||||||
import { map, pipe, sortBy, values } from "remeda"
|
|
||||||
import { UI } from "../ui"
|
|
||||||
import { cmd } from "./cmd"
|
|
||||||
import { ModelsDev } from "../../provider/models"
|
|
||||||
import { App } from "../../app/app"
|
|
||||||
|
|
||||||
const WORKFLOW_FILE = ".github/workflows/opencode.yml"
|
|
||||||
|
|
||||||
export const InstallGithubCommand = cmd({
|
|
||||||
command: "install-github",
|
|
||||||
describe: "install the GitHub agent",
|
|
||||||
async handler() {
|
|
||||||
await App.provide({ cwd: process.cwd() }, async () => {
|
|
||||||
UI.empty()
|
|
||||||
prompts.intro("Install GitHub agent")
|
|
||||||
const app = await getAppInfo()
|
|
||||||
await installGitHubApp()
|
|
||||||
|
|
||||||
const providers = await ModelsDev.get()
|
|
||||||
const provider = await promptProvider()
|
|
||||||
const model = await promptModel()
|
|
||||||
//const key = await promptKey()
|
|
||||||
|
|
||||||
await addWorkflowFiles()
|
|
||||||
printNextSteps()
|
|
||||||
|
|
||||||
function printNextSteps() {
|
|
||||||
let step2
|
|
||||||
if (provider === "amazon-bedrock") {
|
|
||||||
step2 =
|
|
||||||
"Configure OIDC in AWS - https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"
|
|
||||||
} else {
|
|
||||||
const url = `https://github.com/organizations/${app.owner}/settings/secrets/actions`
|
|
||||||
const env = providers[provider].env
|
|
||||||
const envStr =
|
|
||||||
env.length === 1
|
|
||||||
? `\`${env[0]}\` secret`
|
|
||||||
: `\`${[env.slice(0, -1).join("\`, \`"), ...env.slice(-1)].join("\` and \`")}\` secrets`
|
|
||||||
step2 = `Add ${envStr} for ${providers[provider].name} - ${url}`
|
|
||||||
}
|
|
||||||
|
|
||||||
prompts.outro(
|
|
||||||
[
|
|
||||||
"Next steps:",
|
|
||||||
` 1. Commit "${WORKFLOW_FILE}" file and push`,
|
|
||||||
` 2. ${step2}`,
|
|
||||||
" 3. Learn how to use the GitHub agent - https://docs.opencode.ai/docs/github/getting-started",
|
|
||||||
].join("\n"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAppInfo() {
|
|
||||||
const app = App.info()
|
|
||||||
if (!app.git) {
|
|
||||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
|
||||||
throw new UI.CancelledError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get repo info
|
|
||||||
const info = await $`git remote get-url origin`.quiet().nothrow().text()
|
|
||||||
// match https or git pattern
|
|
||||||
// ie. https://github.com/sst/opencode.git
|
|
||||||
// ie. git@github.com:sst/opencode.git
|
|
||||||
const parsed = info.match(/git@github\.com:(.*)\.git/) ?? info.match(/github\.com\/(.*)\.git/)
|
|
||||||
if (!parsed) {
|
|
||||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
|
||||||
throw new UI.CancelledError()
|
|
||||||
}
|
|
||||||
const [owner, repo] = parsed[1].split("/")
|
|
||||||
return { owner, repo, root: app.path.root }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function promptProvider() {
|
|
||||||
const priority: Record<string, number> = {
|
|
||||||
anthropic: 0,
|
|
||||||
"github-copilot": 1,
|
|
||||||
openai: 2,
|
|
||||||
google: 3,
|
|
||||||
}
|
|
||||||
let provider = await prompts.select({
|
|
||||||
message: "Select provider",
|
|
||||||
maxItems: 8,
|
|
||||||
options: pipe(
|
|
||||||
providers,
|
|
||||||
values(),
|
|
||||||
sortBy(
|
|
||||||
(x) => priority[x.id] ?? 99,
|
|
||||||
(x) => x.name ?? x.id,
|
|
||||||
),
|
|
||||||
map((x) => ({
|
|
||||||
label: x.name,
|
|
||||||
value: x.id,
|
|
||||||
hint: priority[x.id] === 0 ? "recommended" : undefined,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
|
||||||
|
|
||||||
return provider
|
|
||||||
}
|
|
||||||
|
|
||||||
async function promptModel() {
|
|
||||||
const providerData = providers[provider]!
|
|
||||||
|
|
||||||
const model = await prompts.select({
|
|
||||||
message: "Select model",
|
|
||||||
maxItems: 8,
|
|
||||||
options: pipe(
|
|
||||||
providerData.models,
|
|
||||||
values(),
|
|
||||||
sortBy((x) => x.name ?? x.id),
|
|
||||||
map((x) => ({
|
|
||||||
label: x.name ?? x.id,
|
|
||||||
value: x.id,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (prompts.isCancel(model)) throw new UI.CancelledError()
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
|
|
||||||
async function installGitHubApp() {
|
|
||||||
const s = prompts.spinner()
|
|
||||||
s.start("Installing GitHub app")
|
|
||||||
|
|
||||||
// Get installation
|
|
||||||
const installation = await getInstallation()
|
|
||||||
if (installation) return s.stop("GitHub app already installed")
|
|
||||||
|
|
||||||
// Open browser
|
|
||||||
const url = "https://github.com/apps/opencode-agent"
|
|
||||||
const command =
|
|
||||||
process.platform === "darwin"
|
|
||||||
? `open "${url}"`
|
|
||||||
: process.platform === "win32"
|
|
||||||
? `start "${url}"`
|
|
||||||
: `xdg-open "${url}"`
|
|
||||||
|
|
||||||
exec(command, (error) => {
|
|
||||||
if (error) {
|
|
||||||
prompts.log.warn(`Could not open browser. Please visit: ${url}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait for installation
|
|
||||||
s.message("Waiting for GitHub app to be installed")
|
|
||||||
const MAX_RETRIES = 60
|
|
||||||
let retries = 0
|
|
||||||
do {
|
|
||||||
const installation = await getInstallation()
|
|
||||||
if (installation) break
|
|
||||||
|
|
||||||
if (retries > MAX_RETRIES) {
|
|
||||||
s.stop(
|
|
||||||
`Failed to detect GitHub app installation. Make sure to install the app for the \`${app.owner}/${app.repo}\` repository.`,
|
|
||||||
)
|
|
||||||
throw new UI.CancelledError()
|
|
||||||
}
|
|
||||||
|
|
||||||
retries++
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
} while (true)
|
|
||||||
|
|
||||||
s.stop("Installed GitHub app")
|
|
||||||
|
|
||||||
async function getInstallation() {
|
|
||||||
return await fetch(`https://api.opencode.ai/get_github_app_installation?owner=${app.owner}&repo=${app.repo}`)
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => data.installation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addWorkflowFiles() {
|
|
||||||
const envStr =
|
|
||||||
provider === "amazon-bedrock"
|
|
||||||
? ""
|
|
||||||
: `\n env:${providers[provider].env.map((e) => `\n ${e}: \${{ secrets.${e} }}`).join("")}`
|
|
||||||
|
|
||||||
await Bun.write(
|
|
||||||
path.join(app.root, WORKFLOW_FILE),
|
|
||||||
`
|
|
||||||
name: opencode
|
|
||||||
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
opencode:
|
|
||||||
if: |
|
|
||||||
startsWith(github.event.comment.body, 'opencode') ||
|
|
||||||
startsWith(github.event.comment.body, 'hi opencode') ||
|
|
||||||
startsWith(github.event.comment.body, 'hey opencode') ||
|
|
||||||
contains(github.event.comment.body, '@opencode-agent')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Run opencode
|
|
||||||
uses: sst/opencode/sdks/github@github-v1${envStr}
|
|
||||||
with:
|
|
||||||
model: ${provider}/${model}
|
|
||||||
`.trim(),
|
|
||||||
)
|
|
||||||
|
|
||||||
prompts.log.success(`Added workflow file: "${WORKFLOW_FILE}"`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -17,7 +17,7 @@ import { TuiCommand } from "./cli/cmd/tui"
|
||||||
import { DebugCommand } from "./cli/cmd/debug"
|
import { DebugCommand } from "./cli/cmd/debug"
|
||||||
import { StatsCommand } from "./cli/cmd/stats"
|
import { StatsCommand } from "./cli/cmd/stats"
|
||||||
import { McpCommand } from "./cli/cmd/mcp"
|
import { McpCommand } from "./cli/cmd/mcp"
|
||||||
import { InstallGithubCommand } from "./cli/cmd/install-github"
|
import { GithubCommand } from "./cli/cmd/github"
|
||||||
import { Trace } from "./trace"
|
import { Trace } from "./trace"
|
||||||
|
|
||||||
Trace.init()
|
Trace.init()
|
||||||
|
@ -78,7 +78,7 @@ const cli = yargs(hideBin(process.argv))
|
||||||
.command(ServeCommand)
|
.command(ServeCommand)
|
||||||
.command(ModelsCommand)
|
.command(ModelsCommand)
|
||||||
.command(StatsCommand)
|
.command(StatsCommand)
|
||||||
.command(InstallGithubCommand)
|
.command(GithubCommand)
|
||||||
.fail((msg) => {
|
.fail((msg) => {
|
||||||
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
||||||
cli.showHelp("log")
|
cli.showHelp("log")
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
name: "opencode GitHub Action"
|
|
||||||
description: "Run opencode in GitHub Actions workflows"
|
|
||||||
branding:
|
|
||||||
icon: "code"
|
|
||||||
color: "orange"
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
model:
|
|
||||||
description: "Model to use"
|
|
||||||
required: false
|
|
||||||
|
|
||||||
share:
|
|
||||||
description: "Share the opencode session (defaults to true for public repos)"
|
|
||||||
required: false
|
|
||||||
|
|
||||||
outputs:
|
|
||||||
share_url:
|
|
||||||
description: "URL to share the opencode execution"
|
|
||||||
value: ${{ steps.run_opencode.outputs.share_url }}
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Install Bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
with:
|
|
||||||
bun-version: 1.2.16
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd ${GITHUB_ACTION_PATH}
|
|
||||||
bun install
|
|
||||||
|
|
||||||
- name: Install opencode
|
|
||||||
shell: bash
|
|
||||||
run: curl -fsSL https://opencode.ai/install | bash
|
|
||||||
|
|
||||||
- name: Run opencode
|
|
||||||
shell: bash
|
|
||||||
id: run_opencode
|
|
||||||
run: |
|
|
||||||
bun run ${GITHUB_ACTION_PATH}/src/index.ts
|
|
||||||
env:
|
|
||||||
INPUT_MODEL: ${{ inputs.model }}
|
|
||||||
INPUT_SHARE: ${{ inputs.share }}
|
|
||||||
|
|
||||||
#- name: Testing
|
|
||||||
# shell: bash
|
|
||||||
# run: |
|
|
||||||
# gh pr comment ${{ github.event.number }} --body "This is an automated comment"
|
|
||||||
# env:
|
|
||||||
# GH_TOKEN: ${{ github.token }}
|
|
|
@ -1,157 +0,0 @@
|
||||||
{
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"workspaces": {
|
|
||||||
"": {
|
|
||||||
"name": "github",
|
|
||||||
"dependencies": {
|
|
||||||
"@actions/core": "^1.11.1",
|
|
||||||
"@actions/github": "^6.0.1",
|
|
||||||
"@octokit/graphql": "^9.0.1",
|
|
||||||
"@octokit/rest": "^22.0.0",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@octokit/webhooks-types": "^7.6.1",
|
|
||||||
"@types/bun": "latest",
|
|
||||||
"@types/node": "^24.0.10",
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": "^5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"packages": {
|
|
||||||
"@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
|
|
||||||
|
|
||||||
"@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="],
|
|
||||||
|
|
||||||
"@actions/github": ["@actions/github@6.0.1", "", { "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", "@octokit/plugin-paginate-rest": "^9.2.2", "@octokit/plugin-rest-endpoint-methods": "^10.4.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "undici": "^5.28.5" } }, "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw=="],
|
|
||||||
|
|
||||||
"@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
|
|
||||||
|
|
||||||
"@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
|
|
||||||
|
|
||||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
|
||||||
|
|
||||||
"@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
|
|
||||||
|
|
||||||
"@octokit/core": ["@octokit/core@5.2.2", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg=="],
|
|
||||||
|
|
||||||
"@octokit/endpoint": ["@octokit/endpoint@9.0.6", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
|
|
||||||
|
|
||||||
"@octokit/graphql": ["@octokit/graphql@9.0.1", "", { "dependencies": { "@octokit/request": "^10.0.2", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg=="],
|
|
||||||
|
|
||||||
"@octokit/openapi-types": ["@octokit/openapi-types@25.1.0", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="],
|
|
||||||
|
|
||||||
"@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="],
|
|
||||||
|
|
||||||
"@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="],
|
|
||||||
|
|
||||||
"@octokit/rest": ["@octokit/rest@22.0.0", "", { "dependencies": { "@octokit/core": "^7.0.2", "@octokit/plugin-paginate-rest": "^13.0.1", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0" } }, "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA=="],
|
|
||||||
|
|
||||||
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
|
|
||||||
|
|
||||||
"@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
|
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
|
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="],
|
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
|
||||||
|
|
||||||
"before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
|
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
|
|
||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
|
||||||
|
|
||||||
"deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
|
|
||||||
|
|
||||||
"fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
|
|
||||||
|
|
||||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
|
||||||
|
|
||||||
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
|
|
||||||
|
|
||||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
|
||||||
|
|
||||||
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
|
||||||
|
|
||||||
"universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],
|
|
||||||
|
|
||||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
|
||||||
|
|
||||||
"@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
|
|
||||||
|
|
||||||
"@octokit/core/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
|
||||||
|
|
||||||
"@octokit/core/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
|
||||||
|
|
||||||
"@octokit/endpoint/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
|
||||||
|
|
||||||
"@octokit/endpoint/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
|
||||||
|
|
||||||
"@octokit/graphql/@octokit/request": ["@octokit/request@10.0.3", "", { "dependencies": { "@octokit/endpoint": "^11.0.0", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-request-log/@octokit/core": ["@octokit/core@7.0.3", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
|
|
||||||
|
|
||||||
"@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
|
||||||
|
|
||||||
"@octokit/request/universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
|
|
||||||
|
|
||||||
"@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core": ["@octokit/core@7.0.3", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.1", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.1.1", "", { "dependencies": { "@octokit/types": "^14.1.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.0.0", "", { "dependencies": { "@octokit/types": "^14.1.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g=="],
|
|
||||||
|
|
||||||
"@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
|
||||||
|
|
||||||
"@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
|
||||||
|
|
||||||
"@octokit/graphql/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ=="],
|
|
||||||
|
|
||||||
"@octokit/graphql/@octokit/request/@octokit/request-error": ["@octokit/request-error@7.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-request-log/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-request-log/@octokit/core/@octokit/request": ["@octokit/request@10.0.3", "", { "dependencies": { "@octokit/endpoint": "^11.0.0", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-request-log/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-request-log/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
|
|
||||||
|
|
||||||
"@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
|
||||||
|
|
||||||
"@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core/@octokit/request": ["@octokit/request@10.0.3", "", { "dependencies": { "@octokit/endpoint": "^11.0.0", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "fast-content-type-parse": "^3.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
|
|
||||||
|
|
||||||
"@octokit/plugin-request-log/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ=="],
|
|
||||||
|
|
||||||
"@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.0", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ=="],
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "github",
|
|
||||||
"type": "module",
|
|
||||||
"private": true,
|
|
||||||
"devDependencies": {
|
|
||||||
"@octokit/webhooks-types": "^7.6.1",
|
|
||||||
"@types/bun": "latest",
|
|
||||||
"@types/node": "^24.0.10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": "^5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@actions/core": "^1.11.1",
|
|
||||||
"@actions/github": "^6.0.1",
|
|
||||||
"@octokit/graphql": "^9.0.1",
|
|
||||||
"@octokit/rest": "^22.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,541 +0,0 @@
|
||||||
#!/usr/bin/env bun
|
|
||||||
|
|
||||||
import os from "os"
|
|
||||||
import path from "path"
|
|
||||||
import { $ } from "bun"
|
|
||||||
import { Octokit } from "@octokit/rest"
|
|
||||||
import { graphql } from "@octokit/graphql"
|
|
||||||
import * as core from "@actions/core"
|
|
||||||
import * as github from "@actions/github"
|
|
||||||
import type { IssueCommentEvent } from "@octokit/webhooks-types"
|
|
||||||
import type { GitHubIssue, GitHubPullRequest, IssueQueryResponse, PullRequestQueryResponse } from "./types"
|
|
||||||
|
|
||||||
if (github.context.eventName !== "issue_comment") {
|
|
||||||
core.setFailed(`Unsupported event type: ${github.context.eventName}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { owner, repo } = github.context.repo
|
|
||||||
const payload = github.context.payload as IssueCommentEvent
|
|
||||||
const actor = github.context.actor
|
|
||||||
const issueId = payload.issue.number
|
|
||||||
const body = payload.comment.body
|
|
||||||
|
|
||||||
let appToken: string
|
|
||||||
let octoRest: Octokit
|
|
||||||
let octoGraph: typeof graphql
|
|
||||||
let commentId: number
|
|
||||||
let gitCredentials: string
|
|
||||||
let shareUrl: string | undefined
|
|
||||||
let state:
|
|
||||||
| {
|
|
||||||
type: "issue"
|
|
||||||
issue: GitHubIssue
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "local-pr"
|
|
||||||
pr: GitHubPullRequest
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "fork-pr"
|
|
||||||
pr: GitHubPullRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
try {
|
|
||||||
const match = body.match(/^hey\s*opencode,/)
|
|
||||||
if (!match?.[1]) throw new Error("Command must start with `hey opencode,`")
|
|
||||||
const userPrompt = match[1]
|
|
||||||
|
|
||||||
const oidcToken = await generateGitHubToken()
|
|
||||||
appToken = await exchangeForAppToken(oidcToken)
|
|
||||||
octoRest = new Octokit({ auth: appToken })
|
|
||||||
octoGraph = graphql.defaults({
|
|
||||||
headers: { authorization: `token ${appToken}` },
|
|
||||||
})
|
|
||||||
|
|
||||||
await configureGit(appToken)
|
|
||||||
await assertPermissions()
|
|
||||||
|
|
||||||
const comment = await createComment("opencode started...")
|
|
||||||
commentId = comment.data.id
|
|
||||||
|
|
||||||
// Set state
|
|
||||||
const repoData = await fetchRepo()
|
|
||||||
if (payload.issue.pull_request) {
|
|
||||||
const prData = await fetchPR()
|
|
||||||
state = {
|
|
||||||
type: prData.headRepository.nameWithOwner === prData.baseRepository.nameWithOwner ? "local-pr" : "fork-pr",
|
|
||||||
pr: prData,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state = {
|
|
||||||
type: "issue",
|
|
||||||
issue: await fetchIssue(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup git branch
|
|
||||||
if (state.type === "local-pr") await checkoutLocalBranch(state.pr)
|
|
||||||
else if (state.type === "fork-pr") await checkoutForkBranch(state.pr)
|
|
||||||
|
|
||||||
// Prompt
|
|
||||||
const share = process.env.INPUT_SHARE === "true" || !repoData.data.private
|
|
||||||
const promptData = state.type === "issue" ? buildPromptDataForIssue(state.issue) : buildPromptDataForPR(state.pr)
|
|
||||||
const responseRet = await runOpencode(`${userPrompt}\n\n${promptData}`, {
|
|
||||||
share,
|
|
||||||
})
|
|
||||||
|
|
||||||
const response = responseRet.stdout
|
|
||||||
shareUrl = responseRet.stderr.match(/https:\/\/opencode\.ai\/s\/\w+/)?.[0]
|
|
||||||
|
|
||||||
// Comment and push changes
|
|
||||||
if (await branchIsDirty()) {
|
|
||||||
const summary =
|
|
||||||
(await runOpencode(`Summarize the following in less than 40 characters:\n\n${response}`, { share: false }))
|
|
||||||
?.stdout || `Fix issue: ${payload.issue.title}`
|
|
||||||
|
|
||||||
if (state.type === "issue") {
|
|
||||||
const branch = await pushToNewBranch(summary)
|
|
||||||
const pr = await createPR(repoData.data.default_branch, branch, summary, `${response}\n\nCloses #${issueId}`)
|
|
||||||
await updateComment(`opencode created pull request #${pr}`)
|
|
||||||
} else if (state.type === "local-pr") {
|
|
||||||
await pushToCurrentBranch(summary)
|
|
||||||
await updateComment(response)
|
|
||||||
} else if (state.type === "fork-pr") {
|
|
||||||
await pushToForkBranch(summary, state.pr)
|
|
||||||
await updateComment(response)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await updateComment(response)
|
|
||||||
}
|
|
||||||
await restoreGitConfig()
|
|
||||||
await revokeAppToken()
|
|
||||||
} catch (e: any) {
|
|
||||||
await restoreGitConfig()
|
|
||||||
await revokeAppToken()
|
|
||||||
console.error(e)
|
|
||||||
let msg = e
|
|
||||||
if (e instanceof $.ShellError) {
|
|
||||||
msg = e.stderr.toString()
|
|
||||||
} else if (e instanceof Error) {
|
|
||||||
msg = e.message
|
|
||||||
}
|
|
||||||
if (commentId) await updateComment(msg)
|
|
||||||
core.setFailed(`opencode failed with error: ${msg}`)
|
|
||||||
// Also output the clean error message for the action to capture
|
|
||||||
//core.setOutput("prepare_error", e.message);
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.main) {
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateGitHubToken() {
|
|
||||||
try {
|
|
||||||
return await core.getIDToken("opencode-github-action")
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to get OIDC token:", error)
|
|
||||||
throw new Error("Could not fetch an OIDC token. Make sure to add `id-token: write` to your workflow permissions.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function exchangeForAppToken(oidcToken: string) {
|
|
||||||
const response = await fetch("https://api.opencode.ai/exchange_github_app_token", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${oidcToken}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const responseJson = (await response.json()) as { error?: string }
|
|
||||||
throw new Error(`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson.error}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseJson = (await response.json()) as { token: string }
|
|
||||||
return responseJson.token
|
|
||||||
}
|
|
||||||
|
|
||||||
async function configureGit(appToken: string) {
|
|
||||||
console.log("Configuring git...")
|
|
||||||
const config = "http.https://github.com/.extraheader"
|
|
||||||
const ret = await $`git config --local --get ${config}`
|
|
||||||
gitCredentials = ret.stdout.toString().trim()
|
|
||||||
|
|
||||||
const newCredentials = Buffer.from(`x-access-token:${appToken}`, "utf8").toString("base64")
|
|
||||||
|
|
||||||
await $`git config --local --unset-all ${config}`
|
|
||||||
await $`git config --local ${config} "AUTHORIZATION: basic ${newCredentials}"`
|
|
||||||
await $`git config --global user.name "opencode-agent[bot]"`
|
|
||||||
await $`git config --global user.email "opencode-agent[bot]@users.noreply.github.com"`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkoutLocalBranch(pr: GitHubPullRequest) {
|
|
||||||
console.log("Checking out local branch...")
|
|
||||||
|
|
||||||
const branch = pr.headRefName
|
|
||||||
const depth = Math.max(pr.commits.totalCount, 20)
|
|
||||||
|
|
||||||
await $`git fetch origin --depth=${depth} ${branch}`
|
|
||||||
await $`git checkout ${branch}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkoutForkBranch(pr: GitHubPullRequest) {
|
|
||||||
console.log("Checking out fork branch...")
|
|
||||||
|
|
||||||
const remoteBranch = pr.headRefName
|
|
||||||
const localBranch = generateBranchName()
|
|
||||||
const depth = Math.max(pr.commits.totalCount, 20)
|
|
||||||
|
|
||||||
await $`git remote add fork https://github.com/${pr.headRepository.nameWithOwner}.git`
|
|
||||||
await $`git fetch fork --depth=${depth} ${remoteBranch}`
|
|
||||||
await $`git checkout -b ${localBranch} fork/${remoteBranch}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function restoreGitConfig() {
|
|
||||||
if (!gitCredentials) return
|
|
||||||
const config = "http.https://github.com/.extraheader"
|
|
||||||
await $`git config --local ${config} "${gitCredentials}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function assertPermissions() {
|
|
||||||
console.log(`Asserting permissions for user ${actor}...`)
|
|
||||||
|
|
||||||
let permission
|
|
||||||
try {
|
|
||||||
const response = await octoRest.repos.getCollaboratorPermissionLevel({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
username: actor,
|
|
||||||
})
|
|
||||||
|
|
||||||
permission = response.data.permission
|
|
||||||
console.log(` permission: ${permission}`)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to check permissions: ${error}`)
|
|
||||||
throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildComment(content: string) {
|
|
||||||
const runId = process.env.GITHUB_RUN_ID!
|
|
||||||
const runUrl = `/${owner}/${repo}/actions/runs/${runId}`
|
|
||||||
return [content, "\n\n", shareUrl ? `[view session](${shareUrl}) | ` : "", `[view log](${runUrl})`].join("")
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createComment(body: string) {
|
|
||||||
console.log("Creating comment...")
|
|
||||||
return await octoRest.rest.issues.createComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: issueId,
|
|
||||||
body: buildComment(body),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateComment(body: string) {
|
|
||||||
console.log("Updating comment...")
|
|
||||||
return await octoRest.rest.issues.updateComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
body: buildComment(body),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateBranchName() {
|
|
||||||
const type = state.type === "issue" ? "issue" : "pr"
|
|
||||||
const timestamp = new Date()
|
|
||||||
.toISOString()
|
|
||||||
.replace(/[:-]/g, "")
|
|
||||||
.replace(/\.\d{3}Z/, "")
|
|
||||||
.split("T")
|
|
||||||
.join("_")
|
|
||||||
return `opencode/${type}${issueId}-${timestamp}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pushToCurrentBranch(summary: string) {
|
|
||||||
console.log("Pushing to current branch...")
|
|
||||||
await $`git add .`
|
|
||||||
await $`git commit -m "${summary}
|
|
||||||
|
|
||||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
|
||||||
await $`git push`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pushToForkBranch(summary: string, pr: GitHubPullRequest) {
|
|
||||||
console.log("Pushing to fork branch...")
|
|
||||||
|
|
||||||
const remoteBranch = pr.headRefName
|
|
||||||
|
|
||||||
await $`git add .`
|
|
||||||
await $`git commit -m "${summary}
|
|
||||||
|
|
||||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
|
||||||
await $`git push fork HEAD:${remoteBranch}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pushToNewBranch(summary: string) {
|
|
||||||
console.log("Pushing to new branch...")
|
|
||||||
const branch = generateBranchName()
|
|
||||||
await $`git checkout -b ${branch}`
|
|
||||||
await $`git add .`
|
|
||||||
await $`git commit -m "${summary}
|
|
||||||
|
|
||||||
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
|
|
||||||
await $`git push -u origin ${branch}`
|
|
||||||
return branch
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createPR(base: string, branch: string, title: string, body: string) {
|
|
||||||
console.log("Creating pull request...")
|
|
||||||
const pr = await octoRest.rest.pulls.create({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
head: branch,
|
|
||||||
base,
|
|
||||||
title,
|
|
||||||
body: buildComment(body),
|
|
||||||
})
|
|
||||||
return pr.data.number
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runOpencode(
|
|
||||||
prompt: string,
|
|
||||||
opts?: {
|
|
||||||
share?: boolean
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
console.log("Running opencode...")
|
|
||||||
|
|
||||||
const promptPath = path.join(os.tmpdir(), "PROMPT")
|
|
||||||
await Bun.write(promptPath, prompt)
|
|
||||||
const ret = await $`cat ${promptPath} | opencode run -m ${process.env.INPUT_MODEL} ${opts?.share ? "--share" : ""}`
|
|
||||||
return {
|
|
||||||
stdout: ret.stdout.toString().trim(),
|
|
||||||
stderr: ret.stderr.toString().trim(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function branchIsDirty() {
|
|
||||||
console.log("Checking if branch is dirty...")
|
|
||||||
const ret = await $`git status --porcelain`
|
|
||||||
return ret.stdout.toString().trim().length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchRepo() {
|
|
||||||
return await octoRest.rest.repos.get({ owner, repo })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchIssue() {
|
|
||||||
console.log("Fetching prompt data for issue...")
|
|
||||||
const issueResult = await octoGraph<IssueQueryResponse>(
|
|
||||||
`
|
|
||||||
query($owner: String!, $repo: String!, $number: Int!) {
|
|
||||||
repository(owner: $owner, name: $repo) {
|
|
||||||
issue(number: $number) {
|
|
||||||
title
|
|
||||||
body
|
|
||||||
author {
|
|
||||||
login
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
state
|
|
||||||
comments(first: 100) {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
databaseId
|
|
||||||
body
|
|
||||||
author {
|
|
||||||
login
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
{
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
number: issueId,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const issue = issueResult.repository.issue
|
|
||||||
if (!issue) throw new Error(`Issue #${issueId} not found`)
|
|
||||||
|
|
||||||
return issue
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPromptDataForIssue(issue: GitHubIssue) {
|
|
||||||
const comments = (issue.comments?.nodes || [])
|
|
||||||
.filter((c) => {
|
|
||||||
const id = parseInt(c.databaseId)
|
|
||||||
return id !== commentId && id !== payload.comment.id
|
|
||||||
})
|
|
||||||
.map((c) => ` - ${c.author.login} at ${c.createdAt}: ${c.body}`)
|
|
||||||
|
|
||||||
return [
|
|
||||||
"Here is the context for the issue:",
|
|
||||||
`- Title: ${issue.title}`,
|
|
||||||
`- Body: ${issue.body}`,
|
|
||||||
`- Author: ${issue.author.login}`,
|
|
||||||
`- Created At: ${issue.createdAt}`,
|
|
||||||
`- State: ${issue.state}`,
|
|
||||||
...(comments.length > 0 ? ["- Comments:", ...comments] : []),
|
|
||||||
].join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchPR() {
|
|
||||||
console.log("Fetching prompt data for PR...")
|
|
||||||
const prResult = await octoGraph<PullRequestQueryResponse>(
|
|
||||||
`
|
|
||||||
query($owner: String!, $repo: String!, $number: Int!) {
|
|
||||||
repository(owner: $owner, name: $repo) {
|
|
||||||
pullRequest(number: $number) {
|
|
||||||
title
|
|
||||||
body
|
|
||||||
author {
|
|
||||||
login
|
|
||||||
}
|
|
||||||
baseRefName
|
|
||||||
headRefName
|
|
||||||
headRefOid
|
|
||||||
createdAt
|
|
||||||
additions
|
|
||||||
deletions
|
|
||||||
state
|
|
||||||
baseRepository {
|
|
||||||
nameWithOwner
|
|
||||||
}
|
|
||||||
headRepository {
|
|
||||||
nameWithOwner
|
|
||||||
}
|
|
||||||
commits(first: 100) {
|
|
||||||
totalCount
|
|
||||||
nodes {
|
|
||||||
commit {
|
|
||||||
oid
|
|
||||||
message
|
|
||||||
author {
|
|
||||||
name
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files(first: 100) {
|
|
||||||
nodes {
|
|
||||||
path
|
|
||||||
additions
|
|
||||||
deletions
|
|
||||||
changeType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
comments(first: 100) {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
databaseId
|
|
||||||
body
|
|
||||||
author {
|
|
||||||
login
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reviews(first: 100) {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
databaseId
|
|
||||||
author {
|
|
||||||
login
|
|
||||||
}
|
|
||||||
body
|
|
||||||
state
|
|
||||||
submittedAt
|
|
||||||
comments(first: 100) {
|
|
||||||
nodes {
|
|
||||||
id
|
|
||||||
databaseId
|
|
||||||
body
|
|
||||||
path
|
|
||||||
line
|
|
||||||
author {
|
|
||||||
login
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
{
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
number: issueId,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const pr = prResult.repository.pullRequest
|
|
||||||
if (!pr) throw new Error(`PR #${issueId} not found`)
|
|
||||||
|
|
||||||
return pr
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPromptDataForPR(pr: GitHubPullRequest) {
|
|
||||||
const comments = (pr.comments?.nodes || [])
|
|
||||||
.filter((c) => {
|
|
||||||
const id = parseInt(c.databaseId)
|
|
||||||
return id !== commentId && id !== payload.comment.id
|
|
||||||
})
|
|
||||||
.map((c) => ` - ${c.author.login} at ${c.createdAt}: ${c.body}`)
|
|
||||||
|
|
||||||
const files = (pr.files.nodes || []).map((f) => ` - ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`)
|
|
||||||
const reviewData = (pr.reviews.nodes || []).map((r) => {
|
|
||||||
const comments = (r.comments.nodes || []).map((c) => ` - ${c.path}:${c.line ?? "?"}: ${c.body}`)
|
|
||||||
return [
|
|
||||||
` - ${r.author.login} at ${r.submittedAt}:`,
|
|
||||||
` - Review body: ${r.body}`,
|
|
||||||
...(comments.length > 0 ? [" - Comments:", ...comments] : []),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
return [
|
|
||||||
"Here is the context for the pull request:",
|
|
||||||
`- Title: ${pr.title}`,
|
|
||||||
`- Body: ${pr.body}`,
|
|
||||||
`- Author: ${pr.author.login}`,
|
|
||||||
`- Created At: ${pr.createdAt}`,
|
|
||||||
`- Base Branch: ${pr.baseRefName}`,
|
|
||||||
`- Head Branch: ${pr.headRefName}`,
|
|
||||||
`- State: ${pr.state}`,
|
|
||||||
`- Additions: ${pr.additions}`,
|
|
||||||
`- Deletions: ${pr.deletions}`,
|
|
||||||
`- Total Commits: ${pr.commits.totalCount}`,
|
|
||||||
`- Changed Files: ${pr.files.nodes.length} files`,
|
|
||||||
...(comments.length > 0 ? ["- Comments:", ...comments] : []),
|
|
||||||
...(files.length > 0 ? ["- Changed files:", ...files] : []),
|
|
||||||
...(reviewData.length > 0 ? ["- Reviews:", ...reviewData] : []),
|
|
||||||
].join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
async function revokeAppToken() {
|
|
||||||
if (!appToken) return
|
|
||||||
|
|
||||||
await fetch("https://api.github.com/installation/token", {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${appToken}`,
|
|
||||||
Accept: "application/vnd.github+json",
|
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
// Types for GitHub GraphQL query responses
|
|
||||||
export type GitHubAuthor = {
|
|
||||||
login: string;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitHubComment = {
|
|
||||||
id: string;
|
|
||||||
databaseId: string;
|
|
||||||
body: string;
|
|
||||||
author: GitHubAuthor;
|
|
||||||
createdAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitHubReviewComment = GitHubComment & {
|
|
||||||
path: string;
|
|
||||||
line: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitHubCommit = {
|
|
||||||
oid: string;
|
|
||||||
message: string;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitHubFile = {
|
|
||||||
path: string;
|
|
||||||
additions: number;
|
|
||||||
deletions: number;
|
|
||||||
changeType: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitHubReview = {
|
|
||||||
id: string;
|
|
||||||
databaseId: string;
|
|
||||||
author: GitHubAuthor;
|
|
||||||
body: string;
|
|
||||||
state: string;
|
|
||||||
submittedAt: string;
|
|
||||||
comments: {
|
|
||||||
nodes: GitHubReviewComment[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitHubPullRequest = {
|
|
||||||
title: string;
|
|
||||||
body: string;
|
|
||||||
author: GitHubAuthor;
|
|
||||||
baseRefName: string;
|
|
||||||
headRefName: string;
|
|
||||||
headRefOid: string;
|
|
||||||
createdAt: string;
|
|
||||||
additions: number;
|
|
||||||
deletions: number;
|
|
||||||
state: string;
|
|
||||||
baseRepository: {
|
|
||||||
nameWithOwner: string;
|
|
||||||
};
|
|
||||||
headRepository: {
|
|
||||||
nameWithOwner: string;
|
|
||||||
};
|
|
||||||
commits: {
|
|
||||||
totalCount: number;
|
|
||||||
nodes: Array<{
|
|
||||||
commit: GitHubCommit;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
files: {
|
|
||||||
nodes: GitHubFile[];
|
|
||||||
};
|
|
||||||
comments: {
|
|
||||||
nodes: GitHubComment[];
|
|
||||||
};
|
|
||||||
reviews: {
|
|
||||||
nodes: GitHubReview[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type GitHubIssue = {
|
|
||||||
title: string;
|
|
||||||
body: string;
|
|
||||||
author: GitHubAuthor;
|
|
||||||
createdAt: string;
|
|
||||||
state: string;
|
|
||||||
comments: {
|
|
||||||
nodes: GitHubComment[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PullRequestQueryResponse = {
|
|
||||||
repository: {
|
|
||||||
pullRequest: GitHubPullRequest;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type IssueQueryResponse = {
|
|
||||||
repository: {
|
|
||||||
issue: GitHubIssue;
|
|
||||||
};
|
|
||||||
};
|
|
9
sdks/github/sst-env.d.ts
vendored
9
sdks/github/sst-env.d.ts
vendored
|
@ -1,9 +0,0 @@
|
||||||
/* This file is auto-generated by SST. Do not edit. */
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
/* deno-fmt-ignore-file */
|
|
||||||
|
|
||||||
/// <reference path="../../sst-env.d.ts" />
|
|
||||||
|
|
||||||
import "sst"
|
|
||||||
export {}
|
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
// Environment setup & latest features
|
|
||||||
"lib": ["ESNext"],
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"allowJs": true,
|
|
||||||
|
|
||||||
// Bundler mode
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
// Best practices
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
|
|
||||||
// Some stricter flags (disabled by default)
|
|
||||||
"noUnusedLocals": false,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noPropertyAccessFromIndexSignature": false
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue