opencode-snippets 1.1.2 → 1.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.
- package/README.md +69 -79
- package/index.ts +64 -17
- package/package.json +12 -5
- package/src/commands.ts +336 -0
- package/src/constants.ts +9 -14
- package/src/expander.test.ts +466 -0
- package/src/expander.ts +47 -37
- package/src/loader.test.ts +261 -0
- package/src/loader.ts +234 -56
- package/src/logger.test.ts +136 -0
- package/src/logger.ts +95 -95
- package/src/notification.ts +29 -0
- package/src/shell.ts +30 -24
- package/src/types.ts +19 -7
package/README.md
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
✨ **Instant inline text expansion for OpenCode** - Type `#snippet` anywhere in your message and watch it transform.
|
|
4
4
|
|
|
5
|
+
> [!TIP]
|
|
6
|
+
> **Share Your Snippets!**
|
|
7
|
+
> Got a snippet that saves you time? Share yours or steal ideas from the community!
|
|
8
|
+
> Browse and contribute in [GitHub Discussions](https://github.com/JosXa/opencode-snippets/discussions/categories/snippets).
|
|
9
|
+
|
|
5
10
|
## Why Snippets?
|
|
6
11
|
|
|
7
12
|
As developers, we DRY (Don't Repeat Yourself) our code. We extract functions, create libraries, compose modules. Why should our prompts be any different?
|
|
8
13
|
|
|
9
|
-
Stop copy-pasting the same instructions into every message. Snippets bring software engineering principles to prompt engineering:
|
|
14
|
+
Stop copy-pasting (or worse, *typing* 🤢) the same instructions into every message. Snippets bring software engineering principles to prompt engineering:
|
|
10
15
|
|
|
11
16
|
- 🔄 **DRY** - Write once, reuse everywhere
|
|
12
17
|
- 🧩 **Composability** - Build complex prompts from simple pieces
|
|
@@ -29,36 +34,15 @@ Snippets work like `@file` mentions - natural, inline, composable.
|
|
|
29
34
|
|
|
30
35
|
Snippets compose with each other and with slash commands. Reference `#snippets` anywhere - in your messages, in slash commands, even inside other snippets:
|
|
31
36
|
|
|
32
|
-
**Example:
|
|
33
|
-
|
|
34
|
-
`~/.config/opencode/snippet/ralph.md`:
|
|
35
|
-
```markdown
|
|
36
|
-
Start a ralph loop for this. Automatically guess how many iterations it will require if not specified.
|
|
37
|
-
#todofile
|
|
38
|
-
Give the path of the status/todo file to the ralph task and tell to continuously update it.
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
`~/.config/opencode/command/ralph.md`:
|
|
42
|
-
```markdown
|
|
43
|
-
---
|
|
44
|
-
description: "Initiates a Ralph loop"
|
|
45
|
-
---
|
|
46
|
-
#ralph
|
|
47
|
-
|
|
48
|
-
$ARGUMENTS
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The `/ralph` slash command simply expands the `#ralph` snippet and passes through arguments. Minimal boilerplate, maximum reuse.
|
|
52
|
-
|
|
53
|
-
**Example: Extending snippets with logic**
|
|
37
|
+
**Example: Extending commands with snippets**
|
|
54
38
|
|
|
55
39
|
`~/.config/opencode/command/commit-and-push.md`:
|
|
56
40
|
```markdown
|
|
57
41
|
---
|
|
58
42
|
description: Create a git commit and push to remote
|
|
59
|
-
agent: fast
|
|
60
43
|
---
|
|
61
44
|
Please create a git commit with the current changes and push to the remote repository.
|
|
45
|
+
#use-conventional-commits
|
|
62
46
|
|
|
63
47
|
Here is the current git status:
|
|
64
48
|
!`git status`
|
|
@@ -66,11 +50,10 @@ Here is the current git status:
|
|
|
66
50
|
Here are the staged changes:
|
|
67
51
|
!`git diff --cached`
|
|
68
52
|
|
|
69
|
-
#conventional-commits
|
|
70
53
|
#project-context
|
|
71
54
|
```
|
|
72
55
|
|
|
73
|
-
|
|
56
|
+
You could also make "current git status and staged changes" a shell-enabled snippet of its own.
|
|
74
57
|
|
|
75
58
|
**Example: Snippets composing snippets**
|
|
76
59
|
|
|
@@ -106,7 +89,7 @@ Add to your `opencode.json` plugins array:
|
|
|
106
89
|
|
|
107
90
|
## Quick Start
|
|
108
91
|
|
|
109
|
-
**1. Create
|
|
92
|
+
**1. Create your global snippets directory:**
|
|
110
93
|
|
|
111
94
|
```bash
|
|
112
95
|
mkdir -p ~/.config/opencode/snippet
|
|
@@ -125,25 +108,13 @@ Ask clarifying questions if anything is ambiguous.
|
|
|
125
108
|
|
|
126
109
|
**3. Use it anywhere:**
|
|
127
110
|
|
|
128
|
-
|
|
129
|
-
Refactor this function #careful
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
The LLM receives:
|
|
133
|
-
```
|
|
134
|
-
Refactor this function Think step by step. Double-check your work before making changes.
|
|
135
|
-
Ask clarifying questions if anything is ambiguous.
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Features
|
|
111
|
+
https://github.com/user-attachments/assets/d31b69b5-cc7a-4208-9f6e-71c1a278536a
|
|
139
112
|
|
|
140
|
-
|
|
113
|
+
## Where to Store Snippets
|
|
141
114
|
|
|
142
|
-
|
|
115
|
+
Snippets can be global (`~/.config/opencode/snippet/*.md`) or project-specific (`.opencode/snippet/*.md`). Both directories are loaded automatically. Project snippets override global ones with the same name, just like OpenCode's slash commands.
|
|
143
116
|
|
|
144
|
-
|
|
145
|
-
#review-checklist Please check my PR
|
|
146
|
-
```
|
|
117
|
+
## Features
|
|
147
118
|
|
|
148
119
|
### Aliases
|
|
149
120
|
|
|
@@ -172,7 +143,7 @@ You can also use JSON array style: `aliases: ["cp", "pick"]`
|
|
|
172
143
|
|
|
173
144
|
### Shell Command Substitution
|
|
174
145
|
|
|
175
|
-
Snippets support the same
|
|
146
|
+
Snippets support the same ``!`command` `` syntax as [OpenCode slash commands](https://opencode.ai/docs/commands/#shell-output) for injecting live command output:
|
|
176
147
|
|
|
177
148
|
```markdown
|
|
178
149
|
Current branch: !`git branch --show-current`
|
|
@@ -180,18 +151,44 @@ Last commit: !`git log -1 --oneline`
|
|
|
180
151
|
Working directory: !`pwd`
|
|
181
152
|
```
|
|
182
153
|
|
|
154
|
+
> **Note:** Snippets deviate slightly from the regular slash command behavior. Instead of just passing the command output to the LLM, snippets prepend the command itself:
|
|
155
|
+
> ``!`ls` `` →
|
|
156
|
+
> ```
|
|
157
|
+
> $ ls
|
|
158
|
+
> --> <output>
|
|
159
|
+
> ```
|
|
160
|
+
> This tells the LLM which command was actually run and makes failures visible (empty output would otherwise be indistinguishable from success).
|
|
161
|
+
>
|
|
162
|
+
> **TODO:** This behavior should either be PR'd upstream to OpenCode or made configurable in opencode-snippets.
|
|
163
|
+
|
|
183
164
|
### Recursive Includes
|
|
184
165
|
|
|
185
|
-
Snippets can include other snippets:
|
|
166
|
+
Snippets can include other snippets using `#snippet-name` syntax. This allows building complex, composable snippets from smaller pieces:
|
|
186
167
|
|
|
187
168
|
```markdown
|
|
188
|
-
# In base-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
#
|
|
169
|
+
# In base-style.md:
|
|
170
|
+
Use TypeScript strict mode. Always add JSDoc comments.
|
|
171
|
+
|
|
172
|
+
# In python-style.md:
|
|
173
|
+
Use type hints. Follow PEP 8.
|
|
174
|
+
|
|
175
|
+
# In review.md:
|
|
176
|
+
Review this code carefully:
|
|
177
|
+
#base-style
|
|
178
|
+
#python-style
|
|
179
|
+
#security-checklist
|
|
192
180
|
```
|
|
193
181
|
|
|
194
|
-
Loop
|
|
182
|
+
**Loop Protection:** Snippets are expanded up to 15 times per message to support deep nesting. If a circular reference is detected (e.g., `#a` includes `#b` which includes `#a`), expansion stops after 15 iterations and the remaining hashtag is left as-is. A warning is logged to help debug the issue.
|
|
183
|
+
|
|
184
|
+
**Example of loop protection:**
|
|
185
|
+
```markdown
|
|
186
|
+
# self.md contains: "I reference #self"
|
|
187
|
+
# Expanding #self produces:
|
|
188
|
+
I reference I reference I reference ... (15 times) ... I reference #self
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This generous limit supports complex snippet hierarchies while preventing infinite loops.
|
|
195
192
|
|
|
196
193
|
## Example Snippets
|
|
197
194
|
|
|
@@ -205,21 +202,6 @@ Branch: !`git branch --show-current`
|
|
|
205
202
|
Recent changes: !`git diff --stat HEAD~3 | tail -5`
|
|
206
203
|
```
|
|
207
204
|
|
|
208
|
-
### `~/.config/opencode/snippet/review.md`
|
|
209
|
-
```markdown
|
|
210
|
-
---
|
|
211
|
-
aliases:
|
|
212
|
-
- pr
|
|
213
|
-
- check
|
|
214
|
-
---
|
|
215
|
-
Review this code for:
|
|
216
|
-
- Security vulnerabilities
|
|
217
|
-
- Performance issues
|
|
218
|
-
- Code style consistency
|
|
219
|
-
- Missing error handling
|
|
220
|
-
- Test coverage gaps
|
|
221
|
-
```
|
|
222
|
-
|
|
223
205
|
### `~/.config/opencode/snippet/minimal.md`
|
|
224
206
|
```markdown
|
|
225
207
|
---
|
|
@@ -234,22 +216,29 @@ Be extremely concise. No explanations unless asked.
|
|
|
234
216
|
|
|
235
217
|
| Feature | `/commands` | `#snippets` |
|
|
236
218
|
|---------|-------------|-------------|
|
|
237
|
-
| Position | Must come first | Anywhere |
|
|
238
|
-
| Multiple per message | No | Yes |
|
|
239
|
-
| Live shell data | Yes | Yes |
|
|
240
|
-
| Best for | Triggering actions & workflows | Context injection |
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
219
|
+
| Position | Must come first 🏁 | Anywhere 📍 |
|
|
220
|
+
| Multiple per message | No ❌ | Yes ✅ |
|
|
221
|
+
| Live shell data | Yes 💻 | Yes 💻 |
|
|
222
|
+
| Best for | Triggering actions & workflows ⚡ | Context injection 📝 |
|
|
223
|
+
|
|
224
|
+
> [!TIP]
|
|
225
|
+
> #### My recommendation:
|
|
226
|
+
>
|
|
227
|
+
> Use /slash commands for **triggering actions and workflows** imperatively - anything that needs to happen _right now_: `/commit-and-push`, `/add-worktree`, or `/pull-rebase`.
|
|
228
|
+
> Use #snippets for **all other context engineering**.
|
|
229
|
+
>
|
|
230
|
+
> If you can't decide, get the best of both worlds and just have your command proxy through to the snippet:
|
|
231
|
+
>
|
|
232
|
+
> `~/.config/opencode/command/pull.md`:
|
|
233
|
+
> ```markdown
|
|
234
|
+
> ---
|
|
235
|
+
> description: Proxy through to the snippet at snippet/pull.md
|
|
236
|
+
> ---
|
|
237
|
+
> #pull
|
|
238
|
+
> ```
|
|
246
239
|
|
|
247
240
|
## Configuration
|
|
248
241
|
|
|
249
|
-
### Snippet Directory
|
|
250
|
-
|
|
251
|
-
All snippets live in `~/.config/opencode/snippet/` as `.md` files.
|
|
252
|
-
|
|
253
242
|
### Debug Logging
|
|
254
243
|
|
|
255
244
|
Enable debug logs by setting an environment variable:
|
|
@@ -271,7 +260,8 @@ Logs are written to `~/.config/opencode/logs/snippets/daily/`.
|
|
|
271
260
|
|
|
272
261
|
## Contributing
|
|
273
262
|
|
|
274
|
-
Contributions welcome! Please open an issue or PR on GitHub.
|
|
263
|
+
Contributions welcome! Please open an issue or PR on GitHub.
|
|
264
|
+
👥 [Discord Forum](https://discord.com/channels/1391832426048651334/1463378026833379409)
|
|
275
265
|
|
|
276
266
|
## License
|
|
277
267
|
|
package/index.ts
CHANGED
|
@@ -1,33 +1,80 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode-ai/plugin"
|
|
2
|
-
import {
|
|
3
|
-
import { expandHashtags } from "./src/expander.js"
|
|
4
|
-
import {
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
import { createCommandExecuteHandler } from "./src/commands.js";
|
|
3
|
+
import { expandHashtags } from "./src/expander.js";
|
|
4
|
+
import { loadSnippets } from "./src/loader.js";
|
|
5
|
+
import { logger } from "./src/logger.js";
|
|
6
|
+
import { executeShellCommands, type ShellContext } from "./src/shell.js";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Snippets Plugin for OpenCode
|
|
8
|
-
*
|
|
10
|
+
*
|
|
9
11
|
* Expands hashtag-based shortcuts in user messages into predefined text snippets.
|
|
10
|
-
*
|
|
12
|
+
* Also provides /snippet command for managing snippets.
|
|
13
|
+
*
|
|
11
14
|
* @see https://github.com/JosXa/opencode-snippets for full documentation
|
|
12
15
|
*/
|
|
13
16
|
export const SnippetsPlugin: Plugin = async (ctx) => {
|
|
14
|
-
// Load all snippets at startup
|
|
15
|
-
const
|
|
17
|
+
// Load all snippets at startup (global + project directory)
|
|
18
|
+
const startupStart = performance.now();
|
|
19
|
+
const snippets = await loadSnippets(ctx.directory);
|
|
20
|
+
const startupTime = performance.now() - startupStart;
|
|
21
|
+
|
|
22
|
+
logger.debug("Plugin startup complete", {
|
|
23
|
+
startupTimeMs: startupTime.toFixed(2),
|
|
24
|
+
snippetCount: snippets.size,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Create command handler
|
|
28
|
+
const commandHandler = createCommandExecuteHandler(ctx.client, snippets, ctx.directory);
|
|
16
29
|
|
|
17
30
|
return {
|
|
18
|
-
|
|
31
|
+
// Register /snippet command
|
|
32
|
+
config: async (opencodeConfig) => {
|
|
33
|
+
opencodeConfig.command ??= {};
|
|
34
|
+
opencodeConfig.command.snippet = {
|
|
35
|
+
template: "",
|
|
36
|
+
description: "Manage text snippets (create, delete, list, help)",
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// Handle /snippet command execution
|
|
41
|
+
"command.execute.before": commandHandler,
|
|
42
|
+
|
|
43
|
+
"chat.message": async (_input, output) => {
|
|
19
44
|
// Only process user messages, never assistant messages
|
|
20
|
-
if (output.message.role !== "user") return
|
|
21
|
-
|
|
45
|
+
if (output.message.role !== "user") return;
|
|
46
|
+
|
|
47
|
+
const messageStart = performance.now();
|
|
48
|
+
let expandTimeTotal = 0;
|
|
49
|
+
let shellTimeTotal = 0;
|
|
50
|
+
let processedParts = 0;
|
|
51
|
+
|
|
22
52
|
for (const part of output.parts) {
|
|
23
53
|
if (part.type === "text" && part.text) {
|
|
24
54
|
// 1. Expand hashtags recursively with loop detection
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
const expandStart = performance.now();
|
|
56
|
+
part.text = expandHashtags(part.text, snippets);
|
|
57
|
+
const expandTime = performance.now() - expandStart;
|
|
58
|
+
expandTimeTotal += expandTime;
|
|
59
|
+
|
|
27
60
|
// 2. Execute shell commands: !`command`
|
|
28
|
-
|
|
61
|
+
const shellStart = performance.now();
|
|
62
|
+
part.text = await executeShellCommands(part.text, ctx as unknown as ShellContext);
|
|
63
|
+
const shellTime = performance.now() - shellStart;
|
|
64
|
+
shellTimeTotal += shellTime;
|
|
65
|
+
processedParts += 1;
|
|
29
66
|
}
|
|
30
67
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
68
|
+
|
|
69
|
+
const totalTime = performance.now() - messageStart;
|
|
70
|
+
if (processedParts > 0) {
|
|
71
|
+
logger.debug("Message processing complete", {
|
|
72
|
+
totalTimeMs: totalTime.toFixed(2),
|
|
73
|
+
snippetExpandTimeMs: expandTimeTotal.toFixed(2),
|
|
74
|
+
shellTimeMs: shellTimeTotal.toFixed(2),
|
|
75
|
+
processedParts,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-snippets",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Hashtag-based snippet expansion plugin for OpenCode - instant inline text shortcuts",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -34,12 +34,19 @@
|
|
|
34
34
|
"@opencode-ai/plugin": ">=1.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
+
"@biomejs/biome": "^2.3.11",
|
|
37
38
|
"@types/bun": "latest",
|
|
38
39
|
"@types/node": "^22",
|
|
40
|
+
"@typescript/native-preview": "^7.0.0-dev.20260120.1",
|
|
41
|
+
"husky": "^9.1.7",
|
|
39
42
|
"typescript": "^5"
|
|
40
43
|
},
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsgo --noEmit",
|
|
46
|
+
"format:check": "biome check .",
|
|
47
|
+
"format:fix": "biome check --write .",
|
|
48
|
+
"ai:check": "bun run format:fix",
|
|
49
|
+
"prepare": "husky"
|
|
50
|
+
},
|
|
51
|
+
"files": ["index.ts", "src/**/*.ts"]
|
|
45
52
|
}
|