opencode-btw 0.1.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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/package.json +33 -0
  4. package/src/plugin.ts +145 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kldzj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # opencode-btw
2
+
3
+ Hint injection plugin for [OpenCode](https://opencode.ai) — nudge the model mid-task without interrupting its flow.
4
+
5
+ When the model is stuck in a loop or heading in the wrong direction, `/btw` lets you inject a hint into its context without sending a new message. The hint persists in the system prompt until you clear it and is picked up on the next LLM call, including during tool loops.
6
+
7
+ ## Install
8
+
9
+ Add to your `opencode.json`:
10
+
11
+ ```json
12
+ {
13
+ "plugin": ["opencode-btw@latest"]
14
+ }
15
+ ```
16
+
17
+ Restart OpenCode after adding the plugin.
18
+
19
+ ## Usage
20
+
21
+ ```
22
+ /btw use the Edit tool instead of sed
23
+ /btw focus on error handling, you keep missing edge cases
24
+ /btw clear
25
+ /btw # show current hint
26
+ ```
27
+
28
+ A confirmation message appears in the chat after each command. The model does not see this confirmation — only the hint itself (via the system prompt).
29
+
30
+ ## How it works
31
+
32
+ 1. `/btw <hint>` saves the hint to a file on disk and cancels the command before an LLM call is made
33
+ 2. On every subsequent LLM call, the `experimental.chat.system.transform` hook reads the hint and appends it to the system prompt
34
+ 3. `/btw clear` removes the hint file
35
+
36
+ Hints are **session-scoped** (each session has its own) and **project-scoped** (stored under a hash of the project directory). All data lives in `~/.cache/opencode/btw/`.
37
+
38
+ ## Use cases
39
+
40
+ - **Error loops**: the model keeps making the same mistake — `/btw you're using the wrong API, check the docs for v2`
41
+ - **Tool preference**: `/btw use Edit instead of sed, use Grep instead of grep`
42
+ - **Scope nudge**: `/btw focus only on the auth module, don't touch other files`
43
+ - **Strategy shift**: `/btw try a completely different approach, the current one won't work`
44
+ - **Direct questions**: `/btw what file are you currently editing?`
45
+
46
+ ## License
47
+
48
+ MIT
49
+
50
+ ---
51
+
52
+ This is a community plugin and is not affiliated with or endorsed by the OpenCode project.
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "opencode-btw",
3
+ "version": "0.1.0",
4
+ "description": "Hint injection plugin for OpenCode — nudge the model mid-task without interrupting its flow",
5
+ "main": "src/plugin.ts",
6
+ "files": [
7
+ "src",
8
+ "LICENSE"
9
+ ],
10
+ "keywords": [
11
+ "opencode",
12
+ "opencode-plugin",
13
+ "copilot",
14
+ "context-injection",
15
+ "btw"
16
+ ],
17
+ "author": "kldzj",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/kldzj/opencode-btw.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/kldzj/opencode-btw/issues"
25
+ },
26
+ "homepage": "https://github.com/kldzj/opencode-btw#readme",
27
+ "peerDependencies": {
28
+ "@opencode-ai/plugin": "*"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,145 @@
1
+ import type { Plugin } from "@opencode-ai/plugin"
2
+ import { createHash } from "crypto"
3
+ import { mkdirSync } from "fs"
4
+
5
+ // btw — inject hints into the model's context without sending a new message.
6
+ //
7
+ // Uses two patterns:
8
+ // 1. Sentinel error throw from command.execute.before — cancels the command
9
+ // before prompt() is called. No LLM request is made.
10
+ // 2. experimental.chat.system.transform — appends the hint to the system
11
+ // prompt on every LLM call (including tool-loop iterations).
12
+ //
13
+ // File layout:
14
+ // ~/.cache/opencode/btw/<project-hash>/
15
+ // <sessionID>.txt # hint content
16
+
17
+ // System prompt instructions — explains the btw system
18
+ const BTW_SYSTEM_INSTRUCTIONS = `<btw-hint-system>
19
+ The user may inject real-time hints via the /btw command. These hints appear below as <btw-active-hint> blocks.
20
+ When a hint is present:
21
+ - Treat it as a direct, high-priority instruction from the user — equivalent to them telling you something face-to-face
22
+ - Apply it IMMEDIATELY to your current and future actions — do not wait for a "good moment"
23
+ - If the hint is a behavioral correction (e.g. "use Edit instead of sed"), adjust silently without calling attention to the change
24
+ - If the hint is a direct request or question (e.g. "explain what you're doing"), respond to it naturally
25
+ - If the hint contradicts your current approach, change course
26
+ - The hint persists until the user clears it — apply it to every action, not just the next one
27
+ - If the hint says to focus on specific files/areas, prioritize those and deprioritize others
28
+ </btw-hint-system>`
29
+
30
+ function projectHash(directory: string): string {
31
+ return createHash("md5").update(directory).digest("hex").slice(0, 12)
32
+ }
33
+
34
+ function btwDir(directory: string): string {
35
+ return `${process.env.HOME}/.cache/opencode/btw/${projectHash(directory)}`
36
+ }
37
+
38
+ // Sentinel error — thrown to prevent OpenCode from calling prompt() after the
39
+ // command hook. Prevents an LLM call from being made for /btw commands.
40
+ const BTW_HANDLED = "__BTW_HANDLED__"
41
+
42
+ export const BtwPlugin: Plugin = async ({ directory, client }) => {
43
+ const dir = btwDir(directory)
44
+
45
+ try {
46
+ mkdirSync(dir, { recursive: true })
47
+ } catch {}
48
+
49
+ const hintPath = (sessionID: string) => `${dir}/${sessionID}.txt`
50
+
51
+ const readHint = async (sessionID: string): Promise<string> => {
52
+ try {
53
+ const file = Bun.file(hintPath(sessionID))
54
+ if (await file.exists()) {
55
+ return (await file.text()).trim()
56
+ }
57
+ } catch {}
58
+ return ""
59
+ }
60
+
61
+ const writeHint = async (sessionID: string, hint: string) => {
62
+ await Bun.write(hintPath(sessionID), hint)
63
+ }
64
+
65
+ // Send a no-op message that appears in the chat UI but is invisible to the
66
+ // LLM (noReply prevents inference, ignored: true hides it from message transforms).
67
+ const sendVisibleMessage = async (sessionID: string, text: string) => {
68
+ try {
69
+ await client.session.prompt({
70
+ path: { id: sessionID },
71
+ body: {
72
+ noReply: true,
73
+ parts: [{ type: "text" as const, text, ignored: true }],
74
+ },
75
+ })
76
+ } catch {
77
+ // Prompt API unavailable — silently skip
78
+ }
79
+ }
80
+
81
+ return {
82
+ // Register /btw as a command so it appears in /help and autocomplete.
83
+ // The template is minimal — command.execute.before intercepts before it's used.
84
+ config: (config) => {
85
+ ;(config as any).command = (config as any).command ?? {}
86
+ ;(config as any).command["btw"] = {
87
+ description:
88
+ "Inject a hint into the model's context (persists in system prompt until cleared)",
89
+ template: "$ARGUMENTS",
90
+ }
91
+ },
92
+
93
+ // Intercept /btw: save hint, send visible message, throw sentinel to prevent LLM call.
94
+ "command.execute.before": async (input, _output) => {
95
+ if (input.command !== "btw") return
96
+
97
+ const args = (input.arguments ?? "").trim()
98
+ const sessionID = input.sessionID
99
+
100
+ if (args === "clear" || args === "reset") {
101
+ await writeHint(sessionID, "")
102
+ await sendVisibleMessage(sessionID, "[btw] Hint cleared")
103
+ throw new Error(BTW_HANDLED)
104
+ }
105
+
106
+ if (!args) {
107
+ const hint = await readHint(sessionID)
108
+ await sendVisibleMessage(
109
+ sessionID,
110
+ hint ? `[btw] Current hint: "${hint}"` : "[btw] No hint set",
111
+ )
112
+ throw new Error(BTW_HANDLED)
113
+ }
114
+
115
+ // Set the hint
116
+ await writeHint(sessionID, args)
117
+ await sendVisibleMessage(sessionID, `[btw] Hint set: "${args}"`)
118
+ throw new Error(BTW_HANDLED)
119
+ },
120
+
121
+ // Inject hint into system prompt on every LLM call (including tool loops).
122
+ "experimental.chat.system.transform": async (input, output) => {
123
+ const sessionID = (input as Record<string, unknown>)?.sessionID
124
+ if (typeof sessionID !== "string" || !sessionID) return
125
+
126
+ try {
127
+ const hint = await readHint(sessionID)
128
+
129
+ if (hint) {
130
+ output.system.push(
131
+ [
132
+ BTW_SYSTEM_INSTRUCTIONS,
133
+ "",
134
+ "<btw-active-hint>",
135
+ hint,
136
+ "</btw-active-hint>",
137
+ ].join("\n"),
138
+ )
139
+ }
140
+ } catch {
141
+ // File read failed — no hint to inject
142
+ }
143
+ },
144
+ }
145
+ }