@xinleibird/bridge-opencode 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/bridge.ts +22 -52
  3. package/package.json +2 -4
package/README.md CHANGED
@@ -7,6 +7,7 @@ Bridge between opencode and Neovim, inspired by [sidekick](https://github.com/Ni
7
7
  - **Buffer protection**: When you have unsaved changes in a buffer, opencode waits — edits are denied and your work is preserved
8
8
  - **Auto-reload**: When opencode modifies a file you have open, the buffer is auto-reloaded with cursor position preserved
9
9
  - **Visual selection context**: Visual selections in Neovim are sent to opencode as chat context
10
+ - `#this`: Type `#this` in chat to attach the current visual selection from Neovim
10
11
 
11
12
  ## Structure
12
13
 
package/bridge.ts CHANGED
@@ -2,6 +2,7 @@ import type { Plugin } from "@opencode-ai/plugin";
2
2
  import crypto from "node:crypto";
3
3
  import { access } from "node:fs/promises";
4
4
  import { basename, isAbsolute, join } from "node:path";
5
+ import { pathToFileURL } from "bun";
5
6
 
6
7
  interface BufferStatus {
7
8
  isCurrent: boolean;
@@ -126,58 +127,17 @@ export const BridgePlugin: Plugin = async ({ directory }) => {
126
127
  }
127
128
  },
128
129
 
129
- "experimental.chat.messages.transform": async (_, output) => {
130
- let selections: Awaited<ReturnType<typeof getVisualSelections>>;
131
- try {
132
- selections = await getVisualSelections();
133
- } catch {
134
- return;
135
- }
136
- if (!selections || selections.length === 0) return;
137
-
138
- const filteredSelections = selections.filter((s) => !s.cwd || s.cwd === cwd);
139
- if (filteredSelections.length === 0) return;
140
-
141
- const userMessages = output.messages.filter((m) => m.info.role === "user");
142
- if (userMessages.length === 0) return;
143
-
144
- const latestUserMessage = userMessages[userMessages.length - 1];
145
- if (!latestUserMessage.parts) {
146
- latestUserMessage.parts = [];
147
- }
148
-
149
- const blocks: string[] = [];
150
- for (const s of filteredSelections) {
151
- try {
152
- await access(s.filePath);
153
- } catch {
154
- continue;
155
- }
156
- if (!s.startLine || !s.content) continue;
157
-
158
- const fileName = basename(s.filePath);
159
- blocks.push(
160
- `Visual selection from nvim:\n## ${fileName}(${s.filePath}:${s.startLine}-${s.endLine})\n\`\`\`\n${s.content}\n\`\`\``,
161
- );
162
- }
163
-
164
- if (blocks.length === 0) return;
165
-
166
- const textPart = latestUserMessage.parts.findLast((p) => p.type === "text");
167
- if (textPart && typeof textPart.text === "string") {
168
- textPart.text += `\n\n${blocks.join("\n\n")}`;
169
- } else {
170
- latestUserMessage.parts.push({
171
- id: crypto.randomUUID(),
172
- sessionID: latestUserMessage.info.sessionID,
173
- messageID: latestUserMessage.info.id,
174
- type: "text",
175
- text: blocks.join("\n\n"),
176
- });
177
- }
178
- },
179
-
180
130
  "chat.message": async (input, output) => {
131
+ const isTriggered = output.parts.some(
132
+ (p) =>
133
+ p.type === "text" &&
134
+ "text" in p &&
135
+ !p.synthetic &&
136
+ typeof p.text === "string" &&
137
+ p.text.includes("#this"),
138
+ );
139
+ if (!isTriggered) return;
140
+
181
141
  let selections: Awaited<ReturnType<typeof getVisualSelections>>;
182
142
  try {
183
143
  selections = await getVisualSelections();
@@ -206,10 +166,20 @@ export const BridgePlugin: Plugin = async ({ directory }) => {
206
166
  messageID: input.messageID ?? "",
207
167
  mime: "text/plain",
208
168
  filename: `${fileName}:${s.startLine}-${s.endLine}`,
209
- url: `file://${s.filePath}?start=${s.startLine}&end=${s.endLine}`,
169
+ url: pathToFileURL(s.filePath).href,
170
+ });
171
+
172
+ output.parts.push({
173
+ type: "text",
174
+ id: crypto.randomUUID(),
175
+ sessionID: input.sessionID,
176
+ messageID: input.messageID ?? "",
177
+ synthetic: true,
178
+ text: `## current selection — ${fileName}(${s.filePath}:${s.startLine}-${s.endLine}):\n\`\`\`\n${s.content}\n\`\`\``,
210
179
  });
211
180
  }
212
181
  },
213
182
  };
214
183
  };
184
+
215
185
  export default BridgePlugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xinleibird/bridge-opencode",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@napi-rs/cli": "^2.18.0",
25
+ "@types/bun": "^1.3.14",
25
26
  "@types/node": "^25.9.1"
26
27
  },
27
28
  "peerDependencies": {
@@ -38,8 +39,5 @@
38
39
  "aarch64-unknown-linux-gnu"
39
40
  ]
40
41
  }
41
- },
42
- "dependencies": {
43
- "@opencode-ai/sdk": "^1.15.12"
44
42
  }
45
43
  }