openhermes 1.12.1 → 1.13.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/README.md +4 -4
- package/bootstrap.mjs +2 -0
- package/harness/commands/ohc.md +13 -0
- package/harness/scripts/sync-commands.mjs +259 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
**Your OpenCode agent, leveled up.** Add it to your plugins and your agent gains a personality, a memory, a conscience, 25 specialist subagents,
|
|
15
|
+
**Your OpenCode agent, leveled up.** Add it to your plugins and your agent gains a personality, a memory, a conscience, 25 specialist subagents, 28 slash commands, 6 native memory tools, 10 procedural skills, autonomous checkpointing, and the discipline to self-improve.
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npm i openhermes
|
|
@@ -73,7 +73,7 @@ Either way, **no other config needed.** The plugin auto-registers:
|
|
|
73
73
|
| What | Details |
|
|
74
74
|
|------|---------|
|
|
75
75
|
| **25 subagents** | 7 core + 18 specialist:<br>**Core:** architect, planner, build-error-resolver, code-reviewer, security-reviewer, e2e-runner, explore<br>**Specialist:** tdd-guide, docs-lookup, doc-updater, refactor-cleaner, loop-operator, harness-optimizer, review-go, build-go, review-rust, build-rust, review-python, review-java, build-java, review-kotlin, build-kotlin, review-cpp, build-cpp, review-database |
|
|
76
|
-
| **
|
|
76
|
+
| **28 slash commands** | `/build-fix`, `/checkpoint`, `/code-review`, `/doctor`, `/eval`, `/go-build`, `/go-review`, `/harness-audit`, `/learn`, `/loop-start`, `/loop-status`, `/memory-search`, `/model-route`, `/ohc`, `/orchestrate`, `/plan`, `/quality-gate`, `/refactor-clean`, `/rust-build`, `/rust-review`, `/security`, `/setup-pm`, `/skill-create`, `/test-coverage`, `/update-codemaps`, `/update-docs`, `/update-me`, `/verify` |
|
|
77
77
|
| **6 native memory tools** | `add_memory`, `fetch_memory`, `list_memory`, `latest_memory`, `search_memory`, `archive_memory` — in-process, no MCP server needed |
|
|
78
78
|
| **10 procedural skills** | API design, backend patterns, coding standards, E2E testing, frontend patterns, frontend slides, security review, strategic compaction, TDD workflow, verification loop |
|
|
79
79
|
| **6 lifecycle plugins** | bootstrap, curator, autorecall, skill-builder, memory-tools, ohc |
|
|
@@ -85,7 +85,7 @@ You only need to define primary agents (like `build` or `OpenHermes`) in `openco
|
|
|
85
85
|
<details>
|
|
86
86
|
<summary><b>What happens on your next session</b></summary>
|
|
87
87
|
|
|
88
|
-
1. **Config hook** — BootstrapPlugin registers auto-config: 25 subagents,
|
|
88
|
+
1. **Config hook** — BootstrapPlugin registers auto-config: 25 subagents, 28 commands, 10 skill dirs.
|
|
89
89
|
2. **Chat transform hook** — bootstrap content is injected into the first user message:
|
|
90
90
|
- ★ **Constitution** (`soul.md`) — 11 immutable principles
|
|
91
91
|
- ★ **Runtime** (`RUNTIME.md`) — gather → delegate → verify → compress
|
|
@@ -153,7 +153,7 @@ System prompt is injected with your budget and floor. As context grows, progress
|
|
|
153
153
|
### BootstrapPlugin
|
|
154
154
|
_Registers agents, commands, skills at config hook; injects constitution + router + runtime into every session._
|
|
155
155
|
- **Hooks:** `config`, `chat.transform`
|
|
156
|
-
- Registers 25 subagents,
|
|
156
|
+
- Registers 25 subagents, 28 commands, 10 skill paths
|
|
157
157
|
|
|
158
158
|
### MemoryToolsPlugin
|
|
159
159
|
_Provides 6 native memory tools — no MCP server, no network, no sidecars._
|
package/bootstrap.mjs
CHANGED
|
@@ -37,6 +37,7 @@ Snapshot before mutation. Never delete unrelated files. Never assume \`%USERPROF
|
|
|
37
37
|
| **Memory recall cache** | \`openhermes/memory/recall/cache.json\` — read on session start, no MCP round-trip |
|
|
38
38
|
| **Subagents** | \`explore\` (read-only), \`general\` (multi-step), \`architect\`, \`planner\`, \`build-error-resolver\`, \`code-reviewer\`, \`security-reviewer\`, \`e2e-runner\`, \`docs-lookup\`, \`doc-updater\`, \`refactor-cleaner\`, \`loop-operator\`, \`harness-optimizer\`, \`tdd-guide\`, \`review-go\`, \`build-go\`, \`review-database\`, \`review-cpp\`, \`build-cpp\`, \`review-java\`, \`build-java\`, \`review-kotlin\`, \`build-kotlin\`, \`review-python\`, \`review-rust\`, \`build-rust\` |
|
|
39
39
|
| **Plugins** | \`curator\` (checkpoints, mistakes, audit, compaction), \`autorecall\` (recall cache on \`session.created\`), \`skill-builder\` (complex session detection) |
|
|
40
|
+
| **Slash commands** | <!-- COMMANDS:START --> \`/build-fix\`, \`/checkpoint\`, \`/code-review\`, \`/doctor\`, \`/eval\`, \`/go-build\`, \`/go-review\`, \`/harness-audit\`, \`/learn\`, \`/loop-start\`, \`/loop-status\`, \`/memory-search\`, \`/model-route\`, \`/ohc\`, \`/orchestrate\`, \`/plan\`, \`/quality-gate\`, \`/refactor-clean\`, \`/rust-build\`, \`/rust-review\`, \`/security\`, \`/setup-pm\`, \`/skill-create\`, \`/test-coverage\`, \`/update-codemaps\`, \`/update-docs\`, \`/update-me\`, \`/verify\`<!-- COMMANDS:END --> |
|
|
40
41
|
|
|
41
42
|
## Skills (available via \`skill\` tool)
|
|
42
43
|
|
|
@@ -169,6 +170,7 @@ export const BootstrapPlugin = async ({ client, directory }) => {
|
|
|
169
170
|
"memory-search": { agent: "OpenHermes", description: "Search OpenHermes memory with LLM summarization", subtask: true, template: ct("memory-search.md") },
|
|
170
171
|
"learn": { agent: "OpenHermes", description: "Create a new skill from recent work patterns", subtask: true, template: ct("learn.md") },
|
|
171
172
|
"ohc": { template: "", description: "OHC context management: /ohc status, /ohc compress [focus]" },
|
|
173
|
+
"update-me": { template: "", description: "Force reinstall OpenHermes plugin from latest source" },
|
|
172
174
|
"orchestrate": { agent: "planner", description: "Orchestrate multiple agents for complex tasks", subtask: true, template: ct("orchestrate.md") },
|
|
173
175
|
"eval": { agent: "planner", description: "Evaluate implementation against acceptance criteria", subtask: true, template: ct("eval.md") },
|
|
174
176
|
"model-route": { agent: "OpenHermes", description: "Recommend model tier by task complexity and budget", subtask: true, template: ct("model-route.md") },
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: OHC context management: /ohc status, /ohc compress [focus]
|
|
3
|
+
agent: OpenHermes
|
|
4
|
+
subtask: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Ohc Command
|
|
8
|
+
|
|
9
|
+
OHC context management — hook-handled.
|
|
10
|
+
|
|
11
|
+
Run `/ohc status` to check context usage, `/ohc compress [targetTokens] [focus]` to free space.
|
|
12
|
+
|
|
13
|
+
$ARGUMENTS
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const REPO_ROOT = path.resolve(__dirname, "..", "..")
|
|
8
|
+
const COMMANDS_DIR = path.join(REPO_ROOT, "harness", "commands")
|
|
9
|
+
const BOOTSTRAP_FILE = path.join(REPO_ROOT, "bootstrap.mjs")
|
|
10
|
+
const README_FILE = path.join(REPO_ROOT, "README.md")
|
|
11
|
+
|
|
12
|
+
const COMMANDS_START = "<!-- COMMANDS:START -->"
|
|
13
|
+
const COMMANDS_END = "<!-- COMMANDS:END -->"
|
|
14
|
+
|
|
15
|
+
const HOOK_ONLY = new Set(["update-me"])
|
|
16
|
+
|
|
17
|
+
function parseRegistry(src) {
|
|
18
|
+
const commands = {}
|
|
19
|
+
const blockMatch = src.match(/config\.command\s*=\s*\{([\s\S]*?)\n\s*\}/)
|
|
20
|
+
if (!blockMatch) return commands
|
|
21
|
+
|
|
22
|
+
let rest = blockMatch[1]
|
|
23
|
+
const entryRe = /^\s*"([^"]+)"\s*:\s*\{([\s\S]*?)\},?\s*$/gm
|
|
24
|
+
let m
|
|
25
|
+
while ((m = entryRe.exec(rest)) !== null) {
|
|
26
|
+
const name = m[1]
|
|
27
|
+
const body = m[2]
|
|
28
|
+
const agent = body.match(/agent:\s*"([^"]+)"/)?.[1] ?? ""
|
|
29
|
+
const description = body.match(/description:\s*"([^"]+)"/)?.[1] ?? ""
|
|
30
|
+
const template = body.match(/template:\s*ct\("([^"]+)"\)/) ? m[0].match(/template:\s*(ct\("[^"]+"\)|""|'')/)?.[1] : ""
|
|
31
|
+
const hasFile = template.includes("ct(")
|
|
32
|
+
commands[name] = { name, agent, description, hasFile }
|
|
33
|
+
}
|
|
34
|
+
return commands
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseCommandFile(filePath) {
|
|
38
|
+
const src = fs.readFileSync(filePath, "utf8")
|
|
39
|
+
const name = path.basename(filePath, ".md")
|
|
40
|
+
const description = src.match(/^description:\s*(.+)$/m)?.[1] ?? ""
|
|
41
|
+
const agent = src.match(/^agent:\s*(.+)$/m)?.[1] ?? ""
|
|
42
|
+
return { name, description, agent }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function scanCommandFiles() {
|
|
46
|
+
const files = fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith(".md"))
|
|
47
|
+
const map = {}
|
|
48
|
+
for (const f of files) {
|
|
49
|
+
const parsed = parseCommandFile(path.join(COMMANDS_DIR, f))
|
|
50
|
+
map[parsed.name] = parsed
|
|
51
|
+
}
|
|
52
|
+
return map
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getHookCommands() {
|
|
56
|
+
return [...HOOK_ONLY].map(name => ({ name, agent: "", description: "Hook-handled (see plugin source)" }))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function printReport(registry, filesOnDisk, hookCommands, cmdNames) {
|
|
60
|
+
console.log("")
|
|
61
|
+
console.log(" COMMAND SYNC REPORT")
|
|
62
|
+
console.log(" " + "=".repeat(50))
|
|
63
|
+
const registered = new Set(Object.keys(registry))
|
|
64
|
+
const onDisk = new Set(Object.keys(filesOnDisk))
|
|
65
|
+
const allHooks = new Set(hookCommands.map(h => h.name))
|
|
66
|
+
const hookNoReg = [...allHooks].filter(n => !registered.has(n) && !onDisk.has(n))
|
|
67
|
+
|
|
68
|
+
console.log(` Registered in bootstrap.mjs: ${registered.size}`)
|
|
69
|
+
console.log(` Files in harness/commands/: ${onDisk.size}`)
|
|
70
|
+
console.log(` Hook-only (no registry): ${hookNoReg.length}`)
|
|
71
|
+
console.log(` Total real commands: ${cmdNames.length}`)
|
|
72
|
+
console.log("")
|
|
73
|
+
|
|
74
|
+
const registeredNoFile = [...registered].filter(n => !onDisk.has(n))
|
|
75
|
+
const fileNotRegistered = [...onDisk].filter(n => !registered.has(n) && !allHooks.has(n))
|
|
76
|
+
|
|
77
|
+
if (registeredNoFile.length) {
|
|
78
|
+
console.log(" ⚠ REGISTERED, NO FILE:")
|
|
79
|
+
for (const n of registeredNoFile) {
|
|
80
|
+
const e = registry[n]
|
|
81
|
+
console.log(` /${n} — ${e.description}`)
|
|
82
|
+
}
|
|
83
|
+
console.log("")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (fileNotRegistered.length) {
|
|
87
|
+
console.log(" ⚠ FILE EXISTS, NOT REGISTERED:")
|
|
88
|
+
for (const n of fileNotRegistered) {
|
|
89
|
+
console.log(` /${n}`)
|
|
90
|
+
}
|
|
91
|
+
console.log("")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (hookNoReg.length) {
|
|
95
|
+
console.log(" ℹ HOOK-ONLY (no registry, no file):")
|
|
96
|
+
for (const n of hookNoReg) {
|
|
97
|
+
console.log(` /${n}`)
|
|
98
|
+
}
|
|
99
|
+
console.log("")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!registeredNoFile.length && !fileNotRegistered.length) {
|
|
103
|
+
console.log(" ✓ Everything in sync — no gaps.")
|
|
104
|
+
console.log("")
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function generateMissingFiles(registry, filesOnDisk) {
|
|
109
|
+
let count = 0
|
|
110
|
+
for (const [name, entry] of Object.entries(registry)) {
|
|
111
|
+
if (filesOnDisk[name]) continue
|
|
112
|
+
|
|
113
|
+
const templateFile = `${name}.md`
|
|
114
|
+
const agent = entry.agent || "OpenHermes"
|
|
115
|
+
const desc = entry.description
|
|
116
|
+
|
|
117
|
+
let md = `---
|
|
118
|
+
description: ${desc}
|
|
119
|
+
agent: ${agent}
|
|
120
|
+
subtask: true
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
# ${name.charAt(0).toUpperCase() + name.slice(1)} Command
|
|
124
|
+
|
|
125
|
+
`
|
|
126
|
+
|
|
127
|
+
if (name === "ohc") {
|
|
128
|
+
md += `OHC context management — hook-handled.\n\nRun \`/ohc status\` to check context usage, \`/ohc compress [targetTokens] [focus]\` to free space.\n\n$ARGUMENTS\n`
|
|
129
|
+
} else {
|
|
130
|
+
md += `${desc}: $ARGUMENTS\n`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fs.writeFileSync(path.join(COMMANDS_DIR, templateFile), md, "utf8")
|
|
134
|
+
console.log(` ✓ Created harness/commands/${templateFile}`)
|
|
135
|
+
count++
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (count === 0) console.log(" No missing files to generate.")
|
|
139
|
+
return count
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getCommandNames(registry, hookCommands) {
|
|
143
|
+
const registered = new Set(Object.keys(registry))
|
|
144
|
+
const hookOnly = hookCommands.filter(h => !registered.has(h.name)).map(h => h.name)
|
|
145
|
+
const names = [...registered, ...hookOnly]
|
|
146
|
+
names.sort()
|
|
147
|
+
return names
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatCommandRow(names) {
|
|
151
|
+
const count = names.length
|
|
152
|
+
const list = names.map(n => `\`/${n}\``).join(", ")
|
|
153
|
+
return `| **${count} slash commands** | ${list} |`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatBacktickList(names) {
|
|
157
|
+
return ` ${names.map(n => `\\\`/${n}\\\``).join(", ")}`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function syncBootstrap(registry, hookCommands) {
|
|
161
|
+
let src = fs.readFileSync(BOOTSTRAP_FILE, "utf8")
|
|
162
|
+
const cmdNames = getCommandNames(registry, hookCommands)
|
|
163
|
+
const row = formatCommandRow(cmdNames)
|
|
164
|
+
|
|
165
|
+
const startIdx = src.indexOf(COMMANDS_START)
|
|
166
|
+
const endIdx = src.indexOf(COMMANDS_END)
|
|
167
|
+
|
|
168
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
169
|
+
console.log(" ✗ Markers not found in bootstrap.mjs. Inserting markers.")
|
|
170
|
+
const plugRowRe = /(\|\s+\*\*Plugins\s*\*\*.*\|)/
|
|
171
|
+
const match = src.match(plugRowRe)
|
|
172
|
+
if (!match) {
|
|
173
|
+
console.log(" ✗ Could not find Plugins row to anchor insertion.")
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
const insertAfter = match.index + match[0].length
|
|
177
|
+
const newRow = `\n| **Slash commands** | ${COMMANDS_START} ${COMMANDS_END} |`
|
|
178
|
+
src = src.slice(0, insertAfter) + newRow + src.slice(insertAfter)
|
|
179
|
+
// Re-find markers for content replacement below
|
|
180
|
+
const newStart = src.indexOf(COMMANDS_START)
|
|
181
|
+
const newEnd = src.indexOf(COMMANDS_END)
|
|
182
|
+
if (newStart !== -1 && newEnd !== -1 && newEnd > newStart) {
|
|
183
|
+
const content = formatBacktickList(cmdNames)
|
|
184
|
+
src = src.slice(0, newStart + COMMANDS_START.length) + content + src.slice(newEnd)
|
|
185
|
+
}
|
|
186
|
+
fs.writeFileSync(BOOTSTRAP_FILE, src, "utf8")
|
|
187
|
+
console.log(" ✓ Inserted Slash commands row in bootstrap.mjs")
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (endIdx <= startIdx) {
|
|
192
|
+
console.log(" ✗ Markers out of order in bootstrap.mjs")
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const content = formatBacktickList(cmdNames)
|
|
197
|
+
src = src.slice(0, startIdx + COMMANDS_START.length) + content + src.slice(endIdx)
|
|
198
|
+
fs.writeFileSync(BOOTSTRAP_FILE, src, "utf8")
|
|
199
|
+
console.log(" ✓ Updated Slash commands row in bootstrap.mjs")
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function syncReadme(registry, hookCommands) {
|
|
204
|
+
let src = fs.readFileSync(README_FILE, "utf8")
|
|
205
|
+
const cmdNames = getCommandNames(registry, hookCommands)
|
|
206
|
+
const row = formatCommandRow(cmdNames)
|
|
207
|
+
|
|
208
|
+
const tableRowRe = /^\|\s*\*\*(\d+)\s*slash commands\s*\*\*.*\|$/m
|
|
209
|
+
const match = src.match(tableRowRe)
|
|
210
|
+
if (!match) {
|
|
211
|
+
console.log(" ✗ Could not find slash commands row in README.md")
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
src = src.replace(tableRowRe, row)
|
|
216
|
+
fs.writeFileSync(README_FILE, src, "utf8")
|
|
217
|
+
console.log(` ✓ Updated README.md: ${cmdNames.length} commands`)
|
|
218
|
+
return true
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Main
|
|
222
|
+
const args = process.argv.slice(2)
|
|
223
|
+
const doAudit = args.includes("--audit") || args.length === 0
|
|
224
|
+
const doGenerate = args.includes("--generate") || args.length === 0
|
|
225
|
+
const doSyncBootstrap = args.includes("--sync-bootstrap") || args.includes("--sync") || args.length === 0
|
|
226
|
+
const doSyncReadme = args.includes("--sync-readme") || args.includes("--sync") || args.length === 0
|
|
227
|
+
|
|
228
|
+
console.log(" ▸ syncing command definitions...")
|
|
229
|
+
console.log("")
|
|
230
|
+
|
|
231
|
+
const bootstrapSrc = fs.readFileSync(BOOTSTRAP_FILE, "utf8")
|
|
232
|
+
const registry = parseRegistry(bootstrapSrc)
|
|
233
|
+
const filesOnDisk = scanCommandFiles()
|
|
234
|
+
const hookCommands = getHookCommands()
|
|
235
|
+
const cmdNames = getCommandNames(registry, hookCommands)
|
|
236
|
+
|
|
237
|
+
if (doAudit) printReport(registry, filesOnDisk, hookCommands, cmdNames)
|
|
238
|
+
|
|
239
|
+
if (doGenerate) {
|
|
240
|
+
console.log(" [generate]")
|
|
241
|
+
generateMissingFiles(registry, filesOnDisk)
|
|
242
|
+
console.log("")
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (doSyncBootstrap) {
|
|
246
|
+
console.log(" [sync-bootstrap]")
|
|
247
|
+
const ok = syncBootstrap(registry, hookCommands)
|
|
248
|
+
if (!ok) process.exitCode = 1
|
|
249
|
+
console.log("")
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (doSyncReadme) {
|
|
253
|
+
console.log(" [sync-readme]")
|
|
254
|
+
const ok = syncReadme(registry, hookCommands)
|
|
255
|
+
if (!ok) process.exitCode = 1
|
|
256
|
+
console.log("")
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log(" done.")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openhermes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.1",
|
|
4
4
|
"description": "OpenHermes plugin suite for OpenCode — autonomous checkpointing, native memory tools, subagent routing, slash commands, and skill-candidate detection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
"harness/"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"test": "node --test"
|
|
29
|
+
"test": "node --test",
|
|
30
|
+
"commands:audit": "node harness/scripts/sync-commands.mjs --audit",
|
|
31
|
+
"commands:generate": "node harness/scripts/sync-commands.mjs --generate",
|
|
32
|
+
"commands:sync": "node harness/scripts/sync-commands.mjs --sync",
|
|
33
|
+
"commands:full": "node harness/scripts/sync-commands.mjs"
|
|
30
34
|
},
|
|
31
35
|
"keywords": [
|
|
32
36
|
"opencode",
|