import { tool } from "@opencode-ai/plugin/tool"; import https from "node:https"; import fs from "node:fs"; import path from "node:path"; const NC_URL = "https://nc.hibbhome.com"; const NC_USER = "opencode_memgpt"; const NC_PASS = "ioH2o-QnQJx-8z7Dx-edPyx-pmxLA"; const DAV_BASE = `${NC_URL}/remote.php/dav/files/${NC_USER}`; function authHeader() { return "Basic " + Buffer.from(`${NC_USER}:${NC_PASS}`).toString("base64"); } function davRequest(method, remotePath, body = null) { return new Promise((resolve, reject) => { const url = new URL(`${DAV_BASE}${remotePath}`); const opts = { hostname: url.hostname, port: 443, path: url.pathname, method, headers: { Authorization: authHeader(), }, }; if (body) { opts.headers["Content-Length"] = Buffer.byteLength(body); } const req = https.request(opts, (res) => { const chunks = []; res.on("data", (c) => chunks.push(c)); res.on("end", () => { resolve({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks), }); }); }); req.on("error", reject); if (body) req.write(body); req.end(); }); } export const NextcloudPlugin = async (_ctx) => { return { tool: { nextcloud_upload: tool({ description: "Upload a local file to NextCloud", args: { local_path: tool.schema.string().describe("Local file path to upload"), remote_path: tool.schema.string().describe("Remote path on NextCloud (e.g., /Documents/file.txt)"), }, async execute(args) { const content = fs.readFileSync(args.local_path); const res = davRequest("PUT", args.remote_path, content); return (await res).status === 201 || (await res).status === 204 ? `Uploaded ${args.local_path} to ${args.remote_path}` : `Upload failed with status ${(await res).status}`; }, }), nextcloud_download: tool({ description: "Download a file from NextCloud to local disk", args: { remote_path: tool.schema.string().describe("Remote path on NextCloud"), local_path: tool.schema.string().describe("Local file path to save to"), }, async execute(args) { const res = await davRequest("GET", args.remote_path); if (res.status === 200) { const dir = path.dirname(args.local_path); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(args.local_path, res.body); return `Downloaded ${args.remote_path} to ${args.local_path}`; } return `Download failed with status ${res.status}`; }, }), nextcloud_list: tool({ description: "List files in a NextCloud directory", args: { remote_path: tool.schema.string().describe("Remote directory path on NextCloud (e.g., /Documents)"), }, async execute(args) { const body = ` `; const res = await davRequest("PROPFIND", args.remote_path, body); if (res.status === 207) { const xml = res.body.toString(); const items = []; const regex = /(.*?)<\/d:displayname>/g; let match; while ((match = regex.exec(xml)) !== null) { if (match[1]) items.push(match[1]); } return items.length > 0 ? items.join("\n") : "Empty directory"; } return `List failed with status ${res.status}`; }, }), nextcloud_mkdir: tool({ description: "Create a directory on NextCloud", args: { remote_path: tool.schema.string().describe("Remote directory path to create"), }, async execute(args) { const res = await davRequest("MKCOL", args.remote_path); return res.status === 201 ? `Created directory ${args.remote_path}` : `mkdir failed with status ${res.status}`; }, }), nextcloud_delete: tool({ description: "Delete a file or directory on NextCloud", args: { remote_path: tool.schema.string().describe("Remote path to delete"), }, async execute(args) { const res = await davRequest("DELETE", args.remote_path); return res.status === 204 ? `Deleted ${args.remote_path}` : `Delete failed with status ${res.status}`; }, }), nextcloud_copy: tool({ description: "Copy a file within NextCloud", args: { source_path: tool.schema.string().describe("Source path on NextCloud"), dest_path: tool.schema.string().describe("Destination path on NextCloud"), }, async execute(args) { const url = new URL(`${DAV_BASE}${args.source_path}`); const opts = { hostname: url.hostname, port: 443, path: url.pathname, method: "COPY", headers: { Authorization: authHeader(), Destination: `${DAV_BASE}${args.dest_path}`, }, }; const res = await new Promise((resolve, reject) => { const req = https.request(opts, (r) => { r.on("data", () => {}); r.on("end", () => resolve(r.statusCode)); }); req.on("error", reject); req.end(); }); return res === 201 || res === 204 ? `Copied ${args.source_path} to ${args.dest_path}` : `Copy failed with status ${res}`; }, }), }, }; };