@2en/clawly-plugins 1.11.0 → 1.12.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.
Files changed (2) hide show
  1. package/gateway/memory.ts +99 -78
  2. package/package.json +1 -1
package/gateway/memory.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Memory directory browser — expose memory directory .md files as Gateway RPC methods.
2
+ * Memory & workspace browser — expose workspace .md files as Gateway RPC methods.
3
3
  *
4
4
  * Gateway methods:
5
- * - memory-browser.list — list all .md files in the OpenClaw memory directory
6
- * - memory-browser.get — return the content of a single .md file by path
5
+ * - memory-browser.list — list .md files in memory directory (may be shadowed by built-in)
6
+ * - memory-browser.get — read a .md file from memory directory (may be shadowed by built-in)
7
+ * - clawly.workspace.list — list root-level .md files in workspace directory
8
+ * - clawly.workspace.get — read a .md file from workspace root
7
9
  */
8
10
 
9
11
  import fs from 'node:fs/promises'
@@ -108,30 +110,84 @@ async function listRootMdFiles(dir: string): Promise<string[]> {
108
110
  .sort()
109
111
  }
110
112
 
113
+ /** Validate path and read a .md file, responding via the RPC respond callback. */
114
+ async function readMdFile(
115
+ dir: string,
116
+ relativePath: string | undefined,
117
+ api: PluginApi,
118
+ respond: (ok: boolean, payload?: unknown, error?: {code: string; message: string}) => void,
119
+ ) {
120
+ if (!relativePath) {
121
+ respond(false, undefined, {
122
+ code: 'invalid_params',
123
+ message: 'path (relative path to .md file) is required',
124
+ })
125
+ return
126
+ }
127
+ if (!isSafeRelativePath(relativePath)) {
128
+ respond(false, undefined, {
129
+ code: 'invalid_params',
130
+ message: 'path must be a safe relative path (no directory traversal)',
131
+ })
132
+ return
133
+ }
134
+ if (!relativePath.toLowerCase().endsWith('.md')) {
135
+ respond(false, undefined, {
136
+ code: 'invalid_params',
137
+ message: 'path must point to a .md file',
138
+ })
139
+ return
140
+ }
141
+
142
+ const fullPath = path.join(dir, path.normalize(relativePath))
143
+ const realBase = await fs.realpath(dir).catch(() => dir)
144
+ const realResolved = await fs.realpath(fullPath).catch(() => null)
145
+
146
+ if (!realResolved) {
147
+ respond(false, undefined, {code: 'not_found', message: 'file not found'})
148
+ return
149
+ }
150
+ const rel = path.relative(realBase, realResolved)
151
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
152
+ respond(false, undefined, {
153
+ code: 'not_found',
154
+ message: 'file not found or outside allowed directory',
155
+ })
156
+ return
157
+ }
158
+
159
+ try {
160
+ const content = await fs.readFile(fullPath, 'utf-8')
161
+ respond(true, {path: relativePath, content})
162
+ } catch (err) {
163
+ const code = (err as NodeJS.ErrnoException).code
164
+ if (code === 'ENOENT') {
165
+ respond(false, undefined, {code: 'not_found', message: 'file not found'})
166
+ return
167
+ }
168
+ api.logger.error(`readMdFile failed: ${err instanceof Error ? err.message : String(err)}`)
169
+ respond(false, undefined, {
170
+ code: 'error',
171
+ message: err instanceof Error ? err.message : String(err),
172
+ })
173
+ }
174
+ }
175
+
111
176
  export function registerMemoryBrowser(api: PluginApi) {
112
177
  const memoryDir = resolveMemoryDir(api)
113
- api.logger.info(`memory-browser: memory directory: ${memoryDir}`)
178
+ const wsRoot = resolveWorkspaceRoot(api)
179
+ api.logger.info(`memory-browser: memory=${memoryDir} workspace=${wsRoot}`)
114
180
 
115
181
  // -----------------------------
116
- // memory-browser.list
182
+ // memory-browser.list (may be shadowed by built-in)
117
183
  // -----------------------------
118
184
  api.registerGatewayMethod('memory-browser.list', async ({params, respond}) => {
119
185
  try {
120
- api.logger.info(`memory-browser.list params: ${JSON.stringify(params)}`)
121
186
  const profile = readString(params, 'profile')
122
- const scope = readString(params, 'scope') ?? 'memory'
123
-
124
- let files: string[]
125
- if (scope === 'root') {
126
- const wsRoot = resolveWorkspaceRoot(api, profile)
127
- files = await listRootMdFiles(wsRoot)
128
- api.logger.info(`memory-browser.list (root): ${files.length} files found in ${wsRoot}`)
129
- } else {
130
- const dir = profile ? resolveMemoryDir(api, profile) : memoryDir
131
- files = await collectMdFiles(dir, dir)
132
- files.sort()
133
- api.logger.info(`memory-browser.list: ${files.length} files found`)
134
- }
187
+ const dir = profile ? resolveMemoryDir(api, profile) : memoryDir
188
+ const files = await collectMdFiles(dir, dir)
189
+ files.sort()
190
+ api.logger.info(`memory-browser.list: ${files.length} files found`)
135
191
  respond(true, {files})
136
192
  } catch (err) {
137
193
  api.logger.error(
@@ -145,71 +201,27 @@ export function registerMemoryBrowser(api: PluginApi) {
145
201
  })
146
202
 
147
203
  // -----------------------------
148
- // memory-browser.get
204
+ // memory-browser.get (may be shadowed by built-in)
149
205
  // -----------------------------
150
206
  api.registerGatewayMethod('memory-browser.get', async ({params, respond}) => {
151
- const relativePath = readString(params, 'path')
152
207
  const profile = readString(params, 'profile')
153
- const scope = readString(params, 'scope') ?? 'memory'
154
- const dir =
155
- scope === 'root'
156
- ? resolveWorkspaceRoot(api, profile)
157
- : profile
158
- ? resolveMemoryDir(api, profile)
159
- : memoryDir
160
- if (!relativePath) {
161
- respond(false, undefined, {
162
- code: 'invalid_params',
163
- message: 'path (relative path to .md file) is required',
164
- })
165
- return
166
- }
167
- if (!isSafeRelativePath(relativePath)) {
168
- respond(false, undefined, {
169
- code: 'invalid_params',
170
- message: 'path must be a safe relative path (no directory traversal)',
171
- })
172
- return
173
- }
174
- if (!relativePath.toLowerCase().endsWith('.md')) {
175
- respond(false, undefined, {
176
- code: 'invalid_params',
177
- message: 'path must point to a .md file',
178
- })
179
- return
180
- }
181
-
182
- const fullPath = path.join(dir, path.normalize(relativePath))
183
- const realMemory = await fs.realpath(dir).catch(() => dir)
184
- const realResolved = await fs.realpath(fullPath).catch(() => null)
185
-
186
- if (!realResolved) {
187
- respond(false, undefined, {
188
- code: 'not_found',
189
- message: 'file not found',
190
- })
191
- return
192
- }
193
- const rel = path.relative(realMemory, realResolved)
194
- if (rel.startsWith('..') || path.isAbsolute(rel)) {
195
- respond(false, undefined, {
196
- code: 'not_found',
197
- message: 'file not found or outside memory directory',
198
- })
199
- return
200
- }
208
+ const dir = profile ? resolveMemoryDir(api, profile) : memoryDir
209
+ await readMdFile(dir, readString(params, 'path'), api, respond)
210
+ })
201
211
 
212
+ // -----------------------------
213
+ // clawly.workspace.list — workspace root .md files (not shadowed)
214
+ // -----------------------------
215
+ api.registerGatewayMethod('clawly.workspace.list', async ({params, respond}) => {
202
216
  try {
203
- const content = await fs.readFile(fullPath, 'utf-8')
204
- respond(true, {path: relativePath, content})
217
+ const profile = readString(params, 'profile')
218
+ const root = resolveWorkspaceRoot(api, profile)
219
+ const files = await listRootMdFiles(root)
220
+ api.logger.info(`clawly.workspace.list: ${files.length} files in ${root}`)
221
+ respond(true, {files})
205
222
  } catch (err) {
206
- const code = (err as NodeJS.ErrnoException).code
207
- if (code === 'ENOENT') {
208
- respond(false, undefined, {code: 'not_found', message: 'file not found'})
209
- return
210
- }
211
223
  api.logger.error(
212
- `memory-browser.get failed: ${err instanceof Error ? err.message : String(err)}`,
224
+ `clawly.workspace.list failed: ${err instanceof Error ? err.message : String(err)}`,
213
225
  )
214
226
  respond(false, undefined, {
215
227
  code: 'error',
@@ -218,5 +230,14 @@ export function registerMemoryBrowser(api: PluginApi) {
218
230
  }
219
231
  })
220
232
 
221
- api.logger.info(`memory-browser: registered v1.11.0 (dir: ${memoryDir})`)
233
+ // -----------------------------
234
+ // clawly.workspace.get — read a single .md file from workspace root
235
+ // -----------------------------
236
+ api.registerGatewayMethod('clawly.workspace.get', async ({params, respond}) => {
237
+ const profile = readString(params, 'profile')
238
+ const root = resolveWorkspaceRoot(api, profile)
239
+ await readMdFile(root, readString(params, 'path'), api, respond)
240
+ })
241
+
242
+ api.logger.info('memory-browser: registered')
222
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2en/clawly-plugins",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {