summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/tool/write.ts12
-rw-r--r--packages/slack/.env.example3
-rw-r--r--packages/slack/.gitignore4
-rw-r--r--packages/slack/README.md27
-rw-r--r--packages/slack/package.json17
-rw-r--r--packages/slack/src/index.ts105
-rw-r--r--packages/slack/sst-env.d.ts3
-rw-r--r--packages/slack/tsconfig.json5
8 files changed, 176 insertions, 0 deletions
diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts
index aa79c9bfb..64f9feb20 100644
--- a/packages/opencode/src/tool/write.ts
+++ b/packages/opencode/src/tool/write.ts
@@ -10,6 +10,8 @@ import { FileTime } from "../file/time"
import { Filesystem } from "../util/filesystem"
import { Instance } from "../project/instance"
import { Agent } from "../agent/agent"
+import { createTwoFilesPatch } from "diff"
+import { trimDiff } from "./edit"
export const WriteTool = Tool.define("write", {
description: DESCRIPTION,
@@ -27,6 +29,13 @@ export const WriteTool = Tool.define("write", {
const exists = await file.exists()
if (exists) await FileTime.assert(ctx.sessionID, filepath)
+ let oldContent = ""
+ let diff = ""
+
+ if (exists) {
+ oldContent = await file.text()
+ }
+
const agent = await Agent.get(ctx.agent)
if (agent.permission.edit === "ask")
await Permission.ask({
@@ -48,6 +57,9 @@ export const WriteTool = Tool.define("write", {
})
FileTime.read(ctx.sessionID, filepath)
+ // Generate diff for the write operation
+ diff = trimDiff(createTwoFilesPatch(filepath, filepath, oldContent, params.content))
+
let output = ""
await LSP.touchFile(filepath, true)
const diagnostics = await LSP.diagnostics()
diff --git a/packages/slack/.env.example b/packages/slack/.env.example
new file mode 100644
index 000000000..ff72adaf9
--- /dev/null
+++ b/packages/slack/.env.example
@@ -0,0 +1,3 @@
+SLACK_BOT_TOKEN=xoxb-your-bot-token
+SLACK_SIGNING_SECRET=your-signing-secret
+SLACK_APP_TOKEN=xapp-your-app-token \ No newline at end of file
diff --git a/packages/slack/.gitignore b/packages/slack/.gitignore
new file mode 100644
index 000000000..1ef9d19ba
--- /dev/null
+++ b/packages/slack/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+dist
+.env
+.DS_Store \ No newline at end of file
diff --git a/packages/slack/README.md b/packages/slack/README.md
new file mode 100644
index 000000000..a12978d8a
--- /dev/null
+++ b/packages/slack/README.md
@@ -0,0 +1,27 @@
+# @opencode-ai/slack
+
+Slack bot integration for opencode that creates threaded conversations.
+
+## Setup
+
+1. Create a Slack app at https://api.slack.com/apps
+2. Enable Socket Mode
+3. Add the following OAuth scopes:
+ - `chat:write`
+ - `app_mentions:read`
+ - `channels:history`
+ - `groups:history`
+4. Install the app to your workspace
+5. Set environment variables in `.env`:
+ - `SLACK_BOT_TOKEN` - Bot User OAuth Token
+ - `SLACK_SIGNING_SECRET` - Signing Secret from Basic Information
+ - `SLACK_APP_TOKEN` - App-Level Token from Basic Information
+
+## Usage
+
+```bash
+# Edit .env with your Slack app credentials
+bun dev
+```
+
+The bot will respond to messages in channels where it's added, creating separate opencode sessions for each thread.
diff --git a/packages/slack/package.json b/packages/slack/package.json
new file mode 100644
index 000000000..179782e23
--- /dev/null
+++ b/packages/slack/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "@opencode-ai/slack",
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "bun run src/index.ts",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@opencode-ai/sdk": "workspace:*",
+ "@slack/bolt": "^3.17.1"
+ },
+ "devDependencies": {
+ "@types/node": "catalog:",
+ "typescript": "catalog:"
+ }
+}
diff --git a/packages/slack/src/index.ts b/packages/slack/src/index.ts
new file mode 100644
index 000000000..e86b8fec5
--- /dev/null
+++ b/packages/slack/src/index.ts
@@ -0,0 +1,105 @@
+import { App } from "@slack/bolt"
+import { createOpencode } from "@opencode-ai/sdk"
+
+const app = new App({
+ token: process.env.SLACK_BOT_TOKEN,
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
+ socketMode: true,
+ appToken: process.env.SLACK_APP_TOKEN,
+})
+
+console.log("๐Ÿ”ง Bot configuration:")
+console.log("- Bot token present:", !!process.env.SLACK_BOT_TOKEN)
+console.log("- Signing secret present:", !!process.env.SLACK_SIGNING_SECRET)
+console.log("- App token present:", !!process.env.SLACK_APP_TOKEN)
+
+console.log("๐Ÿš€ Starting opencode server...")
+const opencode = await createOpencode({
+ port: 0,
+})
+console.log("โœ… Opencode server ready")
+
+const sessions = new Map<string, { client: any; server: any; sessionId: string; channel: string; thread: string }>()
+
+app.use(async ({ next, context }) => {
+ console.log("๐Ÿ“ก Raw Slack event:", JSON.stringify(context, null, 2))
+ await next()
+})
+
+app.message(async ({ message, say }) => {
+ console.log("๐Ÿ“จ Received message event:", JSON.stringify(message, null, 2))
+
+ if (message.subtype || !("text" in message) || !message.text) {
+ console.log("โญ๏ธ Skipping message - no text or has subtype")
+ return
+ }
+
+ console.log("โœ… Processing message:", message.text)
+
+ const channel = message.channel
+ const thread = (message as any).thread_ts || message.ts
+ const sessionKey = `${channel}-${thread}`
+
+ let session = sessions.get(sessionKey)
+
+ if (!session) {
+ console.log("๐Ÿ†• Creating new opencode session...")
+ const { client, server } = opencode
+
+ const createResult = await client.session.create({
+ body: { title: `Slack thread ${thread}` },
+ })
+
+ if (createResult.error) {
+ console.error("โŒ Failed to create session:", createResult.error)
+ await say({ text: "Sorry, I had trouble creating a session. Please try again.", thread_ts: thread })
+ return
+ }
+
+ console.log("โœ… Created opencode session:", createResult.data.id)
+ session = { client, server, sessionId: createResult.data.id, channel, thread }
+ sessions.set(sessionKey, session)
+
+ const shareResult = await client.session.share({ path: { id: createResult.data.id } })
+ if (!shareResult.error && shareResult.data) {
+ const sessionUrl = shareResult.data.share?.url!
+ console.log("๐Ÿ”— Session shared:", sessionUrl)
+ await app.client.chat.postMessage({ channel, thread_ts: thread, text: sessionUrl })
+ }
+ }
+
+ console.log("๐Ÿ“ Sending to opencode:", message.text)
+ const result = await session.client.session.prompt({
+ path: { id: session.sessionId },
+ body: { parts: [{ type: "text", text: message.text }] },
+ })
+
+ console.log("๐Ÿ“ค Opencode response:", JSON.stringify(result, null, 2))
+
+ if (result.error) {
+ console.error("โŒ Failed to send message:", result.error)
+ await say({ text: "Sorry, I had trouble processing your message. Please try again.", thread_ts: thread })
+ return
+ }
+
+ const response = result.data
+ const responseText =
+ response.info?.content ||
+ response.parts
+ ?.filter((p: any) => p.type === "text")
+ .map((p: any) => p.text)
+ .join("\n") ||
+ "I received your message but didn't have a response."
+
+ console.log("๐Ÿ’ฌ Sending response:", responseText)
+ await say({ text: responseText, thread_ts: thread })
+})
+
+app.command("/test", async ({ command, ack, say }) => {
+ await ack()
+ console.log("๐Ÿงช Test command received:", JSON.stringify(command, null, 2))
+ await say("๐Ÿค– Bot is working! I can hear you loud and clear.")
+})
+
+await app.start()
+console.log("โšก๏ธ Slack bot is running!")
diff --git a/packages/slack/sst-env.d.ts b/packages/slack/sst-env.d.ts
new file mode 100644
index 000000000..cde5429a6
--- /dev/null
+++ b/packages/slack/sst-env.d.ts
@@ -0,0 +1,3 @@
+/// <reference types="../../../sst-env.d.ts" />
+
+export {}
diff --git a/packages/slack/tsconfig.json b/packages/slack/tsconfig.json
new file mode 100644
index 000000000..65fa6c7f3
--- /dev/null
+++ b/packages/slack/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "@tsconfig/bun/tsconfig.json",
+ "compilerOptions": {}
+}