rolebox 0.1.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 ADDED
@@ -0,0 +1,167 @@
1
+ # rolebox
2
+
3
+ Define custom AI agent roles for [opencode](https://github.com/nicholasgriffintn/opencode) via YAML. Each role gets its own prompt, model, skills, and permissions. No code required.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ # Install
9
+ cd ~/.config/opencode && npm install rolebox
10
+ ```
11
+
12
+ Add to `opencode.jsonc`:
13
+
14
+ ```jsonc
15
+ {
16
+ "plugin": ["rolebox"]
17
+ }
18
+ ```
19
+
20
+ Create a role:
21
+
22
+ ```bash
23
+ mkdir -p ~/.config/opencode/rolebox/copywriter
24
+ ```
25
+
26
+ ```yaml
27
+ # ~/.config/opencode/rolebox/copywriter/role.yaml
28
+ name: Copywriter
29
+ description: Writes concise, punchy copy.
30
+ prompt: |
31
+ You are a copywriter. Short sentences. No jargon. Every word earns its place.
32
+ ```
33
+
34
+ Restart opencode. The role appears in your agent list.
35
+
36
+ ## Directory structure
37
+
38
+ ```
39
+ ~/.config/opencode/
40
+ ├── opencode.jsonc
41
+ ├── rolebox/
42
+ │ ├── copywriter/
43
+ │ │ └── role.yaml
44
+ │ ├── ai-designer/
45
+ │ │ ├── role.yaml
46
+ │ │ └── skills/
47
+ │ │ ├── visual-design.md
48
+ │ │ └── interaction-patterns/
49
+ │ │ └── SKILL.md
50
+ │ └── ...
51
+ └── skills/ # Global opencode skills
52
+ ```
53
+
54
+ ## Configuration reference
55
+
56
+ ### role.yaml
57
+
58
+ ```yaml
59
+ # Required
60
+ name: string
61
+ description: string
62
+ prompt: | # Or use prompt_file (mutually exclusive)
63
+ Your system prompt here...
64
+
65
+ # Optional
66
+ model: string # e.g. "gpt-4", "claude-3-sonnet"
67
+ mode: primary | subagent | all # Default: "primary"
68
+ color: string # UI color
69
+ variant: string # Model variant
70
+ temperature: number # 0.0 - 2.0
71
+ top_p: number # 0.0 - 1.0
72
+ prompt_file: string # Path to external prompt file
73
+
74
+ # Skills
75
+ skills: # From rolebox/{role}/skills/
76
+ - my-skill
77
+ opencode_skills: # From ~/.config/opencode/skills/
78
+ - humanizer
79
+
80
+ # Permissions
81
+ permission:
82
+ allow:
83
+ - Read
84
+ - Grep
85
+ deny:
86
+ - Bash
87
+ tools:
88
+ Bash: false
89
+ ```
90
+
91
+ ### Environment variables
92
+
93
+ Use `{env:VARIABLE_NAME}` anywhere in role.yaml. Resolved at startup.
94
+
95
+ ```yaml
96
+ model: "{env:PREFERRED_MODEL}"
97
+ prompt: |
98
+ You work for {env:COMPANY_NAME}...
99
+ ```
100
+
101
+ ## Skills
102
+
103
+ Skills use standard opencode SKILL.md format with YAML frontmatter:
104
+
105
+ ```markdown
106
+ ---
107
+ name: my-skill
108
+ description: What this skill does
109
+ ---
110
+
111
+ Skill instructions here...
112
+ ```
113
+
114
+ ### Resolution order (first match wins)
115
+
116
+ 1. `{roleDir}/skills/{name}/SKILL.md` (role-local, directory format)
117
+ 2. `{roleDir}/skills/{name}.md` (role-local, single file)
118
+ 3. `~/.config/opencode/skills/{name}/SKILL.md` (global, directory format)
119
+ 4. `~/.config/opencode/skills/{name}.md` (global, single file)
120
+
121
+ ### How skills work at runtime
122
+
123
+ Rolebox syncs role-local skills into `~/.config/opencode/skills/` at startup. The role's prompt gets an `<available_skills>` XML block listing skill names and descriptions. The model calls the `skill` tool when it needs specialized instructions.
124
+
125
+ ### Example: multi-skill role
126
+
127
+ ```yaml
128
+ name: AI Designer
129
+ description: Professional UI/UX designer producing design specification documents.
130
+ mode: primary
131
+ prompt: |
132
+ You are a professional UI/UX designer...
133
+ skills:
134
+ - ai-designer-core
135
+ - ai-designer-visual
136
+ - ai-designer-interaction
137
+ - ai-designer-psychology
138
+ - ai-designer-research
139
+ - ai-designer-system
140
+ - ai-designer-antipatterns
141
+ ```
142
+
143
+ ## Startup sequence
144
+
145
+ 1. Scans `~/.config/opencode/rolebox/*/role.yaml`
146
+ 2. Parses YAML, resolves `{env:*}` variables, loads `prompt_file` if specified
147
+ 3. Resolves skill references to SKILL.md files
148
+ 4. Registers each role as an opencode agent
149
+ 5. Syncs skills to `~/.config/opencode/skills/`
150
+ 6. Syncs agent .md files to `~/.claude/agents/` for compatibility
151
+
152
+ ## Compatibility
153
+
154
+ Works alongside oh-my-openagent. If both are installed, rolebox roles appear in the agent list and skills are discoverable by the skill tool. No conflicts.
155
+
156
+ ## Error handling
157
+
158
+ Invalid YAML or missing files won't crash opencode. The broken role is skipped, other roles load normally. Missing skills produce a warning but don't block the role.
159
+
160
+ ## Limitations
161
+
162
+ - No role inheritance
163
+ - No hot-reload (restart opencode to pick up changes)
164
+ - No model existence validation
165
+ - No runtime role switching
166
+ - No MCP server integration
167
+ - No conditional skills based on project context
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Environment variable resolver for rolebox.
3
+ *
4
+ * Replaces `{env:VARIABLE_NAME}` patterns in strings with the corresponding
5
+ * environment variable value. If the variable is not set, the original pattern
6
+ * is preserved and a warning is logged.
7
+ */
8
+ /**
9
+ * Replace `{env:VARIABLE_NAME}` placeholders with actual environment values.
10
+ *
11
+ * @param value - The string potentially containing env var placeholders.
12
+ * @returns The resolved string with environment variables substituted.
13
+ * Unresolvable placeholders are left as-is.
14
+ */
15
+ export declare function resolveEnvVars(value: string): string;
16
+ /**
17
+ * Deep-resolve all string values in an object/array tree.
18
+ *
19
+ * Recursively walks through nested objects and arrays, resolving any
20
+ * `{env:VARIABLE_NAME}` patterns found in string values.
21
+ *
22
+ * @param obj - The value to resolve (string, object, array, or primitive).
23
+ * @returns A deeply resolved copy of the input.
24
+ */
25
+ export declare function resolveEnvVarsDeep(obj: unknown): unknown;
26
+ //# sourceMappingURL=env-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-resolver.d.ts","sourceRoot":"","sources":["../src/env-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAkBxD"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Environment variable resolver for rolebox.
3
+ *
4
+ * Replaces `{env:VARIABLE_NAME}` patterns in strings with the corresponding
5
+ * environment variable value. If the variable is not set, the original pattern
6
+ * is preserved and a warning is logged.
7
+ */
8
+ const ENV_VAR_PATTERN = /(?<!\{env:)\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g;
9
+ /**
10
+ * Replace `{env:VARIABLE_NAME}` placeholders with actual environment values.
11
+ *
12
+ * @param value - The string potentially containing env var placeholders.
13
+ * @returns The resolved string with environment variables substituted.
14
+ * Unresolvable placeholders are left as-is.
15
+ */
16
+ export function resolveEnvVars(value) {
17
+ return value.replace(ENV_VAR_PATTERN, (match, varName) => {
18
+ const envValue = process.env[varName];
19
+ if (envValue === undefined) {
20
+ console.warn(`[env-resolver] Environment variable "${varName}" is not set; keeping placeholder "${match}"`);
21
+ return match;
22
+ }
23
+ return envValue;
24
+ });
25
+ }
26
+ /**
27
+ * Deep-resolve all string values in an object/array tree.
28
+ *
29
+ * Recursively walks through nested objects and arrays, resolving any
30
+ * `{env:VARIABLE_NAME}` patterns found in string values.
31
+ *
32
+ * @param obj - The value to resolve (string, object, array, or primitive).
33
+ * @returns A deeply resolved copy of the input.
34
+ */
35
+ export function resolveEnvVarsDeep(obj) {
36
+ if (typeof obj === 'string') {
37
+ return resolveEnvVars(obj);
38
+ }
39
+ if (Array.isArray(obj)) {
40
+ return obj.map((item) => resolveEnvVarsDeep(item));
41
+ }
42
+ if (obj !== null && typeof obj === 'object') {
43
+ const result = {};
44
+ for (const [key, value] of Object.entries(obj)) {
45
+ result[key] = resolveEnvVarsDeep(value);
46
+ }
47
+ return result;
48
+ }
49
+ return obj;
50
+ }
51
+ //# sourceMappingURL=env-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-resolver.js","sourceRoot":"","sources":["../src/env-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,eAAe,GAAG,8CAA8C,CAAC;AAEvE;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,OAAe,EAAE,EAAE;QAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,CAAC,IAAI,CAAC,wCAAwC,OAAO,sCAAsC,KAAK,GAAG,CAAC,CAAC;YAC5G,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAY;IAC7C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ /**
3
+ * OpenCode plugin for rolebox — define custom agent roles via YAML
4
+ * configuration files with custom prompts, models, skills, and permissions.
5
+ */
6
+ declare const RoleboxPlugin: Plugin;
7
+ export default RoleboxPlugin;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA+NlD;;;GAGG;AACH,QAAA,MAAM,aAAa,EAAE,MA+BpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,246 @@
1
+ import path from "node:path";
2
+ import os from "node:os";
3
+ import { writeFileSync, readFileSync, mkdirSync, rmdirSync, readdirSync, unlinkSync, symlinkSync, lstatSync, existsSync } from "node:fs";
4
+ import { discoverRoles } from "./role-loader";
5
+ import { resolveSkills } from "./skill-resolver";
6
+ import { buildAgentPrompt } from "./prompt-builder";
7
+ /**
8
+ * Resolve all discovered roles into ResolvedRole objects.
9
+ *
10
+ * For each role in the Map:
11
+ * 1. Gather both role-local skills (skills[]) and opencode-global skills
12
+ * (opencode_skills[]) into a single name list.
13
+ * 2. Resolve those names to file paths via the 4-candidate priority system.
14
+ * 3. Build the final agent prompt with <available_skills> XML block.
15
+ * 4. Return a ResolvedRole object for each successfully-resolved role.
16
+ *
17
+ * Errors from individual role resolution are caught and the failing role is
18
+ * silently skipped (the returned array omits it).
19
+ */
20
+ async function resolveAllRoles(roles, roleboxDir, globalSkillsDir) {
21
+ const resolved = [];
22
+ for (const [roleId, config] of roles) {
23
+ try {
24
+ const roleDir = path.join(roleboxDir, roleId);
25
+ // Combine both role-local and opencode-global skill names.
26
+ const localSkills = config.skills ?? [];
27
+ const globalSkills = config.opencode_skills ?? [];
28
+ const allSkillNames = [...localSkills, ...globalSkills];
29
+ let skills = [];
30
+ if (allSkillNames.length > 0) {
31
+ skills = await resolveSkills(allSkillNames, roleDir, globalSkillsDir);
32
+ }
33
+ const prompt = buildAgentPrompt(config, skills);
34
+ resolved.push({ id: roleId, config, prompt, skills });
35
+ }
36
+ catch {
37
+ // Silently skip roles that fail during resolution.
38
+ }
39
+ }
40
+ return resolved;
41
+ }
42
+ /**
43
+ * Build an SDK-compatible AgentConfig from a ResolvedRole.
44
+ *
45
+ * Only defined fields are included so that defaults in opencode itself
46
+ * are not accidentally overwritten with empty strings or zero values.
47
+ */
48
+ function buildAgentConfig(resolved) {
49
+ const { config } = resolved;
50
+ const agent = {
51
+ prompt: resolved.prompt,
52
+ mode: config.mode ?? "primary",
53
+ };
54
+ if (config.model !== undefined) {
55
+ agent.model = config.model;
56
+ }
57
+ if (config.description !== undefined) {
58
+ agent.description = config.description;
59
+ }
60
+ if (config.color !== undefined) {
61
+ agent.color = config.color;
62
+ }
63
+ if (config.variant !== undefined) {
64
+ agent.variant = config.variant;
65
+ }
66
+ if (config.temperature !== undefined) {
67
+ agent.temperature = config.temperature;
68
+ }
69
+ if (config.top_p !== undefined) {
70
+ agent.top_p = config.top_p;
71
+ }
72
+ if (config.tools !== undefined) {
73
+ agent.tools = config.tools;
74
+ }
75
+ if (config.permission !== undefined) {
76
+ agent.permission = config.permission;
77
+ }
78
+ return agent;
79
+ }
80
+ /**
81
+ * Marker prefix used in agent .md files to identify rolebox-managed agents.
82
+ * This allows cleanup of stale agents when roles are removed from rolebox.
83
+ */
84
+ const ROLEBOX_MARKER = "<!-- rolebox-managed -->";
85
+ /**
86
+ * Write agent definitions to ~/.claude/agents/ as fallback registration.
87
+ *
88
+ * This ensures agents are discoverable by oh-my-openagent (which reads
89
+ * from this directory) even though rolebox also registers them via the
90
+ * standard config hook. Agents managed by rolebox are tagged with a
91
+ * marker comment so they can be cleaned up if the role is removed.
92
+ */
93
+ function syncAgentFiles(resolvedRoles) {
94
+ const agentsDir = path.join(os.homedir(), ".claude", "agents");
95
+ try {
96
+ mkdirSync(agentsDir, { recursive: true });
97
+ }
98
+ catch {
99
+ return; // Can't write — skip silently
100
+ }
101
+ try {
102
+ const existing = readdirSync(agentsDir);
103
+ for (const file of existing) {
104
+ if (!file.endsWith(".md"))
105
+ continue;
106
+ const filePath = path.join(agentsDir, file);
107
+ try {
108
+ const text = readFileSync(filePath, "utf-8");
109
+ if (text.includes(ROLEBOX_MARKER)) {
110
+ const roleId = file.replace(/\.md$/, "");
111
+ if (!resolvedRoles.some((r) => r.id === roleId)) {
112
+ unlinkSync(filePath);
113
+ }
114
+ }
115
+ }
116
+ catch {
117
+ continue;
118
+ }
119
+ }
120
+ }
121
+ catch {
122
+ // Directory not readable — skip cleanup
123
+ }
124
+ // Write current roles
125
+ for (const resolved of resolvedRoles) {
126
+ const { config } = resolved;
127
+ const lines = [
128
+ ROLEBOX_MARKER,
129
+ "---",
130
+ `name: ${config.name}`,
131
+ `description: ${config.description}`,
132
+ `mode: ${config.mode ?? "primary"}`,
133
+ ];
134
+ if (config.model)
135
+ lines.push(`model: ${config.model}`);
136
+ lines.push("---", "", resolved.prompt);
137
+ const filePath = path.join(agentsDir, `${resolved.id}.md`);
138
+ try {
139
+ writeFileSync(filePath, lines.join("\n"), "utf-8");
140
+ }
141
+ catch {
142
+ // Skip if write fails
143
+ }
144
+ }
145
+ }
146
+ const ROLEBOX_SKILL_PREFIX = "rolebox--";
147
+ /**
148
+ * Sync rolebox skills into ~/.config/opencode/skills/ for oh-my-openagent discovery.
149
+ *
150
+ * oh-my-openagent's loadSkillsFromDir treats symlinks as directories:
151
+ * it resolves them and looks for SKILL.md inside. So:
152
+ * - Directory skills (with SKILL.md): create symlink to the directory
153
+ * - Single-file skills (.md): create a wrapper directory with SKILL.md symlink inside
154
+ */
155
+ function syncSkillSymlinks(resolvedRoles, globalSkillsDir) {
156
+ try {
157
+ mkdirSync(globalSkillsDir, { recursive: true });
158
+ }
159
+ catch {
160
+ return;
161
+ }
162
+ // Clean up stale rolebox entries
163
+ try {
164
+ const existing = readdirSync(globalSkillsDir);
165
+ for (const entry of existing) {
166
+ if (!entry.startsWith(ROLEBOX_SKILL_PREFIX))
167
+ continue;
168
+ const entryPath = path.join(globalSkillsDir, entry);
169
+ try {
170
+ const stat = lstatSync(entryPath);
171
+ if (stat.isSymbolicLink()) {
172
+ unlinkSync(entryPath);
173
+ }
174
+ else if (stat.isDirectory()) {
175
+ // Wrapper directory: remove SKILL.md symlink inside, then rmdir
176
+ const inner = path.join(entryPath, "SKILL.md");
177
+ try {
178
+ unlinkSync(inner);
179
+ }
180
+ catch { }
181
+ try {
182
+ rmdirSync(entryPath);
183
+ }
184
+ catch { }
185
+ }
186
+ }
187
+ catch {
188
+ continue;
189
+ }
190
+ }
191
+ }
192
+ catch { }
193
+ // Create entries for all resolved role-local skills
194
+ for (const role of resolvedRoles) {
195
+ for (const skill of role.skills) {
196
+ if (skill.scope !== "rolebox")
197
+ continue;
198
+ if (!existsSync(skill.filePath))
199
+ continue;
200
+ const entryName = `${ROLEBOX_SKILL_PREFIX}${skill.name}`;
201
+ const entryPath = path.join(globalSkillsDir, entryName);
202
+ const isDirectorySkill = path.basename(skill.filePath).toLowerCase() === "skill.md";
203
+ try {
204
+ if (isDirectorySkill) {
205
+ // Symlink to the directory containing SKILL.md
206
+ symlinkSync(path.dirname(skill.filePath), entryPath);
207
+ }
208
+ else {
209
+ // Create wrapper directory with SKILL.md symlink inside
210
+ mkdirSync(entryPath, { recursive: true });
211
+ symlinkSync(skill.filePath, path.join(entryPath, "SKILL.md"));
212
+ }
213
+ }
214
+ catch { }
215
+ }
216
+ }
217
+ }
218
+ /**
219
+ * OpenCode plugin for rolebox — define custom agent roles via YAML
220
+ * configuration files with custom prompts, models, skills, and permissions.
221
+ */
222
+ const RoleboxPlugin = async (ctx) => {
223
+ const roleboxDir = path.join(ctx.directory, "rolebox");
224
+ const globalSkillsDir = path.join(ctx.directory, "skills");
225
+ // Discover all roles from the rolebox directory.
226
+ // Returns an empty Map when the directory does not exist or is empty.
227
+ const roles = await discoverRoles(roleboxDir);
228
+ // Resolve skills and build final prompts for every discovered role.
229
+ const resolvedRoles = await resolveAllRoles(roles, roleboxDir, globalSkillsDir);
230
+ // Sync agent files to ~/.claude/agents/ for oh-my-openagent compatibility.
231
+ syncAgentFiles(resolvedRoles);
232
+ // Sync role skills as symlinks into ~/.config/opencode/skills/ so that
233
+ // oh-my-openagent's skill tool can discover and load them.
234
+ syncSkillSymlinks(resolvedRoles, globalSkillsDir);
235
+ return {
236
+ config: async (config) => {
237
+ for (const resolved of resolvedRoles) {
238
+ const agentConfig = buildAgentConfig(resolved);
239
+ config.agent ??= {};
240
+ config.agent[resolved.id] = agentConfig;
241
+ }
242
+ },
243
+ };
244
+ };
245
+ export default RoleboxPlugin;
246
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { RoleConfig, ResolvedSkill } from "./types";
2
+ /**
3
+ * Build the final system prompt for a role.
4
+ *
5
+ * If the skills array is non-empty, the role prompt is followed by an
6
+ * <available_skills> XML block that lists each resolved skill with its
7
+ * name, description, and scope.
8
+ *
9
+ * If the skills array is empty, the raw role prompt is returned as-is
10
+ * without any XML wrapping.
11
+ */
12
+ export declare function buildAgentPrompt(role: RoleConfig, skills: ResolvedSkill[]): string;
13
+ //# sourceMappingURL=prompt-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-builder.d.ts","sourceRoot":"","sources":["../src/prompt-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,aAAa,EAAE,GACtB,MAAM,CAsBR"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Build the final system prompt for a role.
3
+ *
4
+ * If the skills array is non-empty, the role prompt is followed by an
5
+ * <available_skills> XML block that lists each resolved skill with its
6
+ * name, description, and scope.
7
+ *
8
+ * If the skills array is empty, the raw role prompt is returned as-is
9
+ * without any XML wrapping.
10
+ */
11
+ export function buildAgentPrompt(role, skills) {
12
+ if (skills.length === 0) {
13
+ return role.prompt;
14
+ }
15
+ const skillBlocks = skills
16
+ .map((s) => ` <skill>
17
+ <name>${s.name}</name>
18
+ <description>${s.description}</description>
19
+ <scope>${s.scope}</scope>
20
+ </skill>`)
21
+ .join("\n");
22
+ return `${role.prompt}
23
+
24
+ <available_skills>
25
+ Skills provide specialized instructions. Use the skill tool to load when task matches.
26
+ ${skillBlocks}
27
+ </available_skills>`;
28
+ }
29
+ //# sourceMappingURL=prompt-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-builder.js","sourceRoot":"","sources":["../src/prompt-builder.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAgB,EAChB,MAAuB;IAEvB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,MAAM,WAAW,GAAG,MAAM;SACvB,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CACJ;YACI,CAAC,CAAC,IAAI;mBACC,CAAC,CAAC,WAAW;aACnB,CAAC,CAAC,KAAK;WACT,CACN;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO,GAAG,IAAI,CAAC,MAAM;;;;EAIrB,WAAW;oBACO,CAAC;AACrB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * YAML role loader for rolebox.
3
+ *
4
+ * Scans a rolebox directory for role definitions (role.yaml files)
5
+ * and returns a Map of parsed and validated RoleConfig objects keyed
6
+ * by their directory name (roleId).
7
+ *
8
+ * Handles: YAML parsing, prompt_file loading, env var resolution,
9
+ * graceful skip-on-error, and empty-directory / nonexistent-directory cases.
10
+ */
11
+ import type { RoleConfig } from "./types";
12
+ /**
13
+ * Discover roles by scanning `roleboxDir` for subdirectories containing
14
+ * `role.yaml` files. Only scans ONE level deep (roleboxDir/{role}/role.yaml).
15
+ *
16
+ * @param roleboxDir - Absolute path to the rolebox configuration directory.
17
+ * @returns Map of roleId → RoleConfig for valid roles. Empty Map if no roles found.
18
+ */
19
+ export declare function discoverRoles(roleboxDir: string): Promise<Map<string, RoleConfig>>;
20
+ //# sourceMappingURL=role-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-loader.d.ts","sourceRoot":"","sources":["../src/role-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAgClC"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * YAML role loader for rolebox.
3
+ *
4
+ * Scans a rolebox directory for role definitions (role.yaml files)
5
+ * and returns a Map of parsed and validated RoleConfig objects keyed
6
+ * by their directory name (roleId).
7
+ *
8
+ * Handles: YAML parsing, prompt_file loading, env var resolution,
9
+ * graceful skip-on-error, and empty-directory / nonexistent-directory cases.
10
+ */
11
+ import { readFile } from "node:fs/promises";
12
+ import { basename, dirname, resolve as pathResolve } from "node:path";
13
+ import fglob from "fast-glob";
14
+ import yaml from "js-yaml";
15
+ import { resolveEnvVarsDeep, resolveEnvVars } from "./env-resolver";
16
+ /**
17
+ * Discover roles by scanning `roleboxDir` for subdirectories containing
18
+ * `role.yaml` files. Only scans ONE level deep (roleboxDir/{role}/role.yaml).
19
+ *
20
+ * @param roleboxDir - Absolute path to the rolebox configuration directory.
21
+ * @returns Map of roleId → RoleConfig for valid roles. Empty Map if no roles found.
22
+ */
23
+ export async function discoverRoles(roleboxDir) {
24
+ const roles = new Map();
25
+ let matches;
26
+ try {
27
+ matches = await fglob("**/role.yaml", {
28
+ cwd: roleboxDir,
29
+ absolute: true,
30
+ deep: 2,
31
+ });
32
+ }
33
+ catch {
34
+ // roleboxDir doesn't exist or glob fails — return empty Map silently
35
+ return roles;
36
+ }
37
+ for (const yamlPath of matches) {
38
+ const roleId = basename(dirname(yamlPath));
39
+ try {
40
+ const config = await loadOneRole(yamlPath, roleId);
41
+ if (config !== null) {
42
+ roles.set(roleId, config);
43
+ }
44
+ }
45
+ catch {
46
+ // Unexpected errors during loadOneRole; skip and continue
47
+ console.warn(`[role-loader] Skipping "${roleId}": unexpected error during load`);
48
+ }
49
+ }
50
+ return roles;
51
+ }
52
+ /**
53
+ * Parse and validate a single role.yaml file.
54
+ *
55
+ * @returns RoleConfig on success, null if the role should be skipped
56
+ * (validation failure already logged via console.warn).
57
+ */
58
+ async function loadOneRole(yamlPath, roleId) {
59
+ let raw;
60
+ try {
61
+ const content = await readFile(yamlPath, "utf-8");
62
+ raw = yaml.load(content);
63
+ }
64
+ catch (err) {
65
+ console.warn(`[role-loader] Skipping "${roleId}": invalid YAML — ${err instanceof Error ? err.message : String(err)}`);
66
+ return null;
67
+ }
68
+ if (raw === null || raw === undefined || typeof raw !== "object") {
69
+ console.warn(`[role-loader] Skipping "${roleId}": YAML does not contain an object`);
70
+ return null;
71
+ }
72
+ const obj = raw;
73
+ if (!obj.name || typeof obj.name !== "string" || obj.name.trim() === "") {
74
+ console.warn(`[role-loader] Skipping "${roleId}": missing or invalid "name" field`);
75
+ return null;
76
+ }
77
+ let prompt;
78
+ if (typeof obj.prompt_file === "string" && obj.prompt_file.trim() !== "") {
79
+ const promptFilePath = pathResolve(dirname(yamlPath), obj.prompt_file);
80
+ try {
81
+ prompt = await readFile(promptFilePath, "utf-8");
82
+ }
83
+ catch {
84
+ console.warn(`[role-loader] Skipping "${roleId}": prompt_file "${obj.prompt_file}" not found`);
85
+ return null;
86
+ }
87
+ }
88
+ else if (typeof obj.prompt === "string" && obj.prompt.trim() !== "") {
89
+ prompt = obj.prompt;
90
+ }
91
+ else {
92
+ console.warn(`[role-loader] Skipping "${roleId}": must provide "prompt" or "prompt_file"`);
93
+ return null;
94
+ }
95
+ prompt = resolveEnvVars(prompt);
96
+ const resolved = resolveEnvVarsDeep(obj);
97
+ const config = {
98
+ name: resolved.name,
99
+ description: resolved.description ?? "",
100
+ prompt,
101
+ ...(typeof resolved.prompt_file === "string"
102
+ ? { prompt_file: resolved.prompt_file }
103
+ : {}),
104
+ ...(typeof resolved.model === "string"
105
+ ? { model: resolved.model }
106
+ : {}),
107
+ ...(typeof resolved.mode === "string" &&
108
+ ["primary", "subagent", "all"].includes(resolved.mode)
109
+ ? { mode: resolved.mode }
110
+ : {}),
111
+ ...(typeof resolved.color === "string"
112
+ ? { color: resolved.color }
113
+ : {}),
114
+ ...(typeof resolved.variant === "string"
115
+ ? { variant: resolved.variant }
116
+ : {}),
117
+ ...(Array.isArray(resolved.skills)
118
+ ? { skills: resolved.skills }
119
+ : {}),
120
+ ...(Array.isArray(resolved.opencode_skills)
121
+ ? { opencode_skills: resolved.opencode_skills }
122
+ : {}),
123
+ ...(resolved.permission != null && typeof resolved.permission === "object"
124
+ ? { permission: resolved.permission }
125
+ : {}),
126
+ ...(resolved.tools != null && typeof resolved.tools === "object"
127
+ ? { tools: resolved.tools }
128
+ : {}),
129
+ ...(typeof resolved.temperature === "number"
130
+ ? { temperature: resolved.temperature }
131
+ : {}),
132
+ ...(typeof resolved.top_p === "number"
133
+ ? { top_p: resolved.top_p }
134
+ : {}),
135
+ };
136
+ return config;
137
+ }
138
+ //# sourceMappingURL=role-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-loader.js","sourceRoot":"","sources":["../src/role-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { ResolvedSkill, SkillMetadata } from "./types";
2
+ /**
3
+ * Resolve skill names to their file locations using fast-glob.
4
+ *
5
+ * For each skill name the four candidate locations are checked in priority
6
+ * order. The first existing file wins. Skills that cannot be found in any
7
+ * location are silently skipped (no error is thrown).
8
+ */
9
+ export declare function resolveSkills(skillNames: string[], roleDir: string, globalSkillsDir: string): Promise<ResolvedSkill[]>;
10
+ /**
11
+ * Read the full SKILL.md content from the resolved skill's file path.
12
+ *
13
+ * @throws If the file cannot be read (e.g. it was deleted since resolution).
14
+ */
15
+ export declare function loadSkillContent(skill: ResolvedSkill): Promise<string>;
16
+ /**
17
+ * Parse YAML frontmatter from SKILL.md content.
18
+ *
19
+ * Frontmatter is delimited by `---` lines at the very start of the file.
20
+ *
21
+ * @returns `metadata` — parsed frontmatter keys (empty object if none/invalid)
22
+ * `body` — everything after the closing `---` (or the entire
23
+ * content when no frontmatter is present).
24
+ */
25
+ export declare function parseFrontmatter(content: string): {
26
+ metadata: SkillMetadata;
27
+ body: string;
28
+ };
29
+ //# sourceMappingURL=skill-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-resolver.d.ts","sourceRoot":"","sources":["../src/skill-resolver.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAyB5D;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAAE,EACpB,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,aAAa,EAAE,CAAC,CAyB1B;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAW5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IACjD,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd,CA2BA"}
@@ -0,0 +1,94 @@
1
+ import fg from "fast-glob";
2
+ import yaml from "js-yaml";
3
+ // Resolution priority:
4
+ // 1. {roleDir}/skills/{name}/SKILL.md (role-local directory)
5
+ // 2. {roleDir}/skills/{name}.md (role-local single-file)
6
+ // 3. {globalSkillsDir}/{name}/SKILL.md (global directory)
7
+ // 4. {globalSkillsDir}/{name}.md (global single-file)
8
+ function buildCandidates(name, roleDir, globalSkillsDir) {
9
+ return [
10
+ { scope: "rolebox", pattern: `${roleDir}/skills/${name}/SKILL.md` },
11
+ { scope: "rolebox", pattern: `${roleDir}/skills/${name}.md` },
12
+ { scope: "opencode", pattern: `${globalSkillsDir}/${name}/SKILL.md` },
13
+ { scope: "opencode", pattern: `${globalSkillsDir}/${name}.md` },
14
+ ];
15
+ }
16
+ /**
17
+ * Resolve skill names to their file locations using fast-glob.
18
+ *
19
+ * For each skill name the four candidate locations are checked in priority
20
+ * order. The first existing file wins. Skills that cannot be found in any
21
+ * location are silently skipped (no error is thrown).
22
+ */
23
+ export async function resolveSkills(skillNames, roleDir, globalSkillsDir) {
24
+ const resolved = [];
25
+ for (const name of skillNames) {
26
+ const candidates = buildCandidates(name, roleDir, globalSkillsDir);
27
+ for (const candidate of candidates) {
28
+ const matches = await fg(candidate.pattern, { onlyFiles: true });
29
+ if (matches.length > 0) {
30
+ const filePath = matches[0];
31
+ let description = "";
32
+ try {
33
+ const content = await Bun.file(filePath).text();
34
+ const { metadata } = parseFrontmatter(content);
35
+ description = metadata.description ?? "";
36
+ }
37
+ catch {
38
+ // If the file can't be read, use empty description
39
+ }
40
+ resolved.push({ name, description, scope: candidate.scope, filePath });
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ return resolved;
46
+ }
47
+ /**
48
+ * Read the full SKILL.md content from the resolved skill's file path.
49
+ *
50
+ * @throws If the file cannot be read (e.g. it was deleted since resolution).
51
+ */
52
+ export async function loadSkillContent(skill) {
53
+ const file = Bun.file(skill.filePath);
54
+ if (!(await file.exists())) {
55
+ throw new Error(`Skill file not found at "${skill.filePath}" for skill "${skill.name}". ` +
56
+ `The file may have been deleted after resolution.`);
57
+ }
58
+ return file.text();
59
+ }
60
+ /**
61
+ * Parse YAML frontmatter from SKILL.md content.
62
+ *
63
+ * Frontmatter is delimited by `---` lines at the very start of the file.
64
+ *
65
+ * @returns `metadata` — parsed frontmatter keys (empty object if none/invalid)
66
+ * `body` — everything after the closing `---` (or the entire
67
+ * content when no frontmatter is present).
68
+ */
69
+ export function parseFrontmatter(content) {
70
+ const trimmed = content.trimStart();
71
+ if (!trimmed.startsWith("---")) {
72
+ return { metadata: {}, body: content };
73
+ }
74
+ const endIdx = trimmed.indexOf("\n---", 3);
75
+ if (endIdx === -1) {
76
+ return { metadata: {}, body: content };
77
+ }
78
+ const yamlStr = trimmed.slice(4, endIdx);
79
+ let body = trimmed.slice(endIdx + 4);
80
+ if (body.startsWith("\n")) {
81
+ body = body.slice(1);
82
+ }
83
+ try {
84
+ const parsed = yaml.load(yamlStr);
85
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
86
+ return { metadata: parsed, body };
87
+ }
88
+ return { metadata: {}, body };
89
+ }
90
+ catch {
91
+ return { metadata: {}, body: content };
92
+ }
93
+ }
94
+ //# sourceMappingURL=skill-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-resolver.js","sourceRoot":"","sources":["../src/skill-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Permission configuration, mirroring opencode's PermissionConfig structure.
3
+ * Controls which tools a role is allowed or denied from using.
4
+ */
5
+ export interface PermissionConfig {
6
+ /** Tool names the role is explicitly allowed to use */
7
+ allow?: string[];
8
+ /** Tool names the role is explicitly denied from using */
9
+ deny?: string[];
10
+ }
11
+ /**
12
+ * Raw role configuration as parsed from a role's YAML file (role.yaml).
13
+ * Contains user-facing settings before any environment variable resolution
14
+ * or file-based prompt loading has occurred.
15
+ */
16
+ export interface RoleConfig {
17
+ /** Human-readable name for the role */
18
+ name: string;
19
+ /** Brief description of the role's purpose */
20
+ description: string;
21
+ /** LLM model identifier (e.g., "gpt-4", "claude-3-sonnet") */
22
+ model?: string;
23
+ /** Role mode: "primary" (default), "subagent", or "all" */
24
+ mode?: "primary" | "subagent" | "all";
25
+ /** Display color for the role in the UI */
26
+ color?: string;
27
+ /** Model variant / configuration flavor */
28
+ variant?: string;
29
+ /** System prompt text (mutually exclusive with prompt_file) */
30
+ prompt: string;
31
+ /** Path to a file containing the system prompt (mutually exclusive with prompt) */
32
+ prompt_file?: string;
33
+ /** Names of rolebox-local skills to load */
34
+ skills?: string[];
35
+ /** Names of opencode-global skills to load */
36
+ opencode_skills?: string[];
37
+ /** Permission controls for tool access */
38
+ permission?: PermissionConfig;
39
+ /** Map of tool names to enabled/disabled state */
40
+ tools?: Record<string, boolean>;
41
+ /** Sampling temperature for the LLM (0.0 - 2.0) */
42
+ temperature?: number;
43
+ /** Top-p nucleus sampling parameter (0.0 - 1.0) */
44
+ top_p?: number;
45
+ }
46
+ /**
47
+ * A resolved skill reference after locating the corresponding SKILL.md file
48
+ * in either the role's local skills directory or the opencode global skills directory.
49
+ */
50
+ export interface ResolvedSkill {
51
+ /** Skill name (matches the directory or frontmatter name) */
52
+ name: string;
53
+ /** Human-readable description from SKILL.md frontmatter */
54
+ description: string;
55
+ /** Scope indicating where the skill was found */
56
+ scope: "rolebox" | "opencode";
57
+ /** Absolute filesystem path to the SKILL.md file */
58
+ filePath: string;
59
+ }
60
+ /**
61
+ * A fully resolved role with all configuration materialized.
62
+ * Environment variables have been substituted, prompt_file content has been
63
+ * loaded into the prompt field, and all skill references have been resolved
64
+ * to their actual file locations.
65
+ */
66
+ export interface ResolvedRole {
67
+ /** Unique identifier for the role (typically the directory name) */
68
+ id: string;
69
+ /** Original role configuration (raw YAML data) */
70
+ config: RoleConfig;
71
+ /** Final system prompt string (after prompt_file resolution) */
72
+ prompt: string;
73
+ /** Resolved skill references */
74
+ skills: ResolvedSkill[];
75
+ }
76
+ /**
77
+ * YAML frontmatter metadata parsed from SKILL.md files.
78
+ * Fields follow the standard opencode skill frontmatter schema.
79
+ */
80
+ export interface SkillMetadata {
81
+ /** Skill name */
82
+ name?: string;
83
+ /** Human-readable description */
84
+ description?: string;
85
+ /** Recommended model for this skill */
86
+ model?: string;
87
+ /** Software license identifier */
88
+ license?: string;
89
+ /** Tool compatibility declaration (e.g., "claude-code opencode") */
90
+ compatibility?: string;
91
+ /** Allowed tools, either as a comma-separated string or an array */
92
+ "allowed-tools"?: string | string[];
93
+ }
94
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,KAAK,CAAC;IACtC,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,8CAA8C;IAC9C,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,0CAA0C;IAC1C,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,KAAK,EAAE,SAAS,GAAG,UAAU,CAAC;IAC9B,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,MAAM,EAAE,UAAU,CAAC;IACnB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,iBAAiB;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACrC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ name: Code Reviewer
2
+ description: Expert code reviewer with deep understanding of CR and best practices
3
+ model: gpt-4
4
+ mode: subagent
5
+ color: '#4CAF50'
6
+ variant: thorough
7
+ temperature: 0.2
8
+ top_p: 0.95
9
+ prompt: |
10
+ You are an expert code reviewer. Your job is to:
11
+
12
+ 1. Review code for correctness, performance, and readability
13
+ 2. Identify potential bugs and security issues
14
+ 3. Suggest improvements with concrete examples
15
+ 4. Be constructive and respectful in your feedback
16
+
17
+ Always provide specific, actionable feedback.
18
+ skills:
19
+ - review-checklist
20
+ permission:
21
+ allow:
22
+ - Read
23
+ - Grep
24
+ - Glob
25
+ - Edit
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: review-checklist
3
+ description: Standard code review checklist covering correctness, security, performance, and style
4
+ license: MIT
5
+ compatibility: opencode
6
+ allowed-tools:
7
+ - Read
8
+ - Grep
9
+ - Glob
10
+ ---
11
+
12
+ # Code Review Checklist
13
+
14
+ ## Correctness
15
+ - Does the code handle edge cases?
16
+ - Are there any logic errors?
17
+ - Are error paths properly handled?
18
+
19
+ ## Security
20
+ - Are inputs validated and sanitized?
21
+ - Are there any injection vulnerabilities?
22
+ - Are secrets hardcoded?
23
+
24
+ ## Performance
25
+ - Are there N+1 query patterns?
26
+ - Is memory usage reasonable?
27
+ - Are there obvious optimization opportunities?
28
+
29
+ ## Style
30
+ - Does the code follow project conventions?
31
+ - Are names descriptive?
32
+ - Is the code well-structured?
@@ -0,0 +1,7 @@
1
+ name: Tech Writer
2
+ description: Technical documentation specialist
3
+ prompt: |
4
+ You are a technical writer specializing in clear, concise documentation.
5
+ Write documentation that is accurate, well-structured, and easy to understand.
6
+ opencode_skills:
7
+ - humanizer
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "rolebox",
3
+ "version": "0.1.0",
4
+ "description": "OpenCode plugin — define custom AI agent roles via YAML with per-role prompts, models, skills, and permissions",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "examples"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "prepublishOnly": "tsc",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "bun test"
24
+ },
25
+ "keywords": [
26
+ "opencode",
27
+ "opencode-plugin",
28
+ "ai-agent",
29
+ "yaml",
30
+ "role",
31
+ "prompt"
32
+ ],
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/EricMoin/rolebox"
37
+ },
38
+ "dependencies": {
39
+ "fast-glob": "^3.3.0",
40
+ "js-yaml": "^4.1.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@opencode-ai/plugin": "^1.3.0"
44
+ },
45
+ "devDependencies": {
46
+ "@opencode-ai/plugin": "^1.3.0",
47
+ "@types/js-yaml": "^4.0.0",
48
+ "bun-types": "latest",
49
+ "typescript": "^5.7.0"
50
+ }
51
+ }