bros-harness 0.1.1 → 0.1.3
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/CHANGELOG.md +8 -0
- package/README.md +3 -2
- package/docs/integrations/opencode.md +2 -2
- package/package.json +1 -1
- package/src/plugin.mjs +93 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.3 - 2026-06-03
|
|
4
|
+
|
|
5
|
+
- Fixed OpenCode 1.15 plugin loading by exporting the V1 plugin module shape with `id` and `server`.
|
|
6
|
+
|
|
7
|
+
## 0.1.2 - 2026-06-03
|
|
8
|
+
|
|
9
|
+
- Added package plugin agent registration so raw OpenCode installs can load BROS agents from `plugin: ["bros-harness"]`.
|
|
10
|
+
|
|
3
11
|
## 0.1.1 - 2026-06-03
|
|
4
12
|
|
|
5
13
|
- Fixed npm CLI bin packaging posture for `bros` by ensuring the packaged `bin/bros.mjs` entry is executable with a valid Node shebang.
|
package/README.md
CHANGED
|
@@ -116,6 +116,7 @@ The package plugin is intentionally narrow.
|
|
|
116
116
|
On load, it verifies packaged asset directories and uses OpenCode’s in-memory `config(cfg)` hook to add only:
|
|
117
117
|
|
|
118
118
|
- the package-relative BROS skills directory to `skills.paths`, when the existing field has the expected safe shape; and
|
|
119
|
+
- packaged BROS agent entries to `agent`, without overwriting existing agent keys; and
|
|
119
120
|
- packaged command prompt entries to `command`, without overwriting existing command keys.
|
|
120
121
|
|
|
121
122
|
It does **not**:
|
|
@@ -125,11 +126,11 @@ It does **not**:
|
|
|
125
126
|
- publish packages;
|
|
126
127
|
- register providers;
|
|
127
128
|
- add MCP servers;
|
|
128
|
-
- change permissions;
|
|
129
|
+
- change top-level permissions;
|
|
129
130
|
- configure telemetry;
|
|
130
131
|
- read, validate, or write secrets.
|
|
131
132
|
|
|
132
|
-
Packaged agent files are
|
|
133
|
+
Packaged agent files are registered from reviewed assets so a package-only OpenCode install can expose BROS agents without copying local files.
|
|
133
134
|
|
|
134
135
|
Three skipped raw skills remain excluded pending separate sanitized review. They are not imported by this package.
|
|
135
136
|
|
|
@@ -24,9 +24,9 @@ This is the preferred path for users and agents. Local path examples are contrib
|
|
|
24
24
|
|
|
25
25
|
## Runtime behavior
|
|
26
26
|
|
|
27
|
-
The package plugin resolves assets relative to its own package root. It validates key asset directories, then uses OpenCode's in-memory `config(cfg)` hook
|
|
27
|
+
The package plugin resolves assets relative to its own package root. It validates key asset directories, then uses OpenCode's in-memory `config(cfg)` hook to add the package-relative skills directory to `skills.paths` when safe, register packaged agent entries without overwriting existing agents, and add packaged command prompt entries without overwriting existing commands.
|
|
28
28
|
|
|
29
|
-
This runtime hook changes only the merged config object OpenCode passes to the plugin at startup. It is distinct from live user config file mutation: the plugin does not write `opencode.json`, `.opencode/`, global config files, or other filesystem config. The plugin does not register providers, MCP servers, permissions, telemetry, or secrets.
|
|
29
|
+
This runtime hook changes only the merged config object OpenCode passes to the plugin at startup. It is distinct from live user config file mutation: the plugin does not write `opencode.json`, `.opencode/`, global OpenCode config files, or other filesystem config. The plugin does not register providers, MCP servers, top-level permissions, telemetry, or secrets.
|
|
30
30
|
|
|
31
31
|
## Safe agent workflow
|
|
32
32
|
|
package/package.json
CHANGED
package/src/plugin.mjs
CHANGED
|
@@ -43,6 +43,80 @@ function parseCommandMarkdown(markdown) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function parseYamlScalar(value) {
|
|
47
|
+
const trimmed = value.trim();
|
|
48
|
+
if (trimmed === "") return "";
|
|
49
|
+
if (trimmed === "true") return true;
|
|
50
|
+
if (trimmed === "false") return false;
|
|
51
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
52
|
+
return trimmed.slice(1, -1);
|
|
53
|
+
}
|
|
54
|
+
return trimmed;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseSimpleYamlObject(yaml) {
|
|
58
|
+
const root = {};
|
|
59
|
+
const stack = [{ indent: -1, value: root }];
|
|
60
|
+
|
|
61
|
+
for (const rawLine of yaml.split("\n")) {
|
|
62
|
+
if (!rawLine.trim() || rawLine.trim().startsWith("#")) continue;
|
|
63
|
+
|
|
64
|
+
const indent = rawLine.match(/^ */)?.[0].length ?? 0;
|
|
65
|
+
const line = rawLine.trim();
|
|
66
|
+
const match = line.match(/^(.+?):(?:\s*(.*))?$/);
|
|
67
|
+
if (!match) continue;
|
|
68
|
+
|
|
69
|
+
const key = match[1].trim().replace(/^['"]|['"]$/g, "");
|
|
70
|
+
const rawValue = match[2] ?? "";
|
|
71
|
+
|
|
72
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
73
|
+
stack.pop();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const parent = stack[stack.length - 1].value;
|
|
77
|
+
if (rawValue === "") {
|
|
78
|
+
parent[key] = {};
|
|
79
|
+
stack.push({ indent, value: parent[key] });
|
|
80
|
+
} else {
|
|
81
|
+
parent[key] = parseYamlScalar(rawValue);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return root;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseAgentMarkdown(markdown) {
|
|
89
|
+
const match = markdown.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
90
|
+
if (!match) return null;
|
|
91
|
+
|
|
92
|
+
const frontmatter = parseSimpleYamlObject(match[1]);
|
|
93
|
+
const { name, ...agent } = frontmatter;
|
|
94
|
+
if (!name) return null;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
name,
|
|
98
|
+
agent: {
|
|
99
|
+
...agent,
|
|
100
|
+
prompt: match[2].trim()
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function loadPackagedAgents() {
|
|
106
|
+
const files = (await readdir(brosHarness.agentsDir))
|
|
107
|
+
.filter((file) => file.endsWith(".md"))
|
|
108
|
+
.filter((file) => file !== "README.md")
|
|
109
|
+
.sort();
|
|
110
|
+
|
|
111
|
+
const agents = {};
|
|
112
|
+
for (const file of files) {
|
|
113
|
+
const markdown = await readFile(join(brosHarness.agentsDir, file), "utf8");
|
|
114
|
+
const parsed = parseAgentMarkdown(markdown);
|
|
115
|
+
if (parsed) agents[parsed.name] = parsed.agent;
|
|
116
|
+
}
|
|
117
|
+
return agents;
|
|
118
|
+
}
|
|
119
|
+
|
|
46
120
|
async function loadPackagedCommands() {
|
|
47
121
|
const files = (await readdir(brosHarness.commandsDir))
|
|
48
122
|
.filter((file) => file.endsWith(".md"))
|
|
@@ -85,14 +159,32 @@ function mergeCommands(cfg, commands) {
|
|
|
85
159
|
}
|
|
86
160
|
}
|
|
87
161
|
|
|
88
|
-
|
|
162
|
+
function mergeAgents(cfg, agents) {
|
|
163
|
+
if (cfg.agent !== undefined && (cfg.agent === null || typeof cfg.agent !== "object" || Array.isArray(cfg.agent))) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
cfg.agent ??= {};
|
|
168
|
+
for (const [name, agent] of Object.entries(agents)) {
|
|
169
|
+
cfg.agent[name] ??= agent;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function brosHarnessServer(_input = {}, _options = {}) {
|
|
89
174
|
await verifyBrosHarnessAssets();
|
|
175
|
+
const agents = await loadPackagedAgents();
|
|
90
176
|
const commands = await loadPackagedCommands();
|
|
91
177
|
|
|
92
178
|
return {
|
|
93
179
|
config(cfg) {
|
|
94
180
|
mergeSkillsPath(cfg);
|
|
181
|
+
mergeAgents(cfg, agents);
|
|
95
182
|
mergeCommands(cfg, commands);
|
|
96
183
|
}
|
|
97
184
|
};
|
|
98
185
|
}
|
|
186
|
+
|
|
187
|
+
export default {
|
|
188
|
+
id: "bros-harness",
|
|
189
|
+
server: brosHarnessServer
|
|
190
|
+
};
|