Răsfoiți Sursa

wip: github actions

Frank 9 luni în urmă
părinte
comite
7361a02ef3

+ 58 - 0
sdks/github/action.yml

@@ -0,0 +1,58 @@
+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 }}

+ 157 - 0
sdks/github/bun.lock

@@ -0,0 +1,157 @@
+{
+  "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/[email protected]", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="],
+
+    "@actions/exec": ["@actions/[email protected]", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="],
+
+    "@actions/github": ["@actions/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
+
+    "@actions/io": ["@actions/[email protected]", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="],
+
+    "@fastify/busboy": ["@fastify/[email protected]", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
+
+    "@octokit/auth-token": ["@octokit/[email protected]", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
+
+    "@octokit/core": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw=="],
+
+    "@octokit/graphql": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/request": "^10.0.2", "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg=="],
+
+    "@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
+
+    "@octokit/plugin-paginate-rest": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="],
+
+    "@octokit/plugin-request-log": ["@octokit/[email protected]", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="],
+
+    "@octokit/plugin-rest-endpoint-methods": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="],
+
+    "@octokit/request": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="],
+
+    "@octokit/rest": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
+
+    "@octokit/webhooks-types": ["@octokit/[email protected]", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
+
+    "@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
+
+    "@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="],
+
+    "@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
+
+    "before-after-hook": ["[email protected]", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
+
+    "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
+
+    "csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+
+    "deprecation": ["[email protected]", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
+
+    "fast-content-type-parse": ["[email protected]", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
+
+    "once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+    "tunnel": ["[email protected]", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
+
+    "typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
+
+    "undici": ["[email protected]", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
+
+    "undici-types": ["[email protected]", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
+
+    "universal-user-agent": ["[email protected]", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="],
+
+    "wrappy": ["[email protected]", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+    "@octokit/core/@octokit/graphql": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
+
+    "@octokit/core/universal-user-agent": ["[email protected]", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
+
+    "@octokit/endpoint/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
+
+    "@octokit/endpoint/universal-user-agent": ["[email protected]", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
+
+    "@octokit/graphql/@octokit/request": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
+
+    "@octokit/plugin-request-log/@octokit/core": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
+
+    "@octokit/request/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
+
+    "@octokit/request/universal-user-agent": ["[email protected]", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
+
+    "@octokit/request-error/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
+
+    "@octokit/rest/@octokit/core": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/types": "^14.1.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw=="],
+
+    "@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^14.1.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g=="],
+
+    "@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
+
+    "@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
+
+    "@octokit/graphql/@octokit/request/@octokit/endpoint": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ=="],
+
+    "@octokit/graphql/@octokit/request/@octokit/request-error": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg=="],
+
+    "@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
+
+    "@octokit/plugin-request-log/@octokit/core/@octokit/auth-token": ["@octokit/[email protected]", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
+
+    "@octokit/plugin-request-log/@octokit/core/@octokit/request": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg=="],
+
+    "@octokit/plugin-request-log/@octokit/core/before-after-hook": ["[email protected]", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
+
+    "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
+
+    "@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
+
+    "@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
+
+    "@octokit/rest/@octokit/core/@octokit/auth-token": ["@octokit/[email protected]", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="],
+
+    "@octokit/rest/@octokit/core/@octokit/request": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/types": "^14.0.0" } }, "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg=="],
+
+    "@octokit/rest/@octokit/core/before-after-hook": ["[email protected]", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="],
+
+    "@octokit/plugin-request-log/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/[email protected]", "", { "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/[email protected]", "", { "dependencies": { "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ=="],
+  }
+}

+ 19 - 0
sdks/github/package.json

@@ -0,0 +1,19 @@
+{
+  "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"
+  }
+}

+ 589 - 0
sdks/github/src/index.ts

@@ -0,0 +1,589 @@
+#!/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,?\s*(.*)$/);
+    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.frank.dev.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",
+    },
+  });
+}

+ 103 - 0
sdks/github/src/types.ts

@@ -0,0 +1,103 @@
+// 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;
+  };
+};

+ 29 - 0
sdks/github/tsconfig.json

@@ -0,0 +1,29 @@
+{
+  "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
+  }
+}