opencode-snippets 1.1.2 → 1.2.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
@@ -29,34 +29,12 @@ Snippets work like `@file` mentions - natural, inline, composable.
29
29
 
30
30
  Snippets compose with each other and with slash commands. Reference `#snippets` anywhere - in your messages, in slash commands, even inside other snippets:
31
31
 
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
32
  **Example: Extending snippets with logic**
54
33
 
55
34
  `~/.config/opencode/command/commit-and-push.md`:
56
35
  ```markdown
57
36
  ---
58
37
  description: Create a git commit and push to remote
59
- agent: fast
60
38
  ---
61
39
  Please create a git commit with the current changes and push to the remote repository.
62
40
 
@@ -70,8 +48,6 @@ Here are the staged changes:
70
48
  #project-context
71
49
  ```
72
50
 
73
- The slash command provides workflow logic (git status, diffs, push) while reusing shared snippets for commit conventions and project context.
74
-
75
51
  **Example: Snippets composing snippets**
76
52
 
77
53
  `~/.config/opencode/snippet/code-standards.md`:
@@ -106,7 +82,7 @@ Add to your `opencode.json` plugins array:
106
82
 
107
83
  ## Quick Start
108
84
 
109
- **1. Create a snippet file:**
85
+ **1. Create your global snippets directory:**
110
86
 
111
87
  ```bash
112
88
  mkdir -p ~/.config/opencode/snippet
@@ -126,24 +102,15 @@ Ask clarifying questions if anything is ambiguous.
126
102
  **3. Use it anywhere:**
127
103
 
128
104
  ```
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.
105
+ Refactor this function. Think step by step. Double-check your work before making changes.
135
106
  Ask clarifying questions if anything is ambiguous.
136
107
  ```
137
108
 
138
- ## Features
139
-
140
- ### Hashtag Expansion
109
+ ## Where to Store Snippets
141
110
 
142
- Any `#snippet-name` is replaced with the contents of `~/.config/opencode/snippet/snippet-name.md`:
111
+ 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
112
 
144
- ```
145
- #review-checklist Please check my PR
146
- ```
113
+ ## Features
147
114
 
148
115
  ### Aliases
149
116
 
@@ -172,7 +139,7 @@ You can also use JSON array style: `aliases: ["cp", "pick"]`
172
139
 
173
140
  ### Shell Command Substitution
174
141
 
175
- Snippets support the same `!`backtick\`` syntax as [OpenCode slash commands](https://opencode.ai/docs/commands/#shell-output) for injecting live command output:
142
+ Snippets support the same ``!`command` `` syntax as [OpenCode slash commands](https://opencode.ai/docs/commands/#shell-output) for injecting live command output:
176
143
 
177
144
  ```markdown
178
145
  Current branch: !`git branch --show-current`
@@ -180,18 +147,44 @@ Last commit: !`git log -1 --oneline`
180
147
  Working directory: !`pwd`
181
148
  ```
182
149
 
150
+ > **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:
151
+ > ``!`ls` `` →
152
+ > ```
153
+ > $ ls
154
+ > --> <output>
155
+ > ```
156
+ > This tells the LLM which command was actually run and makes failures visible (empty output would otherwise be indistinguishable from success).
157
+ >
158
+ > **TODO:** This behavior should either be PR'd upstream to OpenCode or made configurable in opencode-snippets.
159
+
183
160
  ### Recursive Includes
184
161
 
185
- Snippets can include other snippets:
162
+ Snippets can include other snippets using `#snippet-name` syntax. This allows building complex, composable snippets from smaller pieces:
163
+
164
+ ```markdown
165
+ # In base-style.md:
166
+ Use TypeScript strict mode. Always add JSDoc comments.
167
+
168
+ # In python-style.md:
169
+ Use type hints. Follow PEP 8.
170
+
171
+ # In review.md:
172
+ Review this code carefully:
173
+ #base-style
174
+ #python-style
175
+ #security-checklist
176
+ ```
177
+
178
+ **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.
186
179
 
180
+ **Example of loop protection:**
187
181
  ```markdown
188
- # In base-context.md:
189
- #project-info
190
- #coding-standards
191
- #git-conventions
182
+ # self.md contains: "I reference #self"
183
+ # Expanding #self produces:
184
+ I reference I reference I reference ... (15 times) ... I reference #self
192
185
  ```
193
186
 
194
- Loop detection prevents infinite recursion.
187
+ This generous limit supports complex snippet hierarchies while preventing infinite loops.
195
188
 
196
189
  ## Example Snippets
197
190
 
@@ -205,21 +198,6 @@ Branch: !`git branch --show-current`
205
198
  Recent changes: !`git diff --stat HEAD~3 | tail -5`
206
199
  ```
207
200
 
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
201
  ### `~/.config/opencode/snippet/minimal.md`
224
202
  ```markdown
225
203
  ---
@@ -234,23 +212,17 @@ Be extremely concise. No explanations unless asked.
234
212
 
235
213
  | Feature | `/commands` | `#snippets` |
236
214
  |---------|-------------|-------------|
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 |
215
+ | Position | Must come first 🏁 | Anywhere 📍 |
216
+ | Multiple per message | No | Yes |
217
+ | Live shell data | Yes 💻 | Yes 💻 |
218
+ | Best for | Triggering actions & workflows | Context injection 📝 |
241
219
 
242
220
  **Use both together:**
243
221
  ```
244
222
  /commit #conventional-commits #project-context
245
223
  ```
246
224
 
247
- ## Configuration
248
-
249
- ### Snippet Directory
250
-
251
- All snippets live in `~/.config/opencode/snippet/` as `.md` files.
252
-
253
- ### Debug Logging
225
+ ## Configuration### Debug Logging
254
226
 
255
227
  Enable debug logs by setting an environment variable:
256
228
 
package/index.ts CHANGED
@@ -1,33 +1,63 @@
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 { expandHashtags } from "./src/expander.js";
3
+ import { loadSnippets } from "./src/loader.js";
4
+ import { logger } from "./src/logger.js";
5
+ import { executeShellCommands, type ShellContext } from "./src/shell.js";
5
6
 
6
7
  /**
7
8
  * Snippets Plugin for OpenCode
8
- *
9
+ *
9
10
  * Expands hashtag-based shortcuts in user messages into predefined text snippets.
10
- *
11
+ *
11
12
  * @see https://github.com/JosXa/opencode-snippets for full documentation
12
13
  */
13
14
  export const SnippetsPlugin: Plugin = async (ctx) => {
14
- // Load all snippets at startup
15
- const snippets = await loadSnippets()
15
+ // Load all snippets at startup (global + project directory)
16
+ const startupStart = performance.now();
17
+ const snippets = await loadSnippets(ctx.directory);
18
+ const startupTime = performance.now() - startupStart;
19
+
20
+ logger.debug("Plugin startup complete", {
21
+ startupTimeMs: startupTime.toFixed(2),
22
+ snippetCount: snippets.size,
23
+ });
16
24
 
17
25
  return {
18
- "chat.message": async (input, output) => {
26
+ "chat.message": async (_input, output) => {
19
27
  // Only process user messages, never assistant messages
20
- if (output.message.role !== "user") return
21
-
28
+ if (output.message.role !== "user") return;
29
+
30
+ const messageStart = performance.now();
31
+ let expandTimeTotal = 0;
32
+ let shellTimeTotal = 0;
33
+ let processedParts = 0;
34
+
22
35
  for (const part of output.parts) {
23
36
  if (part.type === "text" && part.text) {
24
37
  // 1. Expand hashtags recursively with loop detection
25
- part.text = expandHashtags(part.text, snippets)
26
-
38
+ const expandStart = performance.now();
39
+ part.text = expandHashtags(part.text, snippets);
40
+ const expandTime = performance.now() - expandStart;
41
+ expandTimeTotal += expandTime;
42
+
27
43
  // 2. Execute shell commands: !`command`
28
- part.text = await executeShellCommands(part.text, ctx)
44
+ const shellStart = performance.now();
45
+ part.text = await executeShellCommands(part.text, ctx as unknown as ShellContext);
46
+ const shellTime = performance.now() - shellStart;
47
+ shellTimeTotal += shellTime;
48
+ processedParts += 1;
29
49
  }
30
50
  }
31
- }
32
- }
33
- }
51
+
52
+ const totalTime = performance.now() - messageStart;
53
+ if (processedParts > 0) {
54
+ logger.debug("Message processing complete", {
55
+ totalTimeMs: totalTime.toFixed(2),
56
+ snippetExpandTimeMs: expandTimeTotal.toFixed(2),
57
+ shellTimeMs: shellTimeTotal.toFixed(2),
58
+ processedParts,
59
+ });
60
+ }
61
+ },
62
+ };
63
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-snippets",
3
- "version": "1.1.2",
3
+ "version": "1.2.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,17 @@
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",
39
41
  "typescript": "^5"
40
42
  },
41
- "files": [
42
- "index.ts",
43
- "src/**/*.ts"
44
- ]
43
+ "scripts": {
44
+ "build": "tsgo --noEmit",
45
+ "format:check": "biome check .",
46
+ "format:fix": "biome check --write .",
47
+ "ai:check": "bun run format:fix"
48
+ },
49
+ "files": ["index.ts", "src/**/*.ts"]
45
50
  }
package/src/constants.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { join } from "node:path"
2
- import { homedir } from "node:os"
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
3
 
4
4
  /**
5
5
  * Regular expression patterns used throughout the plugin
@@ -7,15 +7,15 @@ import { homedir } from "node:os"
7
7
  export const PATTERNS = {
8
8
  /** Matches hashtags like #snippet-name */
9
9
  HASHTAG: /#([a-z0-9\-_]+)/gi,
10
-
10
+
11
11
  /** Matches shell commands like !`command` */
12
12
  SHELL_COMMAND: /!`([^`]+)`/g,
13
- } as const
13
+ } as const;
14
14
 
15
15
  /**
16
16
  * OpenCode configuration directory
17
17
  */
18
- export const OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode")
18
+ export const OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode");
19
19
 
20
20
  /**
21
21
  * File system paths
@@ -23,10 +23,10 @@ export const OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode")
23
23
  export const PATHS = {
24
24
  /** OpenCode configuration directory */
25
25
  CONFIG_DIR: OPENCODE_CONFIG_DIR,
26
-
26
+
27
27
  /** Snippets directory */
28
28
  SNIPPETS_DIR: join(OPENCODE_CONFIG_DIR, "snippet"),
29
- } as const
29
+ } as const;
30
30
 
31
31
  /**
32
32
  * Plugin configuration
@@ -34,4 +34,4 @@ export const PATHS = {
34
34
  export const CONFIG = {
35
35
  /** File extension for snippet files */
36
36
  SNIPPET_EXTENSION: ".md",
37
- } as const
37
+ } as const;