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 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: Slash commands as snippet proxies**
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
- The slash command provides workflow logic (git status, diffs, push) while reusing shared snippets for commit conventions and project context.
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 a snippet file:**
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
- ### Hashtag Expansion
113
+ ## Where to Store Snippets
141
114
 
142
- Any `#snippet-name` is replaced with the contents of `~/.config/opencode/snippet/snippet-name.md`:
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 `!`backtick\`` syntax as [OpenCode slash commands](https://opencode.ai/docs/commands/#shell-output) for injecting live command output:
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-context.md:
189
- #project-info
190
- #coding-standards
191
- #git-conventions
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 detection prevents infinite recursion.
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
- **Use both together:**
243
- ```
244
- /commit #conventional-commits #project-context
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 { loadSnippets } from "./src/loader.js"
3
- import { expandHashtags } from "./src/expander.js"
4
- import { executeShellCommands } from "./src/shell.js"
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 snippets = await loadSnippets()
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
- "chat.message": async (input, output) => {
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
- part.text = expandHashtags(part.text, snippets)
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
- part.text = await executeShellCommands(part.text, ctx)
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.1.2",
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
- "files": [
42
- "index.ts",
43
- "src/**/*.ts"
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
  }