opcrew 0.1.5 → 0.1.7

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/dist/cli.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import process from "process";
6
6
 
7
7
  // src/cli/install.ts
8
- import { mkdir, writeFile, cp, rm } from "fs/promises";
8
+ import { mkdir, writeFile, cp, rm, stat } from "fs/promises";
9
9
  import path2 from "path";
10
10
  // src/core/tools/translations.ts
11
11
  var CLAUDE_TOOLS = {
@@ -94,7 +94,7 @@ description: ${this.config.description}
94
94
  tools: [${translatedTools.join(", ")}]
95
95
  ---`;
96
96
  return `${frontmatter}
97
- ${this.buildMarkdownBody()}`;
97
+ ${this.buildMarkdownBody("claude")}`;
98
98
  }
99
99
  compileToOpenCodeMarkdown() {
100
100
  const permissionByRole = {
@@ -114,7 +114,7 @@ permission:
114
114
  webfetch: ${permission.webfetch}
115
115
  ---`;
116
116
  return `${frontmatter}
117
- ${this.buildMarkdownBody()}`;
117
+ ${this.buildMarkdownBody("opencode")}`;
118
118
  }
119
119
  compileToCodexMarkdown() {
120
120
  const translatedTools = translateTools(this.getAllowedTools(), "codex");
@@ -124,13 +124,23 @@ role: ${this.config.role}
124
124
  tools: [${translatedTools.join(", ")}]
125
125
  ---`;
126
126
  return `${frontmatter}
127
- ${this.buildMarkdownBody()}`;
127
+ ${this.buildMarkdownBody("codex")}`;
128
128
  }
129
- buildMarkdownBody() {
129
+ buildToolBoundary(platform) {
130
+ const translatedTools = translateTools(this.getAllowedTools(), platform);
131
+ if (translatedTools.length === 0) {
132
+ return "TOOL BOUNDARY: No tools are available for this agent on this platform.";
133
+ }
134
+ const toolList = translatedTools.join(", ");
135
+ return `TOOL BOUNDARY: You are ONLY permitted to use these tools: ${toolList}. Using any other tool is a violation.`;
136
+ }
137
+ buildMarkdownBody(platform) {
138
+ const toolBoundary = this.buildToolBoundary(platform);
130
139
  const instructionsList = this.config.instructions.map((instruction) => `- ${instruction}`).join(`
131
140
  `);
132
141
  return `
133
142
  # ${this.config.name}
143
+ - ${toolBoundary}
134
144
  ${instructionsList}
135
145
 
136
146
  ## Shared Context
@@ -146,7 +156,6 @@ var Captain = new OpCrewAgent({
146
156
  role: "Orchestrator",
147
157
  mode: "primary",
148
158
  instructions: [
149
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: read, logbook, delegate, skill. Using any other tool is a violation.",
150
159
  "You are the orchestrator - you NEVER execute work directly. Your role is to coordinate, delegate, and verify.",
151
160
  "Workstream Fan-out: divide missions into independent Workstreams (e.g., Logic, Testing, Documentation) to enable parallel specialists.",
152
161
  "Assign roles in order: Navigator plans, Boatswain executes, Quartermaster reviews; resolve conflicts quickly.",
@@ -171,7 +180,6 @@ var Navigator = new OpCrewAgent({
171
180
  role: "Planner",
172
181
  mode: "subagent",
173
182
  instructions: [
174
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: read, websearch, logbook, todo, skill. Using any other tool is a violation.",
175
183
  "Chart the implementation path based on the Captain's intent and constraints.",
176
184
  "Decompose work into atomic, ordered tasks with clear inputs, outputs, and ownership.",
177
185
  "Workstream Decomposition: break the plan into atomic, parallel tracks. Define File Ownership (e.g., Boatswain-A owns src/, Boatswain-B owns tests/).",
@@ -200,7 +208,6 @@ var Boatswain = new OpCrewAgent({
200
208
  role: "Executor",
201
209
  mode: "subagent",
202
210
  instructions: [
203
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: read, edit, write, test, todo, glob, grep, skill. Using any other tool is a violation.",
204
211
  "Scope Guarding: ONLY touch files assigned to your Workstream ID. If you must edit a file owned by another agent, escalate to Captain for a plan revision.",
205
212
  "Follow existing patterns, naming, and formatting; avoid speculative changes.",
206
213
  "Context Anchoring: before editing, read the surrounding code to match indentation, casing, and comment styles. Maintain the project's local laws.",
@@ -223,7 +230,6 @@ var Quartermaster = new OpCrewAgent({
223
230
  role: "Reviewer",
224
231
  mode: "subagent",
225
232
  instructions: [
226
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: read, grep, execute, write, skill. Using any other tool is a violation.",
227
233
  "Inspect each diff for regression risks, scope drift, and missing context.",
228
234
  "Integration Audit: once all parallel Boatswains report Complete, verify the combined diff for logic conflicts or signature mismatches.",
229
235
  "Documentation Gate: REJECT approval if code changed but docs/project-map.md or docs/glossary.md were not updated to reflect the new state.",
@@ -246,7 +252,6 @@ var Scout = new OpCrewAgent({
246
252
  role: "Researcher",
247
253
  mode: "subagent",
248
254
  instructions: [
249
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: websearch, webfetch, read, skill. Using any other tool is a violation.",
250
255
  "You are the crew's eyes to the outside world - research and gather information when summoned by Captain or Navigator.",
251
256
  "Focus exclusively on external research: web searches, documentation lookup, API references, and best practices.",
252
257
  "Never modify project files - your job is to gather intelligence, not execute changes.",
@@ -333,10 +338,70 @@ var CLAUDE_SKILLS_DIR = path2.join(".claude", "skills");
333
338
  var OPENCODE_AGENTS_DIR = path2.join(".opencode", "agents");
334
339
  var OPENCODE_SKILLS_DIR = path2.join(".opencode", "skills");
335
340
  var CODEX_FILE = path2.join(".codex", "instructions.md");
341
+ var OPCREW_DIR = ".opcrew";
342
+ var KNOWLEDGE_DIR = path2.join("docs", "knowledge");
343
+ var TEMPLATE_DIR = path2.join(import.meta.dir, "..", "templates");
336
344
  async function ensureDir(targetDir) {
337
345
  await mkdir(targetDir, { recursive: true });
338
346
  }
347
+ async function fileExists(filePath) {
348
+ try {
349
+ await stat(filePath);
350
+ return true;
351
+ } catch {
352
+ return false;
353
+ }
354
+ }
355
+ async function bootstrapLogbook() {
356
+ const logbookPath = path2.join(OPCREW_DIR, "logbook.json");
357
+ const schemaPath = path2.join(OPCREW_DIR, "logbook.schema.json");
358
+ await ensureDir(OPCREW_DIR);
359
+ if (await fileExists(logbookPath)) {
360
+ console.log(`Skipped ${logbookPath} (already exists)`);
361
+ } else {
362
+ const templatePath = path2.join(TEMPLATE_DIR, "logbook.template.json");
363
+ await cp(templatePath, logbookPath);
364
+ console.log(`Created ${logbookPath}`);
365
+ }
366
+ if (await fileExists(schemaPath)) {
367
+ console.log(`Skipped ${schemaPath} (already exists)`);
368
+ } else {
369
+ const schemaTemplatePath = path2.join(TEMPLATE_DIR, "logbook.schema.json");
370
+ await cp(schemaTemplatePath, schemaPath);
371
+ console.log(`Created ${schemaPath}`);
372
+ }
373
+ }
374
+ async function bootstrapKnowledgeBase() {
375
+ const decisionsDir = path2.join(KNOWLEDGE_DIR, "decisions");
376
+ const readmePath = path2.join(KNOWLEDGE_DIR, "README.md");
377
+ const glossaryPath = path2.join(KNOWLEDGE_DIR, "glossary.md");
378
+ const projectMapPath = path2.join(KNOWLEDGE_DIR, "project-map.md");
379
+ await ensureDir(decisionsDir);
380
+ if (await fileExists(readmePath)) {
381
+ console.log(`Skipped ${readmePath} (already exists)`);
382
+ } else {
383
+ const templatePath = path2.join(TEMPLATE_DIR, "knowledge", "README.md");
384
+ await cp(templatePath, readmePath);
385
+ console.log(`Created ${readmePath}`);
386
+ }
387
+ if (await fileExists(glossaryPath)) {
388
+ console.log(`Skipped ${glossaryPath} (already exists)`);
389
+ } else {
390
+ const templatePath = path2.join(TEMPLATE_DIR, "knowledge", "glossary.md");
391
+ await cp(templatePath, glossaryPath);
392
+ console.log(`Created ${glossaryPath}`);
393
+ }
394
+ if (await fileExists(projectMapPath)) {
395
+ console.log(`Skipped ${projectMapPath} (already exists)`);
396
+ } else {
397
+ const templatePath = path2.join(TEMPLATE_DIR, "knowledge", "project-map.md");
398
+ await cp(templatePath, projectMapPath);
399
+ console.log(`Created ${projectMapPath}`);
400
+ }
401
+ }
339
402
  async function installToTool(tool) {
403
+ await bootstrapLogbook();
404
+ await bootstrapKnowledgeBase();
340
405
  if (tool === "codex") {
341
406
  await installForCodex();
342
407
  return;
@@ -100,7 +100,7 @@ description: ${this.config.description}
100
100
  tools: [${translatedTools.join(", ")}]
101
101
  ---`;
102
102
  return `${frontmatter}
103
- ${this.buildMarkdownBody()}`;
103
+ ${this.buildMarkdownBody("claude")}`;
104
104
  }
105
105
  compileToOpenCodeMarkdown() {
106
106
  const permissionByRole = {
@@ -120,7 +120,7 @@ permission:
120
120
  webfetch: ${permission.webfetch}
121
121
  ---`;
122
122
  return `${frontmatter}
123
- ${this.buildMarkdownBody()}`;
123
+ ${this.buildMarkdownBody("opencode")}`;
124
124
  }
125
125
  compileToCodexMarkdown() {
126
126
  const translatedTools = translateTools(this.getAllowedTools(), "codex");
@@ -130,13 +130,23 @@ role: ${this.config.role}
130
130
  tools: [${translatedTools.join(", ")}]
131
131
  ---`;
132
132
  return `${frontmatter}
133
- ${this.buildMarkdownBody()}`;
133
+ ${this.buildMarkdownBody("codex")}`;
134
134
  }
135
- buildMarkdownBody() {
135
+ buildToolBoundary(platform) {
136
+ const translatedTools = translateTools(this.getAllowedTools(), platform);
137
+ if (translatedTools.length === 0) {
138
+ return "TOOL BOUNDARY: No tools are available for this agent on this platform.";
139
+ }
140
+ const toolList = translatedTools.join(", ");
141
+ return `TOOL BOUNDARY: You are ONLY permitted to use these tools: ${toolList}. Using any other tool is a violation.`;
142
+ }
143
+ buildMarkdownBody(platform) {
144
+ const toolBoundary = this.buildToolBoundary(platform);
136
145
  const instructionsList = this.config.instructions.map((instruction) => `- ${instruction}`).join(`
137
146
  `);
138
147
  return `
139
148
  # ${this.config.name}
149
+ - ${toolBoundary}
140
150
  ${instructionsList}
141
151
 
142
152
  ## Shared Context
@@ -152,7 +162,6 @@ var Captain = new OpCrewAgent({
152
162
  role: "Orchestrator",
153
163
  mode: "primary",
154
164
  instructions: [
155
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: read, logbook, delegate, skill. Using any other tool is a violation.",
156
165
  "You are the orchestrator - you NEVER execute work directly. Your role is to coordinate, delegate, and verify.",
157
166
  "Workstream Fan-out: divide missions into independent Workstreams (e.g., Logic, Testing, Documentation) to enable parallel specialists.",
158
167
  "Assign roles in order: Navigator plans, Boatswain executes, Quartermaster reviews; resolve conflicts quickly.",
@@ -177,7 +186,6 @@ var Navigator = new OpCrewAgent({
177
186
  role: "Planner",
178
187
  mode: "subagent",
179
188
  instructions: [
180
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: read, websearch, logbook, todo, skill. Using any other tool is a violation.",
181
189
  "Chart the implementation path based on the Captain's intent and constraints.",
182
190
  "Decompose work into atomic, ordered tasks with clear inputs, outputs, and ownership.",
183
191
  "Workstream Decomposition: break the plan into atomic, parallel tracks. Define File Ownership (e.g., Boatswain-A owns src/, Boatswain-B owns tests/).",
@@ -206,7 +214,6 @@ var Boatswain = new OpCrewAgent({
206
214
  role: "Executor",
207
215
  mode: "subagent",
208
216
  instructions: [
209
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: read, edit, write, test, todo, glob, grep, skill. Using any other tool is a violation.",
210
217
  "Scope Guarding: ONLY touch files assigned to your Workstream ID. If you must edit a file owned by another agent, escalate to Captain for a plan revision.",
211
218
  "Follow existing patterns, naming, and formatting; avoid speculative changes.",
212
219
  "Context Anchoring: before editing, read the surrounding code to match indentation, casing, and comment styles. Maintain the project's local laws.",
@@ -229,7 +236,6 @@ var Quartermaster = new OpCrewAgent({
229
236
  role: "Reviewer",
230
237
  mode: "subagent",
231
238
  instructions: [
232
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: read, grep, execute, write, skill. Using any other tool is a violation.",
233
239
  "Inspect each diff for regression risks, scope drift, and missing context.",
234
240
  "Integration Audit: once all parallel Boatswains report Complete, verify the combined diff for logic conflicts or signature mismatches.",
235
241
  "Documentation Gate: REJECT approval if code changed but docs/project-map.md or docs/glossary.md were not updated to reflect the new state.",
@@ -252,7 +258,6 @@ var Scout = new OpCrewAgent({
252
258
  role: "Researcher",
253
259
  mode: "subagent",
254
260
  instructions: [
255
- "TOOL BOUNDARY: You are ONLY permitted to use these tools: websearch, webfetch, read, skill. Using any other tool is a violation.",
256
261
  "You are the crew's eyes to the outside world - research and gather information when summoned by Captain or Navigator.",
257
262
  "Focus exclusively on external research: web searches, documentation lookup, API references, and best practices.",
258
263
  "Never modify project files - your job is to gather intelligence, not execute changes.",
@@ -12720,26 +12725,109 @@ The logbook tracks:
12720
12725
  }
12721
12726
  });
12722
12727
 
12728
+ // src/skills.ts
12729
+ import { readdir, readFile } from "fs/promises";
12730
+ import path from "path";
12731
+ import { fileURLToPath } from "url";
12732
+ var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
12733
+ async function loadSkills() {
12734
+ const skillsDir = path.join(__dirname2, "skills");
12735
+ try {
12736
+ const entries = await readdir(skillsDir, { withFileTypes: true });
12737
+ const skills = [];
12738
+ for (const entry of entries) {
12739
+ if (!entry.isDirectory())
12740
+ continue;
12741
+ const skillPath = path.join(skillsDir, entry.name);
12742
+ const skillMdPath = path.join(skillPath, "SKILL.md");
12743
+ try {
12744
+ const content = await readFile(skillMdPath, "utf-8");
12745
+ const { name, description } = parseSkillFrontmatter(content);
12746
+ skills.push({
12747
+ name: name || entry.name,
12748
+ description: description || "",
12749
+ sourcePath: skillPath
12750
+ });
12751
+ } catch {
12752
+ console.warn(`Warning: No SKILL.md found in ${skillPath}`);
12753
+ }
12754
+ }
12755
+ return skills;
12756
+ } catch {
12757
+ return [];
12758
+ }
12759
+ }
12760
+ function parseSkillFrontmatter(content) {
12761
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
12762
+ if (!frontmatterMatch) {
12763
+ return {};
12764
+ }
12765
+ const frontmatter = frontmatterMatch[1];
12766
+ const result = {};
12767
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
12768
+ if (nameMatch) {
12769
+ result.name = nameMatch[1].trim();
12770
+ }
12771
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
12772
+ if (descMatch) {
12773
+ result.description = descMatch[1].trim();
12774
+ }
12775
+ return result;
12776
+ }
12777
+ async function getSkillContent(skillName) {
12778
+ const skillMdPath = path.join(__dirname2, "skills", skillName, "SKILL.md");
12779
+ return readFile(skillMdPath, "utf-8");
12780
+ }
12781
+
12782
+ // src/tools/skill.ts
12783
+ var skillTool = tool({
12784
+ description: `Load and manage agent skills dynamically.
12785
+
12786
+ Actions:
12787
+ - list: List all available skills with names and descriptions
12788
+ - load: Load a specific skill's full content (SKILL.md)
12789
+
12790
+ Skills provide specialized instructions and workflows for specific tasks.
12791
+ Use this tool when you recognize that a task matches one of the available skills.`,
12792
+ args: {
12793
+ action: tool.schema.enum(["list", "load"]).describe("Action to perform"),
12794
+ name: tool.schema.string().optional().describe("Skill name (required for 'load' action)")
12795
+ },
12796
+ async execute(args) {
12797
+ switch (args.action) {
12798
+ case "list": {
12799
+ const skills = await loadSkills();
12800
+ if (skills.length === 0) {
12801
+ return "No skills available.";
12802
+ }
12803
+ const formattedList = skills.map((skill) => `- **${skill.name}**: ${skill.description}`).join(`
12804
+ `);
12805
+ return `Available skills:
12806
+
12807
+ ${formattedList}`;
12808
+ }
12809
+ case "load": {
12810
+ if (!args.name) {
12811
+ return "Error: name is required for 'load' action";
12812
+ }
12813
+ try {
12814
+ const content = await getSkillContent(args.name);
12815
+ return content;
12816
+ } catch (error45) {
12817
+ const message = error45 instanceof Error ? error45.message : String(error45);
12818
+ return `Error: Failed to load skill '${args.name}': ${message}`;
12819
+ }
12820
+ }
12821
+ default:
12822
+ return `Error: Unknown action: ${args.action}`;
12823
+ }
12824
+ }
12825
+ });
12826
+
12723
12827
  // src/opencode-plugin.ts
12724
- var DEFAULT_CANONICAL_TOOLS = [
12725
- "read",
12726
- "write",
12727
- "edit",
12728
- "glob",
12729
- "grep",
12730
- "execute",
12731
- "websearch",
12732
- "webfetch",
12733
- "delegate",
12734
- "todo",
12735
- "test",
12736
- "logbook",
12737
- "skill"
12738
- ];
12739
- var ALL_AVAILABLE_TOOLS = DEFAULT_CANONICAL_TOOLS;
12740
12828
  var permissionByRole = {
12741
12829
  Orchestrator: { edit: "deny", bash: "ask", webfetch: "deny" },
12742
- Planner: { edit: "deny", bash: "ask", webfetch: "allow" },
12830
+ Planner: { edit: "deny", bash: "allow", webfetch: "allow" },
12743
12831
  Executor: { edit: "allow", bash: "allow", webfetch: "ask" },
12744
12832
  Reviewer: { edit: "deny", bash: "allow", webfetch: "deny" },
12745
12833
  Researcher: { edit: "deny", bash: "deny", webfetch: "allow" }
@@ -12747,102 +12835,57 @@ var permissionByRole = {
12747
12835
  function toAgentId(agent) {
12748
12836
  return agent.config.name.toLowerCase().replace(/\s+/g, "-");
12749
12837
  }
12750
- function buildPrompt(agent, availableTools) {
12838
+ function buildPrompt(agent) {
12751
12839
  const instructionsList = agent.config.instructions.map((instruction) => `- ${instruction}`).join(`
12752
12840
  `);
12753
- const allowedTools = agent.getAllowedTools();
12754
- const forbiddenTools = agent.getForbiddenTools(availableTools);
12755
- const toolBoundariesSection = `## TOOL BOUNDARIES
12756
- - **Allowed:** ${allowedTools.length > 0 ? allowedTools.join(", ") : "(none)"}
12757
- - **Forbidden:** ${forbiddenTools.length > 0 ? forbiddenTools.join(", ") : "(none)"}`;
12758
12841
  return `# ${agent.config.name}
12759
-
12760
- ${toolBoundariesSection}
12761
-
12762
12842
  ${instructionsList}
12763
12843
 
12764
12844
  ## Shared Context
12765
12845
  Refer to \`.opcrew/logbook.json\` for the current voyage status.
12766
12846
  `;
12767
12847
  }
12768
- function toOpenCodeAgentConfig(agent, availableTools) {
12769
- const allowedTools = agent.getAllowedTools();
12770
- const validTools = allowedTools.every((tool3) => availableTools.includes(tool3));
12771
- if (!validTools) {
12772
- const invalidTools = allowedTools.filter((tool3) => !availableTools.includes(tool3));
12773
- throw new Error(`Agent '${agent.config.name}' has invalid tools in allowlist: [${invalidTools.join(", ")}]. ` + `Valid tools: [${availableTools.join(", ")}]`);
12774
- }
12848
+ function toOpenCodeAgentConfig(agent) {
12775
12849
  return {
12776
12850
  description: agent.config.name,
12777
12851
  mode: agent.config.mode,
12778
- prompt: buildPrompt(agent, availableTools),
12852
+ prompt: buildPrompt(agent),
12779
12853
  permission: permissionByRole[agent.config.role]
12780
12854
  };
12781
12855
  }
12782
- function createOpCrewPlugin(options = {}) {
12783
- const {
12784
- tools: customTools = {},
12785
- skills,
12786
- availableTools = DEFAULT_CANONICAL_TOOLS,
12787
- includeDefaultTools = true,
12788
- crew: customCrew = crew
12789
- } = options;
12790
- const defaultTools = includeDefaultTools ? { edit_logbook: editLogbookTool } : {};
12791
- const mergedTools = {
12792
- ...defaultTools,
12793
- ...customTools
12794
- };
12795
- const _skills = skills;
12796
- return async ({ client }) => {
12797
- await client.app.log({
12798
- body: {
12799
- service: "opcrew",
12800
- level: "info",
12801
- message: "OpenCode plugin initialized"
12802
- }
12803
- });
12804
- return {
12805
- config: async (config2) => {
12806
- const agents = config2.agent ?? {};
12807
- agents["build"] = { ...agents["build"], disable: true };
12808
- for (const agent of customCrew) {
12809
- const agentId = toAgentId(agent);
12810
- const allowedTools = agent.getAllowedTools();
12811
- const forbiddenTools = agent.getForbiddenTools(availableTools);
12812
- await client.app.log({
12813
- body: {
12814
- service: "opcrew",
12815
- level: "info",
12816
- message: `Registering agent '${agent.config.name}' with tool boundaries`,
12817
- extra: {
12818
- agentId,
12819
- allowedTools,
12820
- forbiddenTools
12821
- }
12822
- }
12823
- });
12824
- agents[agentId] = toOpenCodeAgentConfig(agent, availableTools);
12825
- }
12826
- config2.agent = agents;
12827
- await client.app.log({
12828
- body: {
12829
- service: "opcrew",
12830
- level: "info",
12831
- message: "Crew agents registered",
12832
- extra: { agents: Object.keys(agents) }
12833
- }
12834
- });
12835
- },
12836
- tool: mergedTools
12837
- };
12856
+ var OpCrewPlugin = async ({ client }) => {
12857
+ await client.app.log({
12858
+ body: {
12859
+ service: "opcrew",
12860
+ level: "info",
12861
+ message: "OpenCode plugin initialized"
12862
+ }
12863
+ });
12864
+ return {
12865
+ config: async (config2) => {
12866
+ const agents = config2.agent ?? {};
12867
+ agents["build"] = { ...agents["build"], disable: true };
12868
+ for (const agent of crew) {
12869
+ agents[toAgentId(agent)] = toOpenCodeAgentConfig(agent);
12870
+ }
12871
+ config2.agents = agents;
12872
+ await client.app.log({
12873
+ body: {
12874
+ service: "opcrew",
12875
+ level: "info",
12876
+ message: "Crew agents registered",
12877
+ extra: { agents: Object.keys(agents) }
12878
+ }
12879
+ });
12880
+ },
12881
+ tool: {
12882
+ edit_logbook: editLogbookTool,
12883
+ skill: skillTool
12884
+ }
12838
12885
  };
12839
- }
12840
- var OpCrewPlugin = createOpCrewPlugin();
12886
+ };
12841
12887
  var opencode_plugin_default = OpCrewPlugin;
12842
12888
  export {
12843
12889
  opencode_plugin_default as default,
12844
- createOpCrewPlugin,
12845
- OpCrewPlugin,
12846
- DEFAULT_CANONICAL_TOOLS,
12847
- ALL_AVAILABLE_TOOLS
12890
+ OpCrewPlugin
12848
12891
  };