@2en/clawly-plugins 1.4.1 → 1.6.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/channel.ts +2 -2
- package/{echo.ts → command/clawly_echo.ts} +1 -1
- package/command/index.ts +6 -0
- package/{agent-send.ts → gateway/agent.ts} +1 -1
- package/gateway/clawhub2gateway.md +68 -0
- package/gateway/clawhub2gateway.ts +405 -0
- package/gateway/index.ts +16 -0
- package/gateway/memory-browser.md +55 -0
- package/gateway/memory.ts +187 -0
- package/{notification.ts → gateway/notification.ts} +1 -1
- package/gateway/plugins.ts +243 -0
- package/{presence.ts → gateway/presence.ts} +3 -7
- package/index.ts +22 -12
- package/lib/lruCache.test.ts +85 -0
- package/lib/lruCache.ts +50 -0
- package/lib/semver.test.ts +88 -0
- package/lib/semver.ts +46 -0
- package/lib/stripCliLogs.test.ts +71 -0
- package/lib/stripCliLogs.ts +25 -0
- package/openclaw.plugin.json +40 -1
- package/package.json +4 -6
- package/tools/clawly-is-user-online.ts +1 -1
- package/tools/clawly-send-app-push.ts +1 -1
- package/tools/index.ts +8 -0
- package/tools.ts +0 -2
package/channel.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import {$} from 'zx'
|
|
11
11
|
import type {PluginApi} from './index'
|
|
12
|
-
import {sendPushNotification} from './notification'
|
|
13
|
-
import {isClientOnline} from './presence'
|
|
12
|
+
import {sendPushNotification} from './gateway/notification'
|
|
13
|
+
import {isClientOnline} from './gateway/presence'
|
|
14
14
|
|
|
15
15
|
$.verbose = false
|
|
16
16
|
|
package/command/index.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# clawhub2gateway
|
|
2
|
+
|
|
3
|
+
OpenClaw plugin that wraps the **ClawHub CLI** as **Gateway WebSocket RPC methods**.
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
|
|
7
|
+
Gateway methods (call via WS RPC):
|
|
8
|
+
|
|
9
|
+
- `clawhub2gateway.search`
|
|
10
|
+
- `clawhub2gateway.explore`
|
|
11
|
+
- `clawhub2gateway.install`
|
|
12
|
+
- `clawhub2gateway.update`
|
|
13
|
+
- `clawhub2gateway.list`
|
|
14
|
+
- `clawhub2gateway.inspect`
|
|
15
|
+
- `clawhub2gateway.star`
|
|
16
|
+
- `clawhub2gateway.unstar`
|
|
17
|
+
|
|
18
|
+
## Install on a gateway host
|
|
19
|
+
|
|
20
|
+
1) Install the ClawHub CLI (must be on PATH for the OpenClaw process):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm i -g clawhub
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2) Put this plugin on the gateway host and make it discoverable. Common options:
|
|
27
|
+
|
|
28
|
+
- **Workspace plugin**: copy this folder to `<workspace>/.openclaw/extensions/clawhub2gateway/`
|
|
29
|
+
with `index.ts` + `openclaw.plugin.json`
|
|
30
|
+
- **Config path**: set `plugins.load.paths` to the plugin folder path
|
|
31
|
+
|
|
32
|
+
3) Enable the plugin in your `openclaw.json`:
|
|
33
|
+
|
|
34
|
+
```json5
|
|
35
|
+
{
|
|
36
|
+
"plugins": {
|
|
37
|
+
"enabled": true,
|
|
38
|
+
"entries": {
|
|
39
|
+
"clawhub2gateway": { "enabled": true, "config": {} }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Plugin config
|
|
46
|
+
|
|
47
|
+
Configured under `plugins.entries.clawhub2gateway.config`:
|
|
48
|
+
|
|
49
|
+
- `bin` (string, default: `"clawhub"`): command to execute
|
|
50
|
+
- `defaultDir` (string, default: `"skills"`): default `--dir`
|
|
51
|
+
- `defaultNoInput` (boolean, default: `true`): default `--no-input`
|
|
52
|
+
- `defaultTimeoutMs` (number, default: `60000`)
|
|
53
|
+
- `configPath` (string, optional): sets `CLAWHUB_CONFIG_PATH`. Defaults to
|
|
54
|
+
`<OPENCLAW_STATE_DIR>/clawhub/config.json` when state dir is known.
|
|
55
|
+
|
|
56
|
+
## Call examples (RPC params)
|
|
57
|
+
|
|
58
|
+
Install:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{ "method": "clawhub2gateway.install", "params": { "slug": "my-skill-pack" } }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Explore:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{ "method": "clawhub2gateway.explore", "params": { "limit": 50, "sort": "trending", "json": true } }
|
|
68
|
+
```
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawHub CLI RPC bridge — expose ClawHub CLI commands as Gateway RPC methods.
|
|
3
|
+
*
|
|
4
|
+
* Gateway methods:
|
|
5
|
+
* - clawhub2gateway.search
|
|
6
|
+
* - clawhub2gateway.install
|
|
7
|
+
* - clawhub2gateway.update
|
|
8
|
+
* - clawhub2gateway.list
|
|
9
|
+
* - clawhub2gateway.explore
|
|
10
|
+
* - clawhub2gateway.inspect
|
|
11
|
+
* - clawhub2gateway.star
|
|
12
|
+
* - clawhub2gateway.unstar
|
|
13
|
+
*
|
|
14
|
+
* Notes:
|
|
15
|
+
* - This plugin shells out to the `clawhub` CLI. Install it on the gateway host:
|
|
16
|
+
* `npm i -g clawhub` (or ensure it is in PATH).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs/promises'
|
|
20
|
+
import path from 'node:path'
|
|
21
|
+
|
|
22
|
+
import type {PluginApi} from '../index'
|
|
23
|
+
|
|
24
|
+
type JsonSchema = Record<string, unknown>
|
|
25
|
+
|
|
26
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
27
|
+
return Boolean(v && typeof v === 'object' && !Array.isArray(v))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readBool(params: Record<string, unknown>, key: string): boolean | undefined {
|
|
31
|
+
return typeof params[key] === 'boolean' ? (params[key] as boolean) : undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readString(params: Record<string, unknown>, key: string): string | undefined {
|
|
35
|
+
const v = params[key]
|
|
36
|
+
if (typeof v !== 'string') return undefined
|
|
37
|
+
const t = v.trim()
|
|
38
|
+
return t ? t : undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readNumber(params: Record<string, unknown>, key: string): number | undefined {
|
|
42
|
+
const v = params[key]
|
|
43
|
+
if (typeof v === 'number' && Number.isFinite(v)) return v
|
|
44
|
+
if (typeof v === 'string' && v.trim()) {
|
|
45
|
+
const n = Number(v.trim())
|
|
46
|
+
if (Number.isFinite(n)) return n
|
|
47
|
+
}
|
|
48
|
+
return undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function coercePluginConfig(api: PluginApi): Record<string, unknown> {
|
|
52
|
+
return isRecord(api.pluginConfig) ? api.pluginConfig : {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function configString(
|
|
56
|
+
cfg: Record<string, unknown>,
|
|
57
|
+
key: string,
|
|
58
|
+
fallback?: string,
|
|
59
|
+
): string | undefined {
|
|
60
|
+
const v = cfg[key]
|
|
61
|
+
if (typeof v === 'string' && v.trim()) return v.trim()
|
|
62
|
+
return fallback
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function configBool(cfg: Record<string, unknown>, key: string, fallback: boolean): boolean {
|
|
66
|
+
const v = cfg[key]
|
|
67
|
+
if (typeof v === 'boolean') return v
|
|
68
|
+
return fallback
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function configNumber(cfg: Record<string, unknown>, key: string, fallback: number): number {
|
|
72
|
+
const v = cfg[key]
|
|
73
|
+
if (typeof v === 'number' && Number.isFinite(v)) return v
|
|
74
|
+
return fallback
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function ensureDir(dirPath: string): Promise<void> {
|
|
78
|
+
await fs.mkdir(dirPath, {recursive: true})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildGlobalCliArgs(
|
|
82
|
+
params: Record<string, unknown>,
|
|
83
|
+
defaults: {dir: string; noInput: boolean},
|
|
84
|
+
) {
|
|
85
|
+
const out: string[] = []
|
|
86
|
+
|
|
87
|
+
const workdir = readString(params, 'workdir')
|
|
88
|
+
const dir = readString(params, 'dir') ?? defaults.dir
|
|
89
|
+
const site = readString(params, 'site')
|
|
90
|
+
const registry = readString(params, 'registry')
|
|
91
|
+
const noInput = readBool(params, 'noInput') ?? defaults.noInput
|
|
92
|
+
|
|
93
|
+
if (workdir) out.push('--workdir', workdir)
|
|
94
|
+
if (dir) out.push('--dir', dir)
|
|
95
|
+
if (site) out.push('--site', site)
|
|
96
|
+
if (registry) out.push('--registry', registry)
|
|
97
|
+
if (noInput) out.push('--no-input')
|
|
98
|
+
|
|
99
|
+
return out
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function runClawhubCommand(opts: {
|
|
103
|
+
api: PluginApi
|
|
104
|
+
argv: string[]
|
|
105
|
+
cwd?: string
|
|
106
|
+
timeoutMs: number
|
|
107
|
+
env?: Record<string, string | undefined>
|
|
108
|
+
}) {
|
|
109
|
+
const runner = opts.api.runtime.system?.runCommandWithTimeout
|
|
110
|
+
if (!runner) {
|
|
111
|
+
throw new Error('Plugin runtime missing system.runCommandWithTimeout')
|
|
112
|
+
}
|
|
113
|
+
const result = await runner(opts.argv, {timeoutMs: opts.timeoutMs, cwd: opts.cwd, env: opts.env})
|
|
114
|
+
return result
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function joinOutput(stdout: string, stderr: string): string {
|
|
118
|
+
const s1 = (stdout ?? '').trimEnd()
|
|
119
|
+
const s2 = (stderr ?? '').trimEnd()
|
|
120
|
+
if (s1 && s2) return `${s1}\n\n${s2}`
|
|
121
|
+
return s1 || s2 || ''
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function baseToolParamsSchema(extra?: JsonSchema): JsonSchema {
|
|
125
|
+
return {
|
|
126
|
+
type: 'object',
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
properties: {
|
|
129
|
+
workdir: {type: 'string', description: 'Working directory (ClawHub --workdir)'},
|
|
130
|
+
dir: {type: 'string', description: 'Skills directory relative to workdir (ClawHub --dir)'},
|
|
131
|
+
site: {type: 'string', description: 'ClawHub site URL (ClawHub --site)'},
|
|
132
|
+
registry: {type: 'string', description: 'ClawHub registry API URL (ClawHub --registry)'},
|
|
133
|
+
noInput: {type: 'boolean', description: 'Disable prompts (ClawHub --no-input)'},
|
|
134
|
+
timeoutMs: {type: 'number', description: 'Command timeout in milliseconds'},
|
|
135
|
+
...(extra ?? {}),
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function registerClawhub2gateway(api: PluginApi) {
|
|
141
|
+
const cfg = coercePluginConfig(api)
|
|
142
|
+
const bin = configString(cfg, 'bin', 'clawhub') ?? 'clawhub'
|
|
143
|
+
const defaultDir = configString(cfg, 'defaultDir', 'skills') ?? 'skills'
|
|
144
|
+
const defaultNoInput = configBool(cfg, 'defaultNoInput', true)
|
|
145
|
+
const defaultTimeoutMs = configNumber(cfg, 'defaultTimeoutMs', 60_000)
|
|
146
|
+
const configPath = configString(cfg, 'configPath')
|
|
147
|
+
|
|
148
|
+
const stateDir =
|
|
149
|
+
api.runtime.state?.resolveStateDir?.(process.env) ??
|
|
150
|
+
process.env.OPENCLAW_STATE_DIR ??
|
|
151
|
+
process.env.CLAWDBOT_STATE_DIR ??
|
|
152
|
+
''
|
|
153
|
+
const defaultConfigPath = stateDir ? path.join(stateDir, 'clawhub', 'config.json') : ''
|
|
154
|
+
|
|
155
|
+
const resolveEnv = async (): Promise<Record<string, string | undefined>> => {
|
|
156
|
+
const env: Record<string, string | undefined> = {}
|
|
157
|
+
const resolved = configPath ?? defaultConfigPath
|
|
158
|
+
if (resolved) {
|
|
159
|
+
await ensureDir(path.dirname(resolved))
|
|
160
|
+
env.CLAWHUB_CONFIG_PATH = resolved
|
|
161
|
+
}
|
|
162
|
+
return env
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const registerRpc = (rpc: {
|
|
166
|
+
method: string
|
|
167
|
+
description: string
|
|
168
|
+
parameters: JsonSchema
|
|
169
|
+
buildCli: (
|
|
170
|
+
params: Record<string, unknown>,
|
|
171
|
+
) => Promise<{argv: string[]; cwd?: string; timeoutMs?: number}>
|
|
172
|
+
}) => {
|
|
173
|
+
api.registerGatewayMethod(rpc.method, async ({params, respond}) => {
|
|
174
|
+
try {
|
|
175
|
+
const timeoutMs = Math.max(
|
|
176
|
+
1_000,
|
|
177
|
+
Math.floor(readNumber(params, 'timeoutMs') ?? defaultTimeoutMs),
|
|
178
|
+
)
|
|
179
|
+
const built = await rpc.buildCli(params)
|
|
180
|
+
const env = await resolveEnv()
|
|
181
|
+
const res = await runClawhubCommand({
|
|
182
|
+
api,
|
|
183
|
+
argv: built.argv,
|
|
184
|
+
cwd: built.cwd,
|
|
185
|
+
timeoutMs: built.timeoutMs ?? timeoutMs,
|
|
186
|
+
env,
|
|
187
|
+
})
|
|
188
|
+
const output = joinOutput(res.stdout, res.stderr)
|
|
189
|
+
if (res.code && res.code !== 0) {
|
|
190
|
+
respond(false, undefined, {
|
|
191
|
+
code: 'command_failed',
|
|
192
|
+
message: output || `clawhub exited with code ${res.code}`,
|
|
193
|
+
})
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
respond(true, {
|
|
197
|
+
ok: true,
|
|
198
|
+
output,
|
|
199
|
+
stdout: res.stdout,
|
|
200
|
+
stderr: res.stderr,
|
|
201
|
+
code: res.code,
|
|
202
|
+
signal: res.signal,
|
|
203
|
+
killed: res.killed,
|
|
204
|
+
argv: built.argv,
|
|
205
|
+
cwd: built.cwd,
|
|
206
|
+
})
|
|
207
|
+
} catch (err) {
|
|
208
|
+
respond(false, undefined, {
|
|
209
|
+
code: 'error',
|
|
210
|
+
message: err instanceof Error ? err.message : String(err),
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// -----------------------------
|
|
217
|
+
// Search
|
|
218
|
+
// -----------------------------
|
|
219
|
+
|
|
220
|
+
registerRpc({
|
|
221
|
+
method: 'clawhub2gateway.search',
|
|
222
|
+
description: 'ClawHub CLI: search skills.',
|
|
223
|
+
parameters: baseToolParamsSchema({
|
|
224
|
+
query: {type: 'string', description: 'Search query'},
|
|
225
|
+
limit: {type: 'number', description: 'Max results (clawhub search --limit)'},
|
|
226
|
+
}),
|
|
227
|
+
async buildCli(params) {
|
|
228
|
+
const query = readString(params, 'query')
|
|
229
|
+
if (!query) throw new Error('query required')
|
|
230
|
+
const limit = readNumber(params, 'limit')
|
|
231
|
+
const globalArgs = buildGlobalCliArgs(params, {dir: defaultDir, noInput: defaultNoInput})
|
|
232
|
+
const argv = [bin, 'search', query, ...globalArgs]
|
|
233
|
+
if (limit !== undefined) argv.push('--limit', String(Math.max(1, Math.floor(limit))))
|
|
234
|
+
return {argv}
|
|
235
|
+
},
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
registerRpc({
|
|
239
|
+
method: 'clawhub2gateway.explore',
|
|
240
|
+
description: 'ClawHub CLI: explore skills.!',
|
|
241
|
+
parameters: baseToolParamsSchema({
|
|
242
|
+
limit: {type: 'number', description: 'Max results (clawhub explore --limit, if supported)'},
|
|
243
|
+
sort: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
enum: ['newest', 'downloads', 'rating', 'installs', 'installsAllTime', 'trending'],
|
|
246
|
+
description:
|
|
247
|
+
'Sort order (clawhub explore --sort). One of newest, downloads, rating, installs, installsAllTime, trending.',
|
|
248
|
+
},
|
|
249
|
+
json: {type: 'boolean', description: 'Output JSON'},
|
|
250
|
+
}),
|
|
251
|
+
async buildCli(params) {
|
|
252
|
+
const limit = readNumber(params, 'limit')
|
|
253
|
+
const sort = readString(params, 'sort')
|
|
254
|
+
const json = readBool(params, 'json') ?? false
|
|
255
|
+
const globalArgs = buildGlobalCliArgs(params, {dir: defaultDir, noInput: defaultNoInput})
|
|
256
|
+
const argv = [bin, 'explore', ...globalArgs]
|
|
257
|
+
if (limit !== undefined) argv.push('--limit', String(Math.max(1, Math.floor(limit))))
|
|
258
|
+
if (sort) argv.push('--sort', sort)
|
|
259
|
+
if (json) argv.push('--json')
|
|
260
|
+
return {argv}
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// -----------------------------
|
|
265
|
+
// Install / Update / List
|
|
266
|
+
// -----------------------------
|
|
267
|
+
|
|
268
|
+
registerRpc({
|
|
269
|
+
method: 'clawhub2gateway.install',
|
|
270
|
+
description: 'ClawHub CLI: install a skill into the workspace skills dir.',
|
|
271
|
+
parameters: baseToolParamsSchema({
|
|
272
|
+
slug: {type: 'string', description: 'Skill slug to install'},
|
|
273
|
+
version: {type: 'string', description: 'Specific version to install'},
|
|
274
|
+
force: {
|
|
275
|
+
type: 'boolean',
|
|
276
|
+
description: 'Overwrite if folder exists (clawhub install --force)',
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
async buildCli(params) {
|
|
280
|
+
const slug = readString(params, 'slug')
|
|
281
|
+
if (!slug) throw new Error('slug required')
|
|
282
|
+
const version = readString(params, 'version')
|
|
283
|
+
const force = readBool(params, 'force') ?? false
|
|
284
|
+
const globalArgs = buildGlobalCliArgs(params, {dir: defaultDir, noInput: defaultNoInput})
|
|
285
|
+
const argv = [bin, 'install', slug, ...globalArgs]
|
|
286
|
+
if (version) argv.push('--version', version)
|
|
287
|
+
if (force) argv.push('--force')
|
|
288
|
+
return {argv}
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
registerRpc({
|
|
293
|
+
method: 'clawhub2gateway.update',
|
|
294
|
+
description: 'ClawHub CLI: update one skill or all installed skills.',
|
|
295
|
+
parameters: baseToolParamsSchema({
|
|
296
|
+
slug: {type: 'string', description: 'Skill slug to update (omit when all=true)'},
|
|
297
|
+
all: {type: 'boolean', description: 'Update all skills (clawhub update --all)'},
|
|
298
|
+
version: {type: 'string', description: 'Update to a specific version (single slug only)'},
|
|
299
|
+
force: {
|
|
300
|
+
type: 'boolean',
|
|
301
|
+
description: 'Overwrite when local files do not match any published version',
|
|
302
|
+
},
|
|
303
|
+
}),
|
|
304
|
+
async buildCli(params) {
|
|
305
|
+
const all = readBool(params, 'all') ?? false
|
|
306
|
+
const slug = readString(params, 'slug')
|
|
307
|
+
const version = readString(params, 'version')
|
|
308
|
+
const force = readBool(params, 'force') ?? false
|
|
309
|
+
const globalArgs = buildGlobalCliArgs(params, {dir: defaultDir, noInput: defaultNoInput})
|
|
310
|
+
|
|
311
|
+
const argv = [bin, 'update', ...globalArgs]
|
|
312
|
+
if (all) {
|
|
313
|
+
argv.push('--all')
|
|
314
|
+
} else {
|
|
315
|
+
if (!slug) throw new Error('slug required when all=false')
|
|
316
|
+
argv.push(slug)
|
|
317
|
+
if (version) argv.push('--version', version)
|
|
318
|
+
}
|
|
319
|
+
if (force) argv.push('--force')
|
|
320
|
+
return {argv}
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
registerRpc({
|
|
325
|
+
method: 'clawhub2gateway.list',
|
|
326
|
+
description: 'ClawHub CLI: list installed skills from .clawhub/lock.json.',
|
|
327
|
+
parameters: baseToolParamsSchema(),
|
|
328
|
+
async buildCli(params) {
|
|
329
|
+
const argv = [
|
|
330
|
+
bin,
|
|
331
|
+
'list',
|
|
332
|
+
...buildGlobalCliArgs(params, {dir: defaultDir, noInput: defaultNoInput}),
|
|
333
|
+
]
|
|
334
|
+
return {argv}
|
|
335
|
+
},
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// -----------------------------
|
|
339
|
+
// Inspect / Star
|
|
340
|
+
// -----------------------------
|
|
341
|
+
|
|
342
|
+
registerRpc({
|
|
343
|
+
method: 'clawhub2gateway.inspect',
|
|
344
|
+
description: 'ClawHub CLI: inspect a skill without installing.',
|
|
345
|
+
parameters: baseToolParamsSchema({
|
|
346
|
+
slug: {type: 'string', description: 'Skill slug'},
|
|
347
|
+
}),
|
|
348
|
+
async buildCli(params) {
|
|
349
|
+
const slug = readString(params, 'slug')
|
|
350
|
+
if (!slug) throw new Error('slug required')
|
|
351
|
+
const globalArgs = buildGlobalCliArgs(params, {dir: defaultDir, noInput: defaultNoInput})
|
|
352
|
+
|
|
353
|
+
const argv = [bin, 'inspect', slug, ...globalArgs]
|
|
354
|
+
return {argv}
|
|
355
|
+
},
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
registerRpc({
|
|
359
|
+
method: 'clawhub2gateway.star',
|
|
360
|
+
description: 'ClawHub CLI: star a skill.',
|
|
361
|
+
parameters: baseToolParamsSchema({
|
|
362
|
+
slug: {type: 'string', description: 'Skill slug'},
|
|
363
|
+
}),
|
|
364
|
+
async buildCli(params) {
|
|
365
|
+
const slug = readString(params, 'slug')
|
|
366
|
+
if (!slug) throw new Error('slug required')
|
|
367
|
+
const argv = [
|
|
368
|
+
bin,
|
|
369
|
+
'star',
|
|
370
|
+
slug,
|
|
371
|
+
...buildGlobalCliArgs(params, {dir: defaultDir, noInput: defaultNoInput}),
|
|
372
|
+
]
|
|
373
|
+
return {argv}
|
|
374
|
+
},
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
registerRpc({
|
|
378
|
+
method: 'clawhub2gateway.unstar',
|
|
379
|
+
description: 'ClawHub CLI: unstar a skill.',
|
|
380
|
+
parameters: baseToolParamsSchema({
|
|
381
|
+
slug: {type: 'string', description: 'Skill slug'},
|
|
382
|
+
}),
|
|
383
|
+
async buildCli(params) {
|
|
384
|
+
const slug = readString(params, 'slug')
|
|
385
|
+
if (!slug) throw new Error('slug required')
|
|
386
|
+
const argv = [
|
|
387
|
+
bin,
|
|
388
|
+
'unstar',
|
|
389
|
+
slug,
|
|
390
|
+
...buildGlobalCliArgs(params, {dir: defaultDir, noInput: defaultNoInput}),
|
|
391
|
+
]
|
|
392
|
+
return {argv}
|
|
393
|
+
},
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
api.logger.info(
|
|
397
|
+
[
|
|
398
|
+
`clawhub2gateway: registered.`,
|
|
399
|
+
`Using clawhub bin: ${bin}`,
|
|
400
|
+
defaultConfigPath
|
|
401
|
+
? `CLI config: ${configPath ?? defaultConfigPath}`
|
|
402
|
+
: 'CLI config: (not set)',
|
|
403
|
+
].join(' '),
|
|
404
|
+
)
|
|
405
|
+
}
|
package/gateway/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {PluginApi} from '../index'
|
|
2
|
+
import {registerAgentSend} from './agent'
|
|
3
|
+
import {registerClawhub2gateway} from './clawhub2gateway'
|
|
4
|
+
import {registerMemoryBrowser} from './memory'
|
|
5
|
+
import {registerNotification} from './notification'
|
|
6
|
+
import {registerPlugins} from './plugins'
|
|
7
|
+
import {registerPresence} from './presence'
|
|
8
|
+
|
|
9
|
+
export function registerGateway(api: PluginApi) {
|
|
10
|
+
registerPresence(api)
|
|
11
|
+
registerNotification(api)
|
|
12
|
+
registerAgentSend(api)
|
|
13
|
+
registerMemoryBrowser(api)
|
|
14
|
+
registerClawhub2gateway(api)
|
|
15
|
+
registerPlugins(api)
|
|
16
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# memory-browser
|
|
2
|
+
|
|
3
|
+
OpenClaw plugin that exposes the **memory directory** (all `.md` files) as **Gateway WebSocket RPC methods**.
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
|
|
7
|
+
Gateway methods (call via WS RPC):
|
|
8
|
+
|
|
9
|
+
- **`memory-browser.list`** — List all `.md` files in the memory directory (recursive). Returns `{ files: string[] }` (relative paths).
|
|
10
|
+
- **`memory-browser.get`** — Return the content of a single `.md` file. Params: `{ path: string }` (relative path, e.g. `"notes/foo.md"`). Returns `{ path: string, content: string }`.
|
|
11
|
+
|
|
12
|
+
## Memory directory
|
|
13
|
+
|
|
14
|
+
The memory directory is resolved in this order:
|
|
15
|
+
|
|
16
|
+
1. Plugin config `memoryDir` (absolute path)
|
|
17
|
+
2. `<OPENCLAW_STATE_DIR>/memory` (or `<CLAWDBOT_STATE_DIR>/memory`)
|
|
18
|
+
3. `<process.cwd()>/memory`
|
|
19
|
+
|
|
20
|
+
## Install on a gateway host
|
|
21
|
+
|
|
22
|
+
1. Put this plugin on the gateway host and make it discoverable, e.g.:
|
|
23
|
+
- Workspace: `<workspace>/.openclaw/extensions/memory-browser/` with `index.ts` + `openclaw.plugin.json`
|
|
24
|
+
- Or set `plugins.load.paths` to the plugin folder
|
|
25
|
+
|
|
26
|
+
2. Enable the plugin in `openclaw.json`:
|
|
27
|
+
|
|
28
|
+
```json5
|
|
29
|
+
{
|
|
30
|
+
"plugins": {
|
|
31
|
+
"enabled": true,
|
|
32
|
+
"entries": {
|
|
33
|
+
"memory-browser": { "enabled": true, "config": {} }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Optional config: `config.memoryDir` to override the memory directory path.
|
|
40
|
+
|
|
41
|
+
## Call examples (RPC params)
|
|
42
|
+
|
|
43
|
+
List all `.md` files:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{ "method": "memory-browser.list", "params": {} }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Get one file:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{ "method": "memory-browser.get", "params": { "path": "notes/meeting-2024.md" } }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`path` must be a relative path to a `.md` file inside the memory directory; directory traversal (`..`) is rejected.
|