anywork-library-plugin 0.0.0-stage → 0.2.0
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.
- package/README.md +30 -3
- package/cordis.patch.yml +7 -0
- package/icon.svg +3 -0
- package/lib/api.js +111 -0
- package/lib/client.js +617 -0
- package/lib/index.js +190 -0
- package/lib/panel.js +490 -0
- package/lib/store.js +209 -0
- package/package.json +53 -6
package/lib/store.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export const MAX_UPLOAD = 50 * 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
export function libraryRoot() {
|
|
8
|
+
if (process.env.ANYWORK_LIBRARY_DIR) return path.resolve(process.env.ANYWORK_LIBRARY_DIR);
|
|
9
|
+
const home = process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
10
|
+
return path.join(home, "anywork-library-plugin");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function driveRoot() {
|
|
14
|
+
return path.join(libraryRoot(), "drive");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function notesDir() {
|
|
18
|
+
return path.join(libraryRoot(), "notes");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureDir(dir) {
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveIn(root, rel) {
|
|
26
|
+
if (String(rel || "").includes("\0")) return { error: "bad path" };
|
|
27
|
+
const base = path.resolve(root);
|
|
28
|
+
const abs = path.resolve(base, path.normalize(rel || "."));
|
|
29
|
+
if (abs !== base && !abs.startsWith(base + path.sep)) return { error: "path escapes" };
|
|
30
|
+
return { abs, rel: abs === base ? "" : abs.slice(base.length + 1).split(path.sep).join("/") };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function safeName(name) {
|
|
34
|
+
const value = String(name || "");
|
|
35
|
+
if (!value || value.includes("/") || value.includes("\\") || value.includes("\0")) return null;
|
|
36
|
+
if (value === "." || value === "..") return null;
|
|
37
|
+
if (value.length > 150) return null;
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function stamp(date) {
|
|
42
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
43
|
+
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function listDrive(rel) {
|
|
47
|
+
ensureDir(driveRoot());
|
|
48
|
+
const found = resolveIn(driveRoot(), rel);
|
|
49
|
+
if (found.error) return found;
|
|
50
|
+
if (!fs.existsSync(found.abs)) return { path: found.rel, entries: [] };
|
|
51
|
+
const stat = fs.statSync(found.abs);
|
|
52
|
+
if (!stat.isDirectory()) return { error: "not a directory" };
|
|
53
|
+
const entries = [];
|
|
54
|
+
for (const name of fs.readdirSync(found.abs)) {
|
|
55
|
+
if (name.startsWith(".")) continue;
|
|
56
|
+
let size = 0;
|
|
57
|
+
let mtime = "";
|
|
58
|
+
let dir = false;
|
|
59
|
+
try {
|
|
60
|
+
const item = fs.statSync(path.join(found.abs, name));
|
|
61
|
+
dir = item.isDirectory();
|
|
62
|
+
size = item.isFile() ? item.size : 0;
|
|
63
|
+
mtime = item.mtime.toISOString();
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
entries.push({ name, dir, size, mtime });
|
|
68
|
+
}
|
|
69
|
+
entries.sort((a, b) => (a.dir === b.dir ? a.name.localeCompare(b.name, "zh") : a.dir ? -1 : 1));
|
|
70
|
+
return { path: found.rel, entries };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function makeDir(rel, name) {
|
|
74
|
+
const safe = safeName(name);
|
|
75
|
+
if (!safe) return { error: "bad name" };
|
|
76
|
+
ensureDir(driveRoot());
|
|
77
|
+
const found = resolveIn(driveRoot(), rel ? rel + "/" + safe : safe);
|
|
78
|
+
if (found.error) return found;
|
|
79
|
+
if (fs.existsSync(found.abs)) return { error: "exists" };
|
|
80
|
+
fs.mkdirSync(found.abs, { recursive: false });
|
|
81
|
+
return { ok: true, path: found.rel };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function saveFile(rel, name, data) {
|
|
85
|
+
const safe = safeName(name);
|
|
86
|
+
if (!safe) return { error: "bad name" };
|
|
87
|
+
if (!Buffer.isBuffer(data)) return { error: "bad body" };
|
|
88
|
+
if (data.length > MAX_UPLOAD) return { error: "too large" };
|
|
89
|
+
ensureDir(driveRoot());
|
|
90
|
+
const found = resolveIn(driveRoot(), rel ? rel + "/" + safe : safe);
|
|
91
|
+
if (found.error) return found;
|
|
92
|
+
fs.mkdirSync(path.dirname(found.abs), { recursive: true });
|
|
93
|
+
fs.writeFileSync(found.abs, data);
|
|
94
|
+
return { ok: true, path: found.rel, size: data.length };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function fileForDownload(rel) {
|
|
98
|
+
const found = resolveIn(driveRoot(), rel);
|
|
99
|
+
if (found.error) return found;
|
|
100
|
+
if (!fs.existsSync(found.abs) || !fs.statSync(found.abs).isFile()) return { error: "not found" };
|
|
101
|
+
return { ok: true, abs: found.abs, name: path.basename(found.abs), size: fs.statSync(found.abs).size };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function removePath(rel) {
|
|
105
|
+
if (!rel) return { error: "bad path" };
|
|
106
|
+
const found = resolveIn(driveRoot(), rel);
|
|
107
|
+
if (found.error) return found;
|
|
108
|
+
if (found.abs === path.resolve(driveRoot())) return { error: "cannot remove drive root" };
|
|
109
|
+
if (!fs.existsSync(found.abs)) return { error: "not found" };
|
|
110
|
+
fs.rmSync(found.abs, { recursive: true, force: true });
|
|
111
|
+
return { ok: true };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function movePath(from, toDir) {
|
|
115
|
+
if (!from) return { error: "bad path" };
|
|
116
|
+
const source = resolveIn(driveRoot(), from);
|
|
117
|
+
if (source.error) return source;
|
|
118
|
+
const destDir = resolveIn(driveRoot(), toDir || "");
|
|
119
|
+
if (destDir.error) return destDir;
|
|
120
|
+
if (!fs.existsSync(source.abs)) return { error: "not found" };
|
|
121
|
+
const dest = resolveIn(driveRoot(), destDir.rel ? destDir.rel + "/" + path.basename(source.abs) : path.basename(source.abs));
|
|
122
|
+
if (dest.error) return dest;
|
|
123
|
+
if (dest.abs === source.abs || dest.abs.startsWith(source.abs + path.sep)) return { error: "cannot move into itself" };
|
|
124
|
+
if (fs.existsSync(dest.abs)) return { error: "target exists" };
|
|
125
|
+
fs.mkdirSync(destDir.abs, { recursive: true });
|
|
126
|
+
fs.renameSync(source.abs, dest.abs);
|
|
127
|
+
return { ok: true, path: dest.rel };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function noteName(name) {
|
|
131
|
+
const value = String(name || "");
|
|
132
|
+
if (!value || value !== path.basename(value) || value.startsWith(".") || !/\.md$/i.test(value)) return null;
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function noteMeta(name) {
|
|
137
|
+
const full = path.join(notesDir(), name);
|
|
138
|
+
const stat = fs.statSync(full);
|
|
139
|
+
const head = fs.readFileSync(full, "utf8").slice(0, 400);
|
|
140
|
+
const title = /^title:\s*(.+)$/m.exec(head)?.[1]?.trim() || name.replace(/\.md$/i, "");
|
|
141
|
+
const author = /^author:\s*(.+)$/m.exec(head)?.[1]?.trim() || "";
|
|
142
|
+
return { name, title, author, size: stat.size, mtime: stamp(stat.mtime) };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function listNotes() {
|
|
146
|
+
ensureDir(notesDir());
|
|
147
|
+
const notes = [];
|
|
148
|
+
for (const name of fs.readdirSync(notesDir())) {
|
|
149
|
+
if (!/\.md$/i.test(name) || name.startsWith(".") || /^readme/i.test(name)) continue;
|
|
150
|
+
try { notes.push(noteMeta(name)); } catch { /* skip unreadable */ }
|
|
151
|
+
}
|
|
152
|
+
notes.sort((a, b) => (a.mtime < b.mtime ? 1 : -1));
|
|
153
|
+
return { notes, role: "admin" };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function saveNote(input) {
|
|
157
|
+
const title = String(input.title || "").trim().slice(0, 120);
|
|
158
|
+
const content = String(input.content || "").trim();
|
|
159
|
+
if (!content) return { error: "内容不能为空" };
|
|
160
|
+
if (content.length > 100000) return { error: "内容过长" };
|
|
161
|
+
ensureDir(notesDir());
|
|
162
|
+
const now = new Date();
|
|
163
|
+
const slug = (title || content.split("\n")[0] || "note").replace(/[^\w\u4e00-\u9fff-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "note";
|
|
164
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
165
|
+
const fileStamp = now.getFullYear() + pad(now.getMonth() + 1) + pad(now.getDate()) + "-" + pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds());
|
|
166
|
+
let name = fileStamp + "-" + slug + ".md";
|
|
167
|
+
let n = 2;
|
|
168
|
+
while (fs.existsSync(path.join(notesDir(), name))) {
|
|
169
|
+
name = fileStamp + "-" + slug + "-" + n + ".md";
|
|
170
|
+
n += 1;
|
|
171
|
+
}
|
|
172
|
+
const body = "---\ntitle: " + (title || slug) + "\nauthor: 工作台\ntime: " + stamp(now) + "\n---\n\n" + content + "\n";
|
|
173
|
+
fs.writeFileSync(path.join(notesDir(), name), body, "utf8");
|
|
174
|
+
return { ok: true, name };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function readNote(name) {
|
|
178
|
+
const safe = noteName(name);
|
|
179
|
+
if (!safe) return { error: "名称不合法" };
|
|
180
|
+
const full = path.join(notesDir(), safe);
|
|
181
|
+
if (!fs.existsSync(full)) return { error: "笔记不存在" };
|
|
182
|
+
const raw = fs.readFileSync(full, "utf8");
|
|
183
|
+
const content = raw.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
|
|
184
|
+
return { ok: true, name: safe, content };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function removeNote(name) {
|
|
188
|
+
const safe = noteName(name);
|
|
189
|
+
if (!safe) return { error: "名称不合法" };
|
|
190
|
+
const full = path.join(notesDir(), safe);
|
|
191
|
+
if (!fs.existsSync(full)) return { error: "笔记不存在" };
|
|
192
|
+
fs.unlinkSync(full);
|
|
193
|
+
return { ok: true };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function searchNotes(query) {
|
|
197
|
+
const q = String(query || "").trim().toLowerCase();
|
|
198
|
+
const hits = [];
|
|
199
|
+
const listed = listNotes();
|
|
200
|
+
for (const note of listed.notes) {
|
|
201
|
+
const read = readNote(note.name);
|
|
202
|
+
const text = note.title + "\n" + (read.ok ? read.content : "");
|
|
203
|
+
if (!q || text.toLowerCase().includes(q)) {
|
|
204
|
+
hits.push({ file: "notes/" + note.name, line: 1, text: note.title });
|
|
205
|
+
}
|
|
206
|
+
if (hits.length >= 20) break;
|
|
207
|
+
}
|
|
208
|
+
return { q, hits, truncated: false };
|
|
209
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "anywork-library-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "anywork-library-plugin",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "资料库:文件 + 笔记(DSH Web 插件)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20",
|
|
10
|
+
"dsh": ">=0.2.1-rc.3 <0.3.0"
|
|
11
|
+
},
|
|
12
|
+
"main": "lib/index.js",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./lib/index.js",
|
|
15
|
+
"./client": "./lib/client.js",
|
|
16
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
17
|
+
"./icon": "./icon.svg",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"lib/index.js",
|
|
22
|
+
"lib/store.js",
|
|
23
|
+
"lib/client.js",
|
|
24
|
+
"lib/api.js",
|
|
25
|
+
"lib/panel.js",
|
|
26
|
+
"cordis.patch.yml",
|
|
27
|
+
"icon.svg",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
32
|
+
},
|
|
33
|
+
"dsh": {
|
|
34
|
+
"bundle": {
|
|
35
|
+
"patch": "./cordis.patch.yml"
|
|
36
|
+
},
|
|
37
|
+
"client": {
|
|
38
|
+
"inject": [
|
|
39
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
40
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
41
|
+
],
|
|
42
|
+
"platform": "web"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"assemble": "node scripts/assemble.mjs",
|
|
47
|
+
"test": "node scripts/assemble.mjs --check && node --test"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/TokenAI-Beta/library-plugin.git"
|
|
52
|
+
}
|
|
53
|
+
}
|