opcrew 0.1.5 → 0.1.6

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.
@@ -12720,26 +12720,109 @@ The logbook tracks:
12720
12720
  }
12721
12721
  });
12722
12722
 
12723
+ // src/skills.ts
12724
+ import { readdir, readFile } from "fs/promises";
12725
+ import path from "path";
12726
+ import { fileURLToPath } from "url";
12727
+ var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
12728
+ async function loadSkills() {
12729
+ const skillsDir = path.join(__dirname2, "skills");
12730
+ try {
12731
+ const entries = await readdir(skillsDir, { withFileTypes: true });
12732
+ const skills = [];
12733
+ for (const entry of entries) {
12734
+ if (!entry.isDirectory())
12735
+ continue;
12736
+ const skillPath = path.join(skillsDir, entry.name);
12737
+ const skillMdPath = path.join(skillPath, "SKILL.md");
12738
+ try {
12739
+ const content = await readFile(skillMdPath, "utf-8");
12740
+ const { name, description } = parseSkillFrontmatter(content);
12741
+ skills.push({
12742
+ name: name || entry.name,
12743
+ description: description || "",
12744
+ sourcePath: skillPath
12745
+ });
12746
+ } catch {
12747
+ console.warn(`Warning: No SKILL.md found in ${skillPath}`);
12748
+ }
12749
+ }
12750
+ return skills;
12751
+ } catch {
12752
+ return [];
12753
+ }
12754
+ }
12755
+ function parseSkillFrontmatter(content) {
12756
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
12757
+ if (!frontmatterMatch) {
12758
+ return {};
12759
+ }
12760
+ const frontmatter = frontmatterMatch[1];
12761
+ const result = {};
12762
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
12763
+ if (nameMatch) {
12764
+ result.name = nameMatch[1].trim();
12765
+ }
12766
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
12767
+ if (descMatch) {
12768
+ result.description = descMatch[1].trim();
12769
+ }
12770
+ return result;
12771
+ }
12772
+ async function getSkillContent(skillName) {
12773
+ const skillMdPath = path.join(__dirname2, "skills", skillName, "SKILL.md");
12774
+ return readFile(skillMdPath, "utf-8");
12775
+ }
12776
+
12777
+ // src/tools/skill.ts
12778
+ var skillTool = tool({
12779
+ description: `Load and manage agent skills dynamically.
12780
+
12781
+ Actions:
12782
+ - list: List all available skills with names and descriptions
12783
+ - load: Load a specific skill's full content (SKILL.md)
12784
+
12785
+ Skills provide specialized instructions and workflows for specific tasks.
12786
+ Use this tool when you recognize that a task matches one of the available skills.`,
12787
+ args: {
12788
+ action: tool.schema.enum(["list", "load"]).describe("Action to perform"),
12789
+ name: tool.schema.string().optional().describe("Skill name (required for 'load' action)")
12790
+ },
12791
+ async execute(args) {
12792
+ switch (args.action) {
12793
+ case "list": {
12794
+ const skills = await loadSkills();
12795
+ if (skills.length === 0) {
12796
+ return "No skills available.";
12797
+ }
12798
+ const formattedList = skills.map((skill) => `- **${skill.name}**: ${skill.description}`).join(`
12799
+ `);
12800
+ return `Available skills:
12801
+
12802
+ ${formattedList}`;
12803
+ }
12804
+ case "load": {
12805
+ if (!args.name) {
12806
+ return "Error: name is required for 'load' action";
12807
+ }
12808
+ try {
12809
+ const content = await getSkillContent(args.name);
12810
+ return content;
12811
+ } catch (error45) {
12812
+ const message = error45 instanceof Error ? error45.message : String(error45);
12813
+ return `Error: Failed to load skill '${args.name}': ${message}`;
12814
+ }
12815
+ }
12816
+ default:
12817
+ return `Error: Unknown action: ${args.action}`;
12818
+ }
12819
+ }
12820
+ });
12821
+
12723
12822
  // 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
12823
  var permissionByRole = {
12741
12824
  Orchestrator: { edit: "deny", bash: "ask", webfetch: "deny" },
12742
- Planner: { edit: "deny", bash: "ask", webfetch: "allow" },
12825
+ Planner: { edit: "deny", bash: "allow", webfetch: "allow" },
12743
12826
  Executor: { edit: "allow", bash: "allow", webfetch: "ask" },
12744
12827
  Reviewer: { edit: "deny", bash: "allow", webfetch: "deny" },
12745
12828
  Researcher: { edit: "deny", bash: "deny", webfetch: "allow" }
@@ -12747,102 +12830,57 @@ var permissionByRole = {
12747
12830
  function toAgentId(agent) {
12748
12831
  return agent.config.name.toLowerCase().replace(/\s+/g, "-");
12749
12832
  }
12750
- function buildPrompt(agent, availableTools) {
12833
+ function buildPrompt(agent) {
12751
12834
  const instructionsList = agent.config.instructions.map((instruction) => `- ${instruction}`).join(`
12752
12835
  `);
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
12836
  return `# ${agent.config.name}
12759
-
12760
- ${toolBoundariesSection}
12761
-
12762
12837
  ${instructionsList}
12763
12838
 
12764
12839
  ## Shared Context
12765
12840
  Refer to \`.opcrew/logbook.json\` for the current voyage status.
12766
12841
  `;
12767
12842
  }
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
- }
12843
+ function toOpenCodeAgentConfig(agent) {
12775
12844
  return {
12776
12845
  description: agent.config.name,
12777
12846
  mode: agent.config.mode,
12778
- prompt: buildPrompt(agent, availableTools),
12847
+ prompt: buildPrompt(agent),
12779
12848
  permission: permissionByRole[agent.config.role]
12780
12849
  };
12781
12850
  }
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
- };
12851
+ var OpCrewPlugin = async ({ client }) => {
12852
+ await client.app.log({
12853
+ body: {
12854
+ service: "opcrew",
12855
+ level: "info",
12856
+ message: "OpenCode plugin initialized"
12857
+ }
12858
+ });
12859
+ return {
12860
+ config: async (config2) => {
12861
+ const agents = config2.agent ?? {};
12862
+ agents["build"] = { ...agents["build"], disable: true };
12863
+ for (const agent of crew) {
12864
+ agents[toAgentId(agent)] = toOpenCodeAgentConfig(agent);
12865
+ }
12866
+ config2.agents = agents;
12867
+ await client.app.log({
12868
+ body: {
12869
+ service: "opcrew",
12870
+ level: "info",
12871
+ message: "Crew agents registered",
12872
+ extra: { agents: Object.keys(agents) }
12873
+ }
12874
+ });
12875
+ },
12876
+ tool: {
12877
+ edit_logbook: editLogbookTool,
12878
+ skill: skillTool
12879
+ }
12838
12880
  };
12839
- }
12840
- var OpCrewPlugin = createOpCrewPlugin();
12881
+ };
12841
12882
  var opencode_plugin_default = OpCrewPlugin;
12842
12883
  export {
12843
12884
  opencode_plugin_default as default,
12844
- createOpCrewPlugin,
12845
- OpCrewPlugin,
12846
- DEFAULT_CANONICAL_TOOLS,
12847
- ALL_AVAILABLE_TOOLS
12885
+ OpCrewPlugin
12848
12886
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opcrew",
3
3
  "description": "OpenCrew agents and OpenCode plugin",
4
- "version": "0.1.5",
4
+ "version": "0.1.6",
5
5
  "main": "./dist/opencode-plugin.js",
6
6
  "module": "./dist/opencode-plugin.js",
7
7
  "type": "module",