Initial commit: NextCloud file operations skill
This commit is contained in:
commit
e57ef2c25d
167
nextcloud.mjs
Normal file
167
nextcloud.mjs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
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 = `<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<D:propfind xmlns:D="DAV:">
|
||||||
|
<D:prop><D:displayname/><D:getcontentlength/><D:getlastmodified/><D:resourcetype/></D:prop>
|
||||||
|
</D:propfind>`;
|
||||||
|
const res = await davRequest("PROPFIND", args.remote_path, body);
|
||||||
|
if (res.status === 207) {
|
||||||
|
const xml = res.body.toString();
|
||||||
|
const items = [];
|
||||||
|
const regex = /<d:displayname>(.*?)<\/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}`;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
31
nextcloud.sh
Executable file
31
nextcloud.sh
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# NextCloud file operations helper
|
||||||
|
NC_URL="https://nc.hibbhome.com"
|
||||||
|
NC_USER="opencode_memgpt"
|
||||||
|
NC_PASS="ioH2o-QnQJx-8z7Dx-edPyx-pmxLA"
|
||||||
|
DAV_BASE="$NC_URL/remote.php/dav/files/$NC_USER"
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
upload)
|
||||||
|
curl -s -u "$NC_USER:$NC_PASS" "$DAV_BASE$3" -T "$2"
|
||||||
|
echo "Uploaded $2 to $3"
|
||||||
|
;;
|
||||||
|
download)
|
||||||
|
curl -s -u "$NC_USER:$NC_PASS" "$DAV_BASE$2" -o "$3"
|
||||||
|
echo "Downloaded $2 to $3"
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
curl -s -u "$NC_USER:$NC_PASS" "$DAV_BASE$2" -X PROPFIND -H "Depth: 1" | grep -o '<d:href>[^<]*</d:href>' | sed 's/<[^>]*>//g'
|
||||||
|
;;
|
||||||
|
mkdir)
|
||||||
|
curl -s -u "$NC_USER:$NC_PASS" "$DAV_BASE$2" -X MKCOL
|
||||||
|
echo "Created directory $2"
|
||||||
|
;;
|
||||||
|
delete)
|
||||||
|
curl -s -u "$NC_USER:$NC_PASS" "$DAV_BASE$2" -X DELETE
|
||||||
|
echo "Deleted $2"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: nextcloud.sh {upload|download|list|mkdir|delete} [args]"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Loading…
x
Reference in New Issue
Block a user