177 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { tool } from "@opencode-ai/plugin";
 | 
						|
import { readFile, writeFile, mkdir } from "fs/promises";
 | 
						|
import { resolve, normalize, join, dirname } from "path";
 | 
						|
import { homedir } from "os";
 | 
						|
 | 
						|
const CONFIG_BASE = resolve(homedir(), ".config/opencode");
 | 
						|
 | 
						|
function resolvePath(filePath) {
 | 
						|
  if (!filePath.startsWith('/') && !filePath.startsWith('~')) {
 | 
						|
    filePath = join(CONFIG_BASE, filePath);
 | 
						|
  }
 | 
						|
  
 | 
						|
  if (filePath.startsWith('~/')) {
 | 
						|
    filePath = filePath.replace('~/', homedir() + '/');
 | 
						|
  }
 | 
						|
  
 | 
						|
  const normalized = normalize(resolve(filePath));
 | 
						|
  
 | 
						|
  if (!normalized.startsWith(CONFIG_BASE)) {
 | 
						|
    throw new Error(
 | 
						|
      `Access denied: Path must be within ~/.config/opencode\n` +
 | 
						|
      `Attempted: ${normalized}\n` +
 | 
						|
      `Use config_read/config_write/config_edit ONLY for opencode config files.`
 | 
						|
    );
 | 
						|
  }
 | 
						|
  
 | 
						|
  return normalized;
 | 
						|
}
 | 
						|
 | 
						|
export const FileProxyPlugin = async () => {
 | 
						|
  return {
 | 
						|
    tool: {
 | 
						|
      config_read: tool({
 | 
						|
        description: `Read files from OpenCode config directory (~/.config/opencode).
 | 
						|
 | 
						|
**REQUIRED when reading config files from outside ~/.config/opencode directory.**
 | 
						|
 | 
						|
Use this tool when:
 | 
						|
- Reading agent definitions (agent/*.md)
 | 
						|
- Reading skills (skills/*/SKILL.md)
 | 
						|
- Reading workflows (OPTIMIZATION_WORKFLOW.md, etc.)
 | 
						|
- Current working directory is NOT ~/.config/opencode
 | 
						|
 | 
						|
Do NOT use if already in ~/.config/opencode - use standard 'read' tool instead.`,
 | 
						|
        
 | 
						|
        args: {
 | 
						|
          filePath: tool.schema.string()
 | 
						|
            .describe("Path to file (e.g., 'agent/optimize.md' or '~/.config/opencode/skills/skill-name/SKILL.md')")
 | 
						|
        },
 | 
						|
        
 | 
						|
        async execute(args) {
 | 
						|
          try {
 | 
						|
            const validPath = resolvePath(args.filePath);
 | 
						|
            const content = await readFile(validPath, "utf-8");
 | 
						|
            return content;
 | 
						|
          } catch (error) {
 | 
						|
            if (error.code === 'ENOENT') {
 | 
						|
              return `❌ File not found: ${args.filePath}\nCheck path and try again.`;
 | 
						|
            }
 | 
						|
            if (error.code === 'EACCES') {
 | 
						|
              return `❌ Permission denied: ${args.filePath}`;
 | 
						|
            }
 | 
						|
            if (error.message.includes('Access denied')) {
 | 
						|
              return `❌ ${error.message}`;
 | 
						|
            }
 | 
						|
            return `❌ Error reading file: ${error.message}`;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }),
 | 
						|
 | 
						|
      config_write: tool({
 | 
						|
        description: `Write/create files in OpenCode config directory (~/.config/opencode).
 | 
						|
 | 
						|
**REQUIRED when creating/writing config files from outside ~/.config/opencode directory.**
 | 
						|
 | 
						|
Use this tool when:
 | 
						|
- Creating new skills (skills/new-skill/SKILL.md)
 | 
						|
- Creating new agent definitions
 | 
						|
- Writing workflow documentation
 | 
						|
- Current working directory is NOT ~/.config/opencode
 | 
						|
 | 
						|
Common use case: Optimize agent creating skills or updating workflows from project directories.
 | 
						|
 | 
						|
Do NOT use if already in ~/.config/opencode - use standard 'write' tool instead.`,
 | 
						|
        
 | 
						|
        args: {
 | 
						|
          filePath: tool.schema.string()
 | 
						|
            .describe("Path to file (e.g., 'skills/my-skill/SKILL.md')"),
 | 
						|
          content: tool.schema.string()
 | 
						|
            .describe("Complete file content to write")
 | 
						|
        },
 | 
						|
        
 | 
						|
        async execute(args) {
 | 
						|
          try {
 | 
						|
            const validPath = resolvePath(args.filePath);
 | 
						|
            await mkdir(dirname(validPath), { recursive: true });
 | 
						|
            await writeFile(validPath, args.content, "utf-8");
 | 
						|
            return `✅ Successfully wrote to ${args.filePath}`;
 | 
						|
          } catch (error) {
 | 
						|
            if (error.code === 'EACCES') {
 | 
						|
              return `❌ Permission denied: ${args.filePath}`;
 | 
						|
            }
 | 
						|
            if (error.message.includes('Access denied')) {
 | 
						|
              return `❌ ${error.message}`;
 | 
						|
            }
 | 
						|
            return `❌ Error writing file: ${error.message}`;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }),
 | 
						|
 | 
						|
      config_edit: tool({
 | 
						|
        description: `Edit existing files in OpenCode config directory (~/.config/opencode).
 | 
						|
 | 
						|
**REQUIRED when editing config files from outside ~/.config/opencode directory.**
 | 
						|
 | 
						|
Use this tool when:
 | 
						|
- Updating agent definitions (adding sections to optimize.md)
 | 
						|
- Enhancing existing skills
 | 
						|
- Modifying workflow docs
 | 
						|
- Current working directory is NOT ~/.config/opencode
 | 
						|
 | 
						|
Operations: append (add to end), prepend (add to beginning), replace (find and replace text).
 | 
						|
 | 
						|
Do NOT use if already in ~/.config/opencode - use standard 'edit' tool instead.`,
 | 
						|
        
 | 
						|
        args: {
 | 
						|
          filePath: tool.schema.string()
 | 
						|
            .describe("Path to file to edit"),
 | 
						|
          operation: tool.schema.enum(["append", "prepend", "replace"])
 | 
						|
            .describe("Edit operation to perform"),
 | 
						|
          content: tool.schema.string()
 | 
						|
            .describe("Content to add or replacement text"),
 | 
						|
          searchPattern: tool.schema.string()
 | 
						|
            .optional()
 | 
						|
            .describe("Regex pattern to find (required for 'replace' operation)")
 | 
						|
        },
 | 
						|
        
 | 
						|
        async execute(args) {
 | 
						|
          try {
 | 
						|
            const validPath = resolvePath(args.filePath);
 | 
						|
            let fileContent = await readFile(validPath, "utf-8");
 | 
						|
            
 | 
						|
            switch (args.operation) {
 | 
						|
              case "append":
 | 
						|
                fileContent += "\n" + args.content;
 | 
						|
                break;
 | 
						|
              case "prepend":
 | 
						|
                fileContent = args.content + "\n" + fileContent;
 | 
						|
                break;
 | 
						|
              case "replace":
 | 
						|
                if (!args.searchPattern) {
 | 
						|
                  throw new Error("searchPattern required for replace operation");
 | 
						|
                }
 | 
						|
                fileContent = fileContent.replace(new RegExp(args.searchPattern, "g"), args.content);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            
 | 
						|
            await writeFile(validPath, fileContent, "utf-8");
 | 
						|
            return `✅ Successfully edited ${args.filePath} (${args.operation})`;
 | 
						|
          } catch (error) {
 | 
						|
            if (error.code === 'ENOENT') {
 | 
						|
              return `❌ File not found: ${args.filePath}\nUse config_write to create new files.`;
 | 
						|
            }
 | 
						|
            if (error.code === 'EACCES') {
 | 
						|
              return `❌ Permission denied: ${args.filePath}`;
 | 
						|
            }
 | 
						|
            if (error.message.includes('Access denied')) {
 | 
						|
              return `❌ ${error.message}`;
 | 
						|
            }
 | 
						|
            return `❌ Error editing file: ${error.message}`;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
  };
 | 
						|
};
 |