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 +43 -71
- package/index.ts +47 -17
- package/package.json +10 -5
- package/src/constants.ts +8 -8
- package/src/expander.test.ts +456 -0
- package/src/expander.ts +47 -37
- package/src/loader.test.ts +261 -0
- package/src/loader.ts +99 -45
- package/src/logger.test.ts +136 -0
- package/src/logger.ts +95 -95
- package/src/shell.ts +30 -24
- package/src/types.ts +6 -6
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
|
|
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
|
|
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
|
-
##
|
|
139
|
-
|
|
140
|
-
### Hashtag Expansion
|
|
109
|
+
## Where to Store Snippets
|
|
141
110
|
|
|
142
|
-
|
|
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
|
|
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
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
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
|
-
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
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 {
|
|
2
|
-
import {
|
|
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;
|