@nt-ai-lab/opencode-skillz 0.2.8 → 0.3.1
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/AGENTS.md +1 -0
- package/package.json +15 -4
- package/index.js +0 -237
package/AGENTS.md
CHANGED
|
@@ -15,6 +15,7 @@ Purpose: package OpenCode workflow assets as a plugin.
|
|
|
15
15
|
- Put voice and persona behavior in agent prompts.
|
|
16
16
|
- Treat reusable skill content as command templates in `commands/`.
|
|
17
17
|
- Commands should be manually invoked by default.
|
|
18
|
+
- All plugin-provided commands must use the `nt-skillz:` prefix, including code-backed commands.
|
|
18
19
|
- Prefer minimal additions; only add new commands when needed.
|
|
19
20
|
- Do not add `agent:` in command frontmatter unless the command must force a specific agent.
|
|
20
21
|
|
package/package.json
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nt-ai-lab/opencode-skillz",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Bundled OpenCode commands and agents",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json"
|
|
9
16
|
},
|
|
10
17
|
"files": [
|
|
11
|
-
"
|
|
18
|
+
"dist",
|
|
12
19
|
"commands",
|
|
13
20
|
"agents",
|
|
14
21
|
"AGENTS.md",
|
|
15
22
|
"README.md"
|
|
16
23
|
],
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^24.7.2",
|
|
26
|
+
"typescript": "^5.9.3"
|
|
27
|
+
},
|
|
17
28
|
"publishConfig": {
|
|
18
29
|
"access": "public"
|
|
19
30
|
}
|
package/index.js
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
import { fileURLToPath } from "node:url"
|
|
4
|
-
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
-
const pluginRoot = __dirname
|
|
7
|
-
const commandNamespace = "nt-skillz"
|
|
8
|
-
|
|
9
|
-
function buildCommandName(name) {
|
|
10
|
-
return `${commandNamespace}:${name}`
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function extractFrontmatter(content) {
|
|
14
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/)
|
|
15
|
-
if (!match) return { meta: {}, body: content }
|
|
16
|
-
|
|
17
|
-
const meta = {}
|
|
18
|
-
for (const rawLine of match[1].split("\n")) {
|
|
19
|
-
const line = rawLine.trim()
|
|
20
|
-
if (!line || line.startsWith("#")) continue
|
|
21
|
-
const idx = line.indexOf(":")
|
|
22
|
-
if (idx <= 0) continue
|
|
23
|
-
|
|
24
|
-
const key = line.slice(0, idx).trim()
|
|
25
|
-
let value = line.slice(idx + 1).trim()
|
|
26
|
-
value = value.replace(/^['\"]|['\"]$/g, "")
|
|
27
|
-
if (value === "true") value = true
|
|
28
|
-
if (value === "false") value = false
|
|
29
|
-
meta[key] = value
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return { meta, body: match[2] }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function readMarkdownFiles(dirPath) {
|
|
36
|
-
if (!fs.existsSync(dirPath)) return []
|
|
37
|
-
return fs
|
|
38
|
-
.readdirSync(dirPath)
|
|
39
|
-
.filter((file) => file.endsWith(".md"))
|
|
40
|
-
.sort((a, b) => a.localeCompare(b))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function normalizeCommandReference(value) {
|
|
44
|
-
if (!value || typeof value !== "string") return ""
|
|
45
|
-
return value.trim().replace(/_/g, "-")
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function loadCommands() {
|
|
49
|
-
const commandsDir = path.join(pluginRoot, "commands")
|
|
50
|
-
const files = readMarkdownFiles(commandsDir)
|
|
51
|
-
const rawCommands = {}
|
|
52
|
-
|
|
53
|
-
for (const file of files) {
|
|
54
|
-
const name = file.replace(/\.md$/, "")
|
|
55
|
-
const fullPath = path.join(commandsDir, file)
|
|
56
|
-
const content = fs.readFileSync(fullPath, "utf8")
|
|
57
|
-
const { meta, body } = extractFrontmatter(content)
|
|
58
|
-
|
|
59
|
-
rawCommands[name] = {
|
|
60
|
-
meta,
|
|
61
|
-
body: body.trim(),
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const commands = {}
|
|
66
|
-
|
|
67
|
-
function buildComposedTemplate(name, stack = new Set()) {
|
|
68
|
-
const rawCommand = rawCommands[name]
|
|
69
|
-
if (!rawCommand) return ""
|
|
70
|
-
if (stack.has(name)) return rawCommand.body
|
|
71
|
-
|
|
72
|
-
stack.add(name)
|
|
73
|
-
|
|
74
|
-
let template = rawCommand.body
|
|
75
|
-
const composeAfterName = normalizeCommandReference(rawCommand.meta.compose_after)
|
|
76
|
-
const composedCommand = rawCommands[composeAfterName]
|
|
77
|
-
|
|
78
|
-
if (composeAfterName && composedCommand) {
|
|
79
|
-
const composedTemplate = buildComposedTemplate(composeAfterName, stack)
|
|
80
|
-
if (composedTemplate) {
|
|
81
|
-
template = [
|
|
82
|
-
template,
|
|
83
|
-
`In addition you must adhere to the following:\n\n${composedTemplate}`,
|
|
84
|
-
]
|
|
85
|
-
.filter(Boolean)
|
|
86
|
-
.join("\n\n")
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
stack.delete(name)
|
|
91
|
-
|
|
92
|
-
return template.trim()
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
for (const [name, rawCommand] of Object.entries(rawCommands)) {
|
|
96
|
-
const { meta } = rawCommand
|
|
97
|
-
const command = {
|
|
98
|
-
description: meta.description || `Run /${name}`,
|
|
99
|
-
template: buildComposedTemplate(name),
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (meta.agent) command.agent = meta.agent
|
|
103
|
-
if (meta.model) command.model = meta.model
|
|
104
|
-
if (typeof meta.subtask === "boolean") command.subtask = meta.subtask
|
|
105
|
-
|
|
106
|
-
commands[name] = command
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return commands
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function parseCsvList(value) {
|
|
113
|
-
if (!value || typeof value !== "string") return []
|
|
114
|
-
return value
|
|
115
|
-
.split(",")
|
|
116
|
-
.map((item) => item.trim())
|
|
117
|
-
.filter(Boolean)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function materializePreloadedTemplate(template) {
|
|
121
|
-
return template.replace(/\$ARGUMENTS/g, "all relevant current work in this session")
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function loadAgents(commands) {
|
|
125
|
-
const agentsDir = path.join(pluginRoot, "agents")
|
|
126
|
-
const files = readMarkdownFiles(agentsDir)
|
|
127
|
-
const rawAgents = {}
|
|
128
|
-
|
|
129
|
-
for (const file of files) {
|
|
130
|
-
const name = file.replace(/\.md$/, "")
|
|
131
|
-
const fullPath = path.join(agentsDir, file)
|
|
132
|
-
const content = fs.readFileSync(fullPath, "utf8")
|
|
133
|
-
const { meta, body } = extractFrontmatter(content)
|
|
134
|
-
|
|
135
|
-
rawAgents[name] = {
|
|
136
|
-
meta,
|
|
137
|
-
body: body.trim(),
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const agents = {}
|
|
142
|
-
|
|
143
|
-
function collectPreloadedCommands(agentName, stack = new Set()) {
|
|
144
|
-
const rawAgent = rawAgents[agentName]
|
|
145
|
-
if (!rawAgent) return []
|
|
146
|
-
if (stack.has(agentName)) return parseCsvList(rawAgent.meta.preload_commands)
|
|
147
|
-
|
|
148
|
-
stack.add(agentName)
|
|
149
|
-
|
|
150
|
-
const merged = []
|
|
151
|
-
const parentAgentName = typeof rawAgent.meta.extends === "string" ? rawAgent.meta.extends.trim() : ""
|
|
152
|
-
if (parentAgentName && rawAgents[parentAgentName]) {
|
|
153
|
-
merged.push(...collectPreloadedCommands(parentAgentName, stack))
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
merged.push(...parseCsvList(rawAgent.meta.preload_commands))
|
|
157
|
-
stack.delete(agentName)
|
|
158
|
-
|
|
159
|
-
return [...new Set(merged)]
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
for (const [name, raw] of Object.entries(rawAgents)) {
|
|
163
|
-
const { meta, body } = raw
|
|
164
|
-
const promptParts = []
|
|
165
|
-
|
|
166
|
-
const parentAgentName = typeof meta.extends === "string" ? meta.extends.trim() : ""
|
|
167
|
-
if (parentAgentName && rawAgents[parentAgentName]) {
|
|
168
|
-
const parentPrompt = rawAgents[parentAgentName].body
|
|
169
|
-
if (parentPrompt) promptParts.push(parentPrompt)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (body) {
|
|
173
|
-
promptParts.push(body)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const preloadedCommands = collectPreloadedCommands(name)
|
|
177
|
-
for (const commandName of preloadedCommands) {
|
|
178
|
-
const command = commands[commandName]
|
|
179
|
-
if (!command || !command.template) continue
|
|
180
|
-
const rendered = materializePreloadedTemplate(command.template)
|
|
181
|
-
promptParts.push(`[Preloaded command /${commandName}]\n${rendered}`)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const agent = {
|
|
185
|
-
prompt: promptParts.join("\n\n").trim(),
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (meta.description) agent.description = meta.description
|
|
189
|
-
if (meta.mode) agent.mode = meta.mode
|
|
190
|
-
if (meta.model) agent.model = meta.model
|
|
191
|
-
if (meta.color) agent.color = meta.color
|
|
192
|
-
|
|
193
|
-
agents[name] = agent
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return agents
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export const OpencodeSkillzPlugin = async () => {
|
|
200
|
-
return {
|
|
201
|
-
config: async (config) => {
|
|
202
|
-
config.command = config.command || {}
|
|
203
|
-
config.agent = config.agent || {}
|
|
204
|
-
|
|
205
|
-
const commands = loadCommands()
|
|
206
|
-
for (const [name, command] of Object.entries(commands)) {
|
|
207
|
-
const commandName = buildCommandName(name)
|
|
208
|
-
if (!config.command[commandName]) {
|
|
209
|
-
config.command[commandName] = command
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const agents = loadAgents(commands)
|
|
214
|
-
for (const [name, agent] of Object.entries(agents)) {
|
|
215
|
-
if (!config.agent[name]) {
|
|
216
|
-
config.agent[name] = agent
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
config.agent.build = {
|
|
221
|
-
...(config.agent.build || {}),
|
|
222
|
-
disable: true,
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
config.agent.plan = {
|
|
226
|
-
...(config.agent.plan || {}),
|
|
227
|
-
disable: true,
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (!config.default_agent && config.agent.default) {
|
|
231
|
-
config.default_agent = "default"
|
|
232
|
-
}
|
|
233
|
-
},
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export default OpencodeSkillzPlugin
|