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}`;
|
|
}
|
|
}
|
|
})
|
|
}
|
|
};
|
|
};
|