clawpad 0.3.7 → 0.3.8

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.
Files changed (160) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +8 -8
  3. package/.next/standalone/.next/build-manifest.json +3 -3
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/api/ai/write/route_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/api/changes/[id]/route_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/api/changes/record/route_client-reference-manifest.js +1 -1
  26. package/.next/standalone/.next/server/app/api/changes/revert/route_client-reference-manifest.js +1 -1
  27. package/.next/standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/api/changes/run/route_client-reference-manifest.js +1 -1
  29. package/.next/standalone/.next/server/app/api/chat/abort/route_client-reference-manifest.js +1 -1
  30. package/.next/standalone/.next/server/app/api/chat/route.js +1 -1
  31. package/.next/standalone/.next/server/app/api/chat/route_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/api/files/pages/[...path]/route_client-reference-manifest.js +1 -1
  33. package/.next/standalone/.next/server/app/api/files/recent/route_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/api/files/search/route_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/api/files/spaces/[space]/pages/route_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/api/files/spaces/route_client-reference-manifest.js +1 -1
  37. package/.next/standalone/.next/server/app/api/files/watch/route_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/api/gateway/detect/route_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app/api/gateway/events/route.js +1 -1
  40. package/.next/standalone/.next/server/app/api/gateway/events/route_client-reference-manifest.js +1 -1
  41. package/.next/standalone/.next/server/app/api/gateway/features/route_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/api/gateway/history/route_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/api/gateway/resolve/route_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/api/gateway/sessions/route_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/api/gateway/status/route_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/api/openclaw/commands/route_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/api/settings/search-status/route_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/api/setup/bootstrap/route_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/api/setup/bootstrap-workspace/route_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/api/setup/status/route_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/api/setup/trigger-onboarding/route_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/index.html +1 -1
  54. package/.next/standalone/.next/server/app/index.rsc +3 -3
  55. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  56. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  57. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  58. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  59. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  60. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/settings/connection/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/connection.html +1 -1
  63. package/.next/standalone/.next/server/app/settings/connection.rsc +3 -3
  64. package/.next/standalone/.next/server/app/settings/connection.segments/_full.segment.rsc +3 -3
  65. package/.next/standalone/.next/server/app/settings/connection.segments/_head.segment.rsc +1 -1
  66. package/.next/standalone/.next/server/app/settings/connection.segments/_index.segment.rsc +3 -3
  67. package/.next/standalone/.next/server/app/settings/connection.segments/_tree.segment.rsc +1 -1
  68. package/.next/standalone/.next/server/app/settings/connection.segments/settings/connection/__PAGE__.segment.rsc +1 -1
  69. package/.next/standalone/.next/server/app/settings/connection.segments/settings/connection.segment.rsc +1 -1
  70. package/.next/standalone/.next/server/app/settings/connection.segments/settings.segment.rsc +1 -1
  71. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/settings/relay/page_client-reference-manifest.js +1 -1
  73. package/.next/standalone/.next/server/app/settings/relay.html +1 -1
  74. package/.next/standalone/.next/server/app/settings/relay.rsc +3 -3
  75. package/.next/standalone/.next/server/app/settings/relay.segments/_full.segment.rsc +3 -3
  76. package/.next/standalone/.next/server/app/settings/relay.segments/_head.segment.rsc +1 -1
  77. package/.next/standalone/.next/server/app/settings/relay.segments/_index.segment.rsc +3 -3
  78. package/.next/standalone/.next/server/app/settings/relay.segments/_tree.segment.rsc +1 -1
  79. package/.next/standalone/.next/server/app/settings/relay.segments/settings/relay/__PAGE__.segment.rsc +1 -1
  80. package/.next/standalone/.next/server/app/settings/relay.segments/settings/relay.segment.rsc +1 -1
  81. package/.next/standalone/.next/server/app/settings/relay.segments/settings.segment.rsc +1 -1
  82. package/.next/standalone/.next/server/app/settings.html +1 -1
  83. package/.next/standalone/.next/server/app/settings.rsc +3 -3
  84. package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  85. package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  86. package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +3 -3
  87. package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  88. package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
  89. package/.next/standalone/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  90. package/.next/standalone/.next/server/app/setup/page.js +2 -2
  91. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  92. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  93. package/.next/standalone/.next/server/app/setup.html +1 -1
  94. package/.next/standalone/.next/server/app/setup.rsc +4 -4
  95. package/.next/standalone/.next/server/app/setup.segments/_full.segment.rsc +4 -4
  96. package/.next/standalone/.next/server/app/setup.segments/_head.segment.rsc +1 -1
  97. package/.next/standalone/.next/server/app/setup.segments/_index.segment.rsc +3 -3
  98. package/.next/standalone/.next/server/app/setup.segments/_tree.segment.rsc +1 -1
  99. package/.next/standalone/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +2 -2
  100. package/.next/standalone/.next/server/app/setup.segments/setup.segment.rsc +1 -1
  101. package/.next/standalone/.next/server/app/waitlist/page_client-reference-manifest.js +1 -1
  102. package/.next/standalone/.next/server/app/waitlist.html +1 -1
  103. package/.next/standalone/.next/server/app/waitlist.rsc +3 -3
  104. package/.next/standalone/.next/server/app/waitlist.segments/_full.segment.rsc +3 -3
  105. package/.next/standalone/.next/server/app/waitlist.segments/_head.segment.rsc +1 -1
  106. package/.next/standalone/.next/server/app/waitlist.segments/_index.segment.rsc +3 -3
  107. package/.next/standalone/.next/server/app/waitlist.segments/_tree.segment.rsc +1 -1
  108. package/.next/standalone/.next/server/app/waitlist.segments/waitlist/__PAGE__.segment.rsc +1 -1
  109. package/.next/standalone/.next/server/app/waitlist.segments/waitlist.segment.rsc +1 -1
  110. package/.next/standalone/.next/server/app/workspace/[...path]/page.js +1 -1
  111. package/.next/standalone/.next/server/app/workspace/[...path]/page.js.nft.json +1 -1
  112. package/.next/standalone/.next/server/app/workspace/[...path]/page_client-reference-manifest.js +1 -1
  113. package/.next/standalone/.next/server/app/workspace/page.js +1 -1
  114. package/.next/standalone/.next/server/app/workspace/page.js.nft.json +1 -1
  115. package/.next/standalone/.next/server/app/workspace/page_client-reference-manifest.js +1 -1
  116. package/.next/standalone/.next/server/app/workspace/search/page.js +1 -1
  117. package/.next/standalone/.next/server/app/workspace/search/page.js.nft.json +1 -1
  118. package/.next/standalone/.next/server/app/workspace/search/page_client-reference-manifest.js +1 -1
  119. package/.next/standalone/.next/server/app/workspace/search.html +2 -2
  120. package/.next/standalone/.next/server/app/workspace/search.rsc +4 -4
  121. package/.next/standalone/.next/server/app/workspace/search.segments/_full.segment.rsc +4 -4
  122. package/.next/standalone/.next/server/app/workspace/search.segments/_head.segment.rsc +1 -1
  123. package/.next/standalone/.next/server/app/workspace/search.segments/_index.segment.rsc +3 -3
  124. package/.next/standalone/.next/server/app/workspace/search.segments/_tree.segment.rsc +1 -1
  125. package/.next/standalone/.next/server/app/workspace/search.segments/workspace/search/__PAGE__.segment.rsc +1 -1
  126. package/.next/standalone/.next/server/app/workspace/search.segments/workspace/search.segment.rsc +1 -1
  127. package/.next/standalone/.next/server/app/workspace/search.segments/workspace.segment.rsc +2 -2
  128. package/.next/standalone/.next/server/app/workspace.html +2 -2
  129. package/.next/standalone/.next/server/app/workspace.rsc +4 -4
  130. package/.next/standalone/.next/server/app/workspace.segments/_full.segment.rsc +4 -4
  131. package/.next/standalone/.next/server/app/workspace.segments/_head.segment.rsc +1 -1
  132. package/.next/standalone/.next/server/app/workspace.segments/_index.segment.rsc +3 -3
  133. package/.next/standalone/.next/server/app/workspace.segments/_tree.segment.rsc +1 -1
  134. package/.next/standalone/.next/server/app/workspace.segments/workspace/__PAGE__.segment.rsc +1 -1
  135. package/.next/standalone/.next/server/app/workspace.segments/workspace.segment.rsc +2 -2
  136. package/.next/standalone/.next/server/app-paths-manifest.json +8 -8
  137. package/.next/standalone/.next/server/chunks/3251.js +1 -1
  138. package/.next/standalone/.next/server/chunks/3887.js +9 -0
  139. package/.next/standalone/.next/server/chunks/4153.js +1 -1
  140. package/.next/standalone/.next/server/middleware-build-manifest.js +1 -1
  141. package/.next/standalone/.next/server/pages/404.html +1 -1
  142. package/.next/standalone/.next/server/pages/500.html +2 -2
  143. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  144. package/.next/standalone/package.json +3 -1
  145. package/.next/static/chunks/4747-c8d07e5bd3f8e1e6.js +9 -0
  146. package/.next/static/chunks/app/layout-df1bcd23e841ae19.js +1 -0
  147. package/.next/static/chunks/app/setup/{page-b7a32490f665e413.js → page-9e2117ba208823f3.js} +1 -1
  148. package/.next/static/chunks/app/workspace/{layout-0d997ba181c7a0ac.js → layout-e575f9cf6cc5dad5.js} +2 -2
  149. package/.next/static/chunks/{ba12c10f-c1e7cb71acafac81.js → ba12c10f-711be30d2e4894a2.js} +1 -1
  150. package/.next/static/chunks/{webpack-0c9808987c6b957c.js → webpack-c9a7907951504d87.js} +1 -1
  151. package/bin/clawpad.js +197 -21
  152. package/openclaw-plugin/index.js +429 -0
  153. package/openclaw-plugin/openclaw.plugin.json +15 -0
  154. package/openclaw-plugin/package.json +35 -0
  155. package/package.json +3 -1
  156. package/.next/standalone/.next/server/chunks/4098.js +0 -9
  157. package/.next/static/chunks/7545-372a6ac31856a302.js +0 -9
  158. package/.next/static/chunks/app/layout-d1b7bdd23960e354.js +0 -1
  159. /package/.next/static/{cngTVEHNBmOtH1dj5epp6 → lPQAvW9U_0cnBIN_JiTvo}/_buildManifest.js +0 -0
  160. /package/.next/static/{cngTVEHNBmOtH1dj5epp6 → lPQAvW9U_0cnBIN_JiTvo}/_ssgManifest.js +0 -0
@@ -0,0 +1,429 @@
1
+ import fs from "node:fs";
2
+ import fsp from "node:fs/promises";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { Type } from "@sinclair/typebox";
6
+ import matter from "gray-matter";
7
+
8
+ const DEFAULT_PAGES_DIRNAME = "pages";
9
+
10
+ function jsonResult(payload) {
11
+ return {
12
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
13
+ details: payload,
14
+ };
15
+ }
16
+
17
+ function resolveUserPath(input) {
18
+ if (!input || typeof input !== "string") return input;
19
+ const trimmed = input.trim();
20
+ if (!trimmed) return trimmed;
21
+ if (trimmed.startsWith("~")) {
22
+ return path.resolve(trimmed.replace(/^~(?=$|[\\/])/, os.homedir()));
23
+ }
24
+ return path.resolve(trimmed);
25
+ }
26
+
27
+ function resolveOpenClawStateDir() {
28
+ const override = process.env.OPENCLAW_STATE_DIR || process.env.CLAWDBOT_STATE_DIR;
29
+ if (override && override.trim()) {
30
+ return resolveUserPath(override);
31
+ }
32
+ return path.join(os.homedir(), ".openclaw");
33
+ }
34
+
35
+ function resolvePagesDir(config, pluginConfig) {
36
+ const explicit = process.env.CLAWPAD_PAGES_DIR;
37
+ if (explicit && explicit.trim()) {
38
+ return resolveUserPath(explicit);
39
+ }
40
+ if (pluginConfig?.pagesDir && String(pluginConfig.pagesDir).trim()) {
41
+ return resolveUserPath(String(pluginConfig.pagesDir));
42
+ }
43
+
44
+ const legacyDir = path.join(resolveOpenClawStateDir(), DEFAULT_PAGES_DIRNAME);
45
+ if (fs.existsSync(legacyDir)) {
46
+ return legacyDir;
47
+ }
48
+
49
+ const workspace = config?.agents?.defaults?.workspace;
50
+ if (typeof workspace === "string" && workspace.trim()) {
51
+ return path.join(resolveUserPath(workspace), DEFAULT_PAGES_DIRNAME);
52
+ }
53
+
54
+ return legacyDir;
55
+ }
56
+
57
+ function ensureSafeRelative(relPath) {
58
+ if (!relPath || typeof relPath !== "string") return false;
59
+ if (relPath.includes("\0")) return false;
60
+ if (path.isAbsolute(relPath)) return false;
61
+ const normalized = path.normalize(relPath);
62
+ if (normalized.startsWith("..") || normalized.includes(`${path.sep}..`)) return false;
63
+ return true;
64
+ }
65
+
66
+ function resolvePagePath(pagesDir, relPath) {
67
+ if (!ensureSafeRelative(relPath)) {
68
+ throw new Error(`Invalid path: ${relPath}`);
69
+ }
70
+ const resolved = path.resolve(pagesDir, relPath);
71
+ const pagesResolved = path.resolve(pagesDir);
72
+ if (!resolved.startsWith(pagesResolved + path.sep) && resolved !== pagesResolved) {
73
+ throw new Error(`Path escapes pages dir: ${relPath}`);
74
+ }
75
+ return resolved;
76
+ }
77
+
78
+ function ensureMdExtension(relPath) {
79
+ return relPath.endsWith(".md") ? relPath : `${relPath}.md`;
80
+ }
81
+
82
+ async function readSpaceMeta(spacePath) {
83
+ const ymlPath = path.join(spacePath, "_space.yml");
84
+ try {
85
+ const raw = await fsp.readFile(ymlPath, "utf-8");
86
+ const meta = { name: path.basename(spacePath) };
87
+ const lines = raw.split("\n");
88
+ for (const line of lines) {
89
+ const match = line.match(/^(\w+):\s*(.+)$/);
90
+ if (!match) continue;
91
+ const [, key, rawValue] = match;
92
+ const value = rawValue.replace(/^["']|["']$/g, "").trim();
93
+ if (key === "name") meta.name = value;
94
+ if (key === "icon") meta.icon = value;
95
+ if (key === "color") meta.color = value;
96
+ if (key === "sort") meta.sort = value;
97
+ }
98
+ return meta;
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+
104
+ async function countPagesInDir(dir) {
105
+ let count = 0;
106
+ const entries = await fsp.readdir(dir, { withFileTypes: true });
107
+ for (const entry of entries) {
108
+ if (entry.name.startsWith(".")) continue;
109
+ const full = path.join(dir, entry.name);
110
+ if (entry.isDirectory()) {
111
+ count += await countPagesInDir(full);
112
+ continue;
113
+ }
114
+ if (entry.isFile() && entry.name.endsWith(".md")) {
115
+ count += 1;
116
+ }
117
+ }
118
+ return count;
119
+ }
120
+
121
+ async function listSpaces(pagesDir) {
122
+ await fsp.mkdir(pagesDir, { recursive: true });
123
+ const entries = await fsp.readdir(pagesDir, { withFileTypes: true });
124
+ const spaces = [];
125
+ for (const entry of entries) {
126
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
127
+ const spacePath = path.join(pagesDir, entry.name);
128
+ const meta = await readSpaceMeta(spacePath);
129
+ const pageCount = await countPagesInDir(spacePath);
130
+ spaces.push({
131
+ name: meta?.name ?? entry.name,
132
+ icon: meta?.icon,
133
+ color: meta?.color,
134
+ sort: meta?.sort,
135
+ path: entry.name,
136
+ pageCount,
137
+ });
138
+ }
139
+ return spaces;
140
+ }
141
+
142
+ async function listPages(pagesDir, space, recursive) {
143
+ const relSpace = ensureSafeRelative(space) ? space : null;
144
+ if (!relSpace) throw new Error(`Invalid space: ${space}`);
145
+ const spacePath = resolvePagePath(pagesDir, relSpace);
146
+ const pages = [];
147
+
148
+ async function walk(dir, prefix) {
149
+ const entries = await fsp.readdir(dir, { withFileTypes: true });
150
+ for (const entry of entries) {
151
+ if (entry.name.startsWith(".")) continue;
152
+ const full = path.join(dir, entry.name);
153
+ const rel = path.join(prefix, entry.name);
154
+ if (entry.isDirectory()) {
155
+ if (recursive) {
156
+ await walk(full, rel);
157
+ }
158
+ continue;
159
+ }
160
+ if (entry.isFile() && entry.name.endsWith(".md")) {
161
+ try {
162
+ const raw = await fsp.readFile(full, "utf-8");
163
+ const stat = await fsp.stat(full);
164
+ const { meta, content } = parseFrontmatter(raw);
165
+ pages.push({
166
+ path: rel.replace(/\\/g, "/"),
167
+ title: meta.title || extractTitle(content, entry.name),
168
+ icon: meta.icon,
169
+ created: meta.created || stat.birthtime.toISOString(),
170
+ modified: meta.modified || stat.mtime.toISOString(),
171
+ tags: meta.tags,
172
+ size: stat.size,
173
+ });
174
+ } catch {
175
+ pages.push({ path: rel.replace(/\\/g, "/") });
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ await walk(spacePath, relSpace);
182
+ return pages;
183
+ }
184
+
185
+ async function readPage(pagesDir, relPath) {
186
+ const full = resolvePagePath(pagesDir, relPath);
187
+ const raw = await fsp.readFile(full, "utf-8");
188
+ const { meta, content } = parseFrontmatter(raw);
189
+ return { path: relPath, content, meta };
190
+ }
191
+
192
+ async function writePage(pagesDir, relPath, content, mode, metaInput) {
193
+ const withExt = ensureMdExtension(relPath);
194
+ const full = resolvePagePath(pagesDir, withExt);
195
+ await fsp.mkdir(path.dirname(full), { recursive: true });
196
+ let existing = { meta: {}, content: "" };
197
+ if (fs.existsSync(full)) {
198
+ try {
199
+ const raw = await fsp.readFile(full, "utf-8");
200
+ existing = parseFrontmatter(raw);
201
+ } catch {
202
+ // ignore
203
+ }
204
+ }
205
+
206
+ const nextContent = mode === "append" ? `${existing.content}${content}` : content;
207
+ const now = new Date().toISOString();
208
+ const nextMeta = buildNextMeta({
209
+ existing: existing.meta,
210
+ incoming: metaInput,
211
+ content: nextContent,
212
+ filename: path.basename(withExt),
213
+ now,
214
+ });
215
+
216
+ const serialized = serializeFrontmatter(nextContent, nextMeta);
217
+ await fsp.writeFile(full, serialized, "utf-8");
218
+ return { path: withExt, meta: nextMeta };
219
+ }
220
+
221
+ async function searchPages(pagesDir, query, limit) {
222
+ const results = [];
223
+ const normalizedQuery = query.toLowerCase();
224
+ const max = Math.max(1, limit || 10);
225
+
226
+ async function walk(dir) {
227
+ const entries = await fsp.readdir(dir, { withFileTypes: true });
228
+ for (const entry of entries) {
229
+ if (results.length >= max) return;
230
+ if (entry.name.startsWith(".")) continue;
231
+ const full = path.join(dir, entry.name);
232
+ if (entry.isDirectory()) {
233
+ await walk(full);
234
+ continue;
235
+ }
236
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
237
+ const text = await fsp.readFile(full, "utf-8");
238
+ const idx = text.toLowerCase().indexOf(normalizedQuery);
239
+ if (idx === -1) continue;
240
+ const start = Math.max(0, idx - 80);
241
+ const end = Math.min(text.length, idx + 120);
242
+ const snippet = text.slice(start, end).replace(/\s+/g, " ").trim();
243
+ results.push({
244
+ path: path.relative(pagesDir, full).replace(/\\/g, "/"),
245
+ snippet,
246
+ });
247
+ }
248
+ }
249
+
250
+ if (fs.existsSync(pagesDir)) {
251
+ await walk(pagesDir);
252
+ }
253
+
254
+ return results;
255
+ }
256
+
257
+ function parseFrontmatter(raw) {
258
+ const { data, content } = matter(raw);
259
+ const meta = {};
260
+ if (typeof data.title === "string") meta.title = data.title;
261
+ if (typeof data.icon === "string") meta.icon = data.icon;
262
+ if (data.created) meta.created = new Date(data.created).toISOString();
263
+ if (data.modified) meta.modified = new Date(data.modified).toISOString();
264
+ if (Array.isArray(data.tags)) {
265
+ meta.tags = data.tags.filter((t) => typeof t === "string");
266
+ }
267
+ return { meta, content };
268
+ }
269
+
270
+ function serializeFrontmatter(content, meta) {
271
+ const frontmatter = {};
272
+ if (meta.title) frontmatter.title = meta.title;
273
+ if (meta.icon) frontmatter.icon = meta.icon;
274
+ if (meta.created) frontmatter.created = meta.created;
275
+ if (meta.modified) frontmatter.modified = meta.modified;
276
+ if (Array.isArray(meta.tags) && meta.tags.length > 0) {
277
+ frontmatter.tags = meta.tags;
278
+ }
279
+ if (Object.keys(frontmatter).length === 0) {
280
+ return content;
281
+ }
282
+ return matter.stringify(content, frontmatter);
283
+ }
284
+
285
+ function extractTitle(content, filename) {
286
+ const h1Match = content.match(/^#\\s+(.+)$/m);
287
+ if (h1Match) {
288
+ return h1Match[1].trim();
289
+ }
290
+ const name = filename.replace(/\\.md$/, "");
291
+ return name
292
+ .replace(/[-_]+/g, " ")
293
+ .replace(/\\b\\w/g, (c) => c.toUpperCase());
294
+ }
295
+
296
+ function normalizeTags(value) {
297
+ if (!value) return undefined;
298
+ if (Array.isArray(value)) {
299
+ const tags = value.filter((t) => typeof t === "string").map((t) => t.trim()).filter(Boolean);
300
+ return tags.length > 0 ? tags : undefined;
301
+ }
302
+ if (typeof value === "string") {
303
+ const trimmed = value.trim();
304
+ return trimmed ? [trimmed] : undefined;
305
+ }
306
+ return undefined;
307
+ }
308
+
309
+ function buildNextMeta(params) {
310
+ const existing = params.existing || {};
311
+ const incoming = params.incoming || {};
312
+ const next = { ...existing };
313
+
314
+ if (typeof incoming.title === "string" && incoming.title.trim()) {
315
+ next.title = incoming.title.trim();
316
+ } else if (!next.title) {
317
+ next.title = extractTitle(params.content, params.filename);
318
+ }
319
+
320
+ if (typeof incoming.icon === "string" && incoming.icon.trim()) {
321
+ next.icon = incoming.icon.trim();
322
+ }
323
+
324
+ const tags = normalizeTags(incoming.tags);
325
+ if (tags) {
326
+ next.tags = tags;
327
+ }
328
+
329
+ const created = typeof incoming.created === "string" && incoming.created.trim()
330
+ ? incoming.created.trim()
331
+ : next.created;
332
+ next.created = created || params.now;
333
+
334
+ const modified = typeof incoming.modified === "string" && incoming.modified.trim()
335
+ ? incoming.modified.trim()
336
+ : params.now;
337
+ next.modified = modified;
338
+
339
+ return next;
340
+ }
341
+
342
+ export default {
343
+ id: "openclaw-plugin",
344
+ name: "ClawPad",
345
+ description: "ClawPad document tools (read/write/search pages)",
346
+ version: "0.1.0",
347
+ register(api) {
348
+ const pagesDir = resolvePagesDir(api.config, api.pluginConfig);
349
+
350
+ api.registerTool({
351
+ name: "clawpad_spaces",
352
+ description: "List ClawPad spaces (top-level folders).",
353
+ parameters: Type.Object({}),
354
+ execute: async () => jsonResult({ pagesDir, spaces: await listSpaces(pagesDir) }),
355
+ });
356
+
357
+ api.registerTool({
358
+ name: "clawpad_pages",
359
+ description: "List pages in a ClawPad space.",
360
+ parameters: Type.Object({
361
+ space: Type.String(),
362
+ recursive: Type.Optional(Type.Boolean()),
363
+ }),
364
+ execute: async (_toolCallId, params) => {
365
+ const space = String(params.space || "").trim();
366
+ const recursive = Boolean(params.recursive);
367
+ const pages = await listPages(pagesDir, space, recursive);
368
+ return jsonResult({ pagesDir, space, pages });
369
+ },
370
+ });
371
+
372
+ api.registerTool({
373
+ name: "clawpad_read",
374
+ description: "Read a ClawPad markdown page by relative path.",
375
+ parameters: Type.Object({
376
+ path: Type.String(),
377
+ }),
378
+ execute: async (_toolCallId, params) => {
379
+ const relPath = String(params.path || "").trim();
380
+ const data = await readPage(pagesDir, relPath);
381
+ return jsonResult(data);
382
+ },
383
+ });
384
+
385
+ api.registerTool({
386
+ name: "clawpad_write",
387
+ description: "Write a ClawPad markdown page by relative path.",
388
+ parameters: Type.Object({
389
+ path: Type.String(),
390
+ content: Type.String(),
391
+ title: Type.Optional(Type.String()),
392
+ icon: Type.Optional(Type.String()),
393
+ tags: Type.Optional(Type.Array(Type.String())),
394
+ created: Type.Optional(Type.String()),
395
+ modified: Type.Optional(Type.String()),
396
+ mode: Type.Optional(Type.String()),
397
+ }),
398
+ execute: async (_toolCallId, params) => {
399
+ const relPath = String(params.path || "").trim();
400
+ const content = String(params.content ?? "");
401
+ const mode = params.mode === "append" ? "append" : "overwrite";
402
+ const metaInput = {
403
+ title: params.title,
404
+ icon: params.icon,
405
+ tags: params.tags,
406
+ created: params.created,
407
+ modified: params.modified,
408
+ };
409
+ const data = await writePage(pagesDir, relPath, content, mode, metaInput);
410
+ return jsonResult(data);
411
+ },
412
+ });
413
+
414
+ api.registerTool({
415
+ name: "clawpad_search",
416
+ description: "Search ClawPad pages for a text query.",
417
+ parameters: Type.Object({
418
+ query: Type.String(),
419
+ limit: Type.Optional(Type.Number()),
420
+ }),
421
+ execute: async (_toolCallId, params) => {
422
+ const query = String(params.query || "").trim();
423
+ const limit = typeof params.limit === "number" ? params.limit : undefined;
424
+ const results = await searchPages(pagesDir, query, limit);
425
+ return jsonResult({ query, results });
426
+ },
427
+ });
428
+ },
429
+ };
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "openclaw-plugin",
3
+ "name": "ClawPad",
4
+ "description": "ClawPad document tools (read/write/search pages)",
5
+ "version": "0.3.1",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "pagesDir": {
11
+ "type": "string"
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@clawpad/openclaw-plugin",
3
+ "version": "0.3.1",
4
+ "description": "OpenClaw integration for ClawPad (docs tools)",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/mhmdez/clawpad",
8
+ "directory": "openclaw-plugin"
9
+ },
10
+ "type": "module",
11
+ "main": "./index.js",
12
+ "exports": "./index.js",
13
+ "files": [
14
+ "index.js",
15
+ "openclaw.plugin.json"
16
+ ],
17
+ "keywords": [
18
+ "openclaw",
19
+ "clawpad",
20
+ "plugin",
21
+ "workspace"
22
+ ],
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "dependencies": {
27
+ "@sinclair/typebox": "^0.33.5",
28
+ "gray-matter": "^4.0.3"
29
+ },
30
+ "openclaw": {
31
+ "extensions": [
32
+ "./index.js"
33
+ ]
34
+ }
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawpad",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "A file-based, Notion-style document workspace for OpenClaw",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -27,6 +27,7 @@
27
27
  "files": [
28
28
  "bin/",
29
29
  "skills/",
30
+ "openclaw-plugin/",
30
31
  ".next/standalone/",
31
32
  ".next/static/",
32
33
  "public/",
@@ -53,6 +54,7 @@
53
54
  "@blocknote/mantine": "^0.46.2",
54
55
  "@blocknote/react": "^0.46.2",
55
56
  "@mantine/core": "^8.3.14",
57
+ "@sinclair/typebox": "^0.33.5",
56
58
  "@tanstack/react-query": "^5.90.20",
57
59
  "ai": "^6.0.69",
58
60
  "chokidar": "^5.0.0",