@2en/clawly-plugins 1.4.1 → 1.5.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 +14 -0
- package/gateway/memory-browser.md +55 -0
- package/gateway/memory.ts +187 -0
- package/{notification.ts → gateway/notification.ts} +1 -1
- package/{presence.ts → gateway/presence.ts} +1 -1
- package/index.ts +22 -12
- package/openclaw.plugin.json +40 -1
- package/package.json +3 -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,14 @@
|
|
|
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 {registerPresence} from './presence'
|
|
7
|
+
|
|
8
|
+
export function registerGateway(api: PluginApi) {
|
|
9
|
+
registerPresence(api)
|
|
10
|
+
registerNotification(api)
|
|
11
|
+
registerAgentSend(api)
|
|
12
|
+
registerMemoryBrowser(api)
|
|
13
|
+
registerClawhub2gateway(api)
|
|
14
|
+
}
|
|
@@ -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.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory directory browser — expose memory directory .md files as Gateway RPC methods.
|
|
3
|
+
*
|
|
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
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs/promises'
|
|
10
|
+
import os from 'node:os'
|
|
11
|
+
import path from 'node:path'
|
|
12
|
+
|
|
13
|
+
import type {PluginApi} from '../index'
|
|
14
|
+
|
|
15
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
16
|
+
return Boolean(v && typeof v === 'object' && !Array.isArray(v))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function configString(
|
|
20
|
+
cfg: Record<string, unknown>,
|
|
21
|
+
key: string,
|
|
22
|
+
fallback?: string,
|
|
23
|
+
): string | undefined {
|
|
24
|
+
const v = cfg[key]
|
|
25
|
+
if (typeof v === 'string' && v.trim()) return v.trim()
|
|
26
|
+
return fallback
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readString(params: Record<string, unknown>, key: string): string | undefined {
|
|
30
|
+
const v = params[key]
|
|
31
|
+
if (typeof v !== 'string') return undefined
|
|
32
|
+
const t = v.trim()
|
|
33
|
+
return t ? t : undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function coercePluginConfig(api: PluginApi): Record<string, unknown> {
|
|
37
|
+
return isRecord(api.pluginConfig) ? api.pluginConfig : {}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Resolve the memory directory path. Memory lives under workspace (agents.defaults.workspace). */
|
|
41
|
+
function resolveMemoryDir(api: PluginApi, profile?: string): string {
|
|
42
|
+
const cfg = coercePluginConfig(api)
|
|
43
|
+
const configPath = configString(cfg, 'memoryDir')
|
|
44
|
+
if (configPath) return configPath
|
|
45
|
+
|
|
46
|
+
const baseDir =
|
|
47
|
+
process.env.OPENCLAW_WORKSPACE ?? path.join(os.homedir(), '.openclaw', 'workspace')
|
|
48
|
+
// Profile-aware: "main" or empty → default workspace, otherwise workspace-<profile>
|
|
49
|
+
if (profile && profile !== 'main') {
|
|
50
|
+
const parentDir = path.dirname(baseDir)
|
|
51
|
+
const baseName = path.basename(baseDir)
|
|
52
|
+
return path.join(parentDir, `${baseName}-${profile}`, 'memory')
|
|
53
|
+
}
|
|
54
|
+
return path.join(baseDir, 'memory')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Check that a relative path is safe (no directory traversal). */
|
|
58
|
+
function isSafeRelativePath(relativePath: string): boolean {
|
|
59
|
+
if (!relativePath || relativePath.startsWith('/') || relativePath.includes('\\')) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
const normalized = path.normalize(relativePath)
|
|
63
|
+
if (normalized.startsWith('..') || normalized.includes('..')) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Recursively collect all .md file paths under dir, relative to baseDir. */
|
|
70
|
+
async function collectMdFiles(dir: string, baseDir: string, acc: string[] = []): Promise<string[]> {
|
|
71
|
+
let entries: fs.Dirent[]
|
|
72
|
+
try {
|
|
73
|
+
entries = await fs.readdir(dir, {withFileTypes: true})
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return acc
|
|
76
|
+
throw err
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const e of entries) {
|
|
80
|
+
const full = path.join(dir, e.name)
|
|
81
|
+
const rel = path.relative(baseDir, full)
|
|
82
|
+
|
|
83
|
+
if (e.isDirectory()) {
|
|
84
|
+
await collectMdFiles(full, baseDir, acc)
|
|
85
|
+
} else if (e.isFile() && e.name.toLowerCase().endsWith('.md')) {
|
|
86
|
+
acc.push(rel)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return acc
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function registerMemoryBrowser(api: PluginApi) {
|
|
93
|
+
const memoryDir = resolveMemoryDir(api)
|
|
94
|
+
api.logger.info(`memory-browser: memory directory: ${memoryDir}`)
|
|
95
|
+
|
|
96
|
+
// -----------------------------
|
|
97
|
+
// memory-browser.list
|
|
98
|
+
// -----------------------------
|
|
99
|
+
api.registerGatewayMethod('memory-browser.list', async ({params, respond}) => {
|
|
100
|
+
try {
|
|
101
|
+
const profile = readString(params, 'profile')
|
|
102
|
+
const dir = profile ? resolveMemoryDir(api, profile) : memoryDir
|
|
103
|
+
const files = await collectMdFiles(dir, dir)
|
|
104
|
+
api.logger.info(`memory-browser.list: ${files.length} files found`)
|
|
105
|
+
files.sort()
|
|
106
|
+
respond(true, {files})
|
|
107
|
+
} catch (err) {
|
|
108
|
+
api.logger.error(
|
|
109
|
+
`memory-browser.list failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
110
|
+
)
|
|
111
|
+
respond(false, undefined, {
|
|
112
|
+
code: 'error',
|
|
113
|
+
message: err instanceof Error ? err.message : String(err),
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// -----------------------------
|
|
119
|
+
// memory-browser.get
|
|
120
|
+
// -----------------------------
|
|
121
|
+
api.registerGatewayMethod('memory-browser.get', async ({params, respond}) => {
|
|
122
|
+
const relativePath = readString(params, 'path')
|
|
123
|
+
const profile = readString(params, 'profile')
|
|
124
|
+
const dir = profile ? resolveMemoryDir(api, profile) : memoryDir
|
|
125
|
+
if (!relativePath) {
|
|
126
|
+
respond(false, undefined, {
|
|
127
|
+
code: 'invalid_params',
|
|
128
|
+
message: 'path (relative path to .md file) is required',
|
|
129
|
+
})
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (!isSafeRelativePath(relativePath)) {
|
|
133
|
+
respond(false, undefined, {
|
|
134
|
+
code: 'invalid_params',
|
|
135
|
+
message: 'path must be a safe relative path (no directory traversal)',
|
|
136
|
+
})
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
if (!relativePath.toLowerCase().endsWith('.md')) {
|
|
140
|
+
respond(false, undefined, {
|
|
141
|
+
code: 'invalid_params',
|
|
142
|
+
message: 'path must point to a .md file',
|
|
143
|
+
})
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const fullPath = path.join(dir, path.normalize(relativePath))
|
|
148
|
+
const realMemory = await fs.realpath(dir).catch(() => dir)
|
|
149
|
+
const realResolved = await fs.realpath(fullPath).catch(() => null)
|
|
150
|
+
|
|
151
|
+
if (!realResolved) {
|
|
152
|
+
respond(false, undefined, {
|
|
153
|
+
code: 'not_found',
|
|
154
|
+
message: 'file not found',
|
|
155
|
+
})
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
const rel = path.relative(realMemory, realResolved)
|
|
159
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
160
|
+
respond(false, undefined, {
|
|
161
|
+
code: 'not_found',
|
|
162
|
+
message: 'file not found or outside memory directory',
|
|
163
|
+
})
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const content = await fs.readFile(fullPath, 'utf-8')
|
|
169
|
+
respond(true, {path: relativePath, content})
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const code = (err as NodeJS.ErrnoException).code
|
|
172
|
+
if (code === 'ENOENT') {
|
|
173
|
+
respond(false, undefined, {code: 'not_found', message: 'file not found'})
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
api.logger.error(
|
|
177
|
+
`memory-browser.get failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
178
|
+
)
|
|
179
|
+
respond(false, undefined, {
|
|
180
|
+
code: 'error',
|
|
181
|
+
message: err instanceof Error ? err.message : String(err),
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
api.logger.info(`memory-browser: registered (dir: ${memoryDir})`)
|
|
187
|
+
}
|
|
@@ -13,7 +13,7 @@ import fs from 'node:fs'
|
|
|
13
13
|
import os from 'node:os'
|
|
14
14
|
import path from 'node:path'
|
|
15
15
|
|
|
16
|
-
import type {PluginApi} from '
|
|
16
|
+
import type {PluginApi} from '../index'
|
|
17
17
|
|
|
18
18
|
const TOKEN_DIR = path.join(os.homedir(), '.openclaw', 'clawly')
|
|
19
19
|
const TOKEN_FILE = path.join(TOKEN_DIR, 'expo-push-token.json')
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenClaw plugin: Clawly utility RPC methods (clawly.*).
|
|
2
|
+
* OpenClaw plugin: Clawly utility RPC methods (clawly.*), memory browser, and ClawHub CLI bridge.
|
|
3
3
|
*
|
|
4
4
|
* Gateway methods:
|
|
5
5
|
* - clawly.file.getOutbound — read a persisted outbound file by original-path hash
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* - clawly.notification.send — send a push notification directly
|
|
9
9
|
* - clawly.agent.send — send a message to the agent (+ optional push)
|
|
10
10
|
* - clawly.agent.echo — echo-wrapped agent message (bypasses LLM)
|
|
11
|
+
* - memory-browser.list — list all .md files in the memory directory
|
|
12
|
+
* - memory-browser.get — return content of a single .md file
|
|
13
|
+
* - clawhub2gateway.* — ClawHub CLI RPC bridge (search/install/update/list/explore/inspect/star/unstar)
|
|
11
14
|
*
|
|
12
15
|
* Agent tools:
|
|
13
16
|
* - clawly_is_user_online — check if user's device is connected
|
|
@@ -21,19 +24,29 @@
|
|
|
21
24
|
* - before_tool_call — enforces delivery fields on cron.create
|
|
22
25
|
*/
|
|
23
26
|
|
|
24
|
-
import {registerAgentSend} from './agent-send'
|
|
25
27
|
import {registerCalendar} from './calendar'
|
|
26
|
-
import {registerIsUserOnlineTool, registerSendAppPushTool} from './tools'
|
|
27
28
|
import {registerClawlyCronChannel} from './channel'
|
|
29
|
+
import {registerCommands} from './command'
|
|
28
30
|
import {registerCronHook} from './cron-hook'
|
|
29
|
-
import {registerEchoCommand} from './echo'
|
|
30
31
|
import {registerEmail} from './email'
|
|
32
|
+
import {registerGateway} from './gateway'
|
|
31
33
|
import {getGatewayConfig} from './gateway-fetch'
|
|
32
|
-
import {registerNotification} from './notification'
|
|
33
34
|
import {registerOutboundHook, registerOutboundMethods} from './outbound'
|
|
34
|
-
import {
|
|
35
|
+
import {registerTools} from './tools'
|
|
35
36
|
|
|
36
37
|
type PluginRuntime = {
|
|
38
|
+
system?: {
|
|
39
|
+
runCommandWithTimeout?: (
|
|
40
|
+
argv: string[],
|
|
41
|
+
opts: {timeoutMs: number; cwd?: string; env?: Record<string, string | undefined>},
|
|
42
|
+
) => Promise<{
|
|
43
|
+
stdout: string
|
|
44
|
+
stderr: string
|
|
45
|
+
code: number | null
|
|
46
|
+
signal: string | null
|
|
47
|
+
killed: boolean
|
|
48
|
+
}>
|
|
49
|
+
}
|
|
37
50
|
state?: {
|
|
38
51
|
resolveStateDir?: (env?: NodeJS.ProcessEnv) => string
|
|
39
52
|
}
|
|
@@ -86,14 +99,11 @@ export default {
|
|
|
86
99
|
register(api: PluginApi) {
|
|
87
100
|
registerOutboundHook(api)
|
|
88
101
|
registerOutboundMethods(api)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
registerNotification(api)
|
|
92
|
-
registerAgentSend(api)
|
|
93
|
-
registerIsUserOnlineTool(api)
|
|
94
|
-
registerSendAppPushTool(api)
|
|
102
|
+
registerCommands(api)
|
|
103
|
+
registerTools(api)
|
|
95
104
|
registerClawlyCronChannel(api)
|
|
96
105
|
registerCronHook(api)
|
|
106
|
+
registerGateway(api)
|
|
97
107
|
|
|
98
108
|
// Email & calendar (optional — requires gatewayBaseUrl + gatewayToken in config)
|
|
99
109
|
const gw = getGatewayConfig(api)
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,12 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "clawly-plugins",
|
|
3
3
|
"name": "Clawly Plugins",
|
|
4
|
-
"description": "Clawly utility RPC methods (clawly.*): file access, presence, push notifications, and
|
|
4
|
+
"description": "Clawly utility RPC methods (clawly.*): file access, presence, push notifications, agent messaging, memory browser, and ClawHub CLI bridge.",
|
|
5
5
|
"version": "0.2.0",
|
|
6
|
+
"uiHints": {
|
|
7
|
+
"memoryDir": {
|
|
8
|
+
"label": "Memory directory",
|
|
9
|
+
"help": "Absolute path to the memory directory containing .md files. Defaults to <OPENCLAW_WORKSPACE>/memory.",
|
|
10
|
+
"placeholder": "/data/memory"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"label": "clawhub binary",
|
|
14
|
+
"help": "Command to run (must be in PATH on the gateway host).",
|
|
15
|
+
"placeholder": "clawhub"
|
|
16
|
+
},
|
|
17
|
+
"defaultDir": {
|
|
18
|
+
"label": "Default skills dir",
|
|
19
|
+
"help": "Maps to `clawhub --dir` when not provided per-call.",
|
|
20
|
+
"placeholder": "skills"
|
|
21
|
+
},
|
|
22
|
+
"defaultNoInput": {
|
|
23
|
+
"label": "Default no-input",
|
|
24
|
+
"help": "If true, adds `--no-input` unless the tool call overrides it.",
|
|
25
|
+
"advanced": true
|
|
26
|
+
},
|
|
27
|
+
"defaultTimeoutMs": {
|
|
28
|
+
"label": "Default timeout (ms)",
|
|
29
|
+
"help": "Default command timeout for most tool calls.",
|
|
30
|
+
"advanced": true
|
|
31
|
+
},
|
|
32
|
+
"configPath": {
|
|
33
|
+
"label": "CLAWHUB_CONFIG_PATH",
|
|
34
|
+
"help": "Where the ClawHub CLI stores auth/config. Defaults to `<OPENCLAW_STATE_DIR>/clawhub/config.json`.",
|
|
35
|
+
"advanced": true,
|
|
36
|
+
"placeholder": "~/.openclaw/clawhub/config.json"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
6
39
|
"configSchema": {
|
|
7
40
|
"type": "object",
|
|
8
41
|
"additionalProperties": false,
|
|
9
42
|
"properties": {
|
|
43
|
+
"memoryDir": { "type": "string", "minLength": 1 },
|
|
44
|
+
"bin": { "type": "string", "minLength": 1 },
|
|
45
|
+
"defaultDir": { "type": "string", "minLength": 1 },
|
|
46
|
+
"defaultNoInput": { "type": "boolean" },
|
|
47
|
+
"defaultTimeoutMs": { "type": "number", "minimum": 1000 },
|
|
48
|
+
"configPath": { "type": "string" },
|
|
10
49
|
"gatewayBaseUrl": { "type": "string" },
|
|
11
50
|
"gatewayToken": { "type": "string" }
|
|
12
51
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2en/clawly-plugins",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"zx": "npm:zx@8.8.5-lite"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
+
"command",
|
|
16
|
+
"gateway",
|
|
15
17
|
"tools",
|
|
16
18
|
"index.ts",
|
|
17
19
|
"calendar.ts",
|
|
@@ -20,11 +22,6 @@
|
|
|
20
22
|
"email.ts",
|
|
21
23
|
"gateway-fetch.ts",
|
|
22
24
|
"outbound.ts",
|
|
23
|
-
"echo.ts",
|
|
24
|
-
"presence.ts",
|
|
25
|
-
"notification.ts",
|
|
26
|
-
"agent-send.ts",
|
|
27
|
-
"tools.ts",
|
|
28
25
|
"openclaw.plugin.json"
|
|
29
26
|
],
|
|
30
27
|
"publishConfig": {
|
package/tools/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type {PluginApi} from '../index'
|
|
2
|
+
import {registerIsUserOnlineTool} from './clawly-is-user-online'
|
|
3
|
+
import {registerSendAppPushTool} from './clawly-send-app-push'
|
|
4
|
+
|
|
5
|
+
export function registerTools(api: PluginApi) {
|
|
6
|
+
registerIsUserOnlineTool(api)
|
|
7
|
+
registerSendAppPushTool(api)
|
|
8
|
+
}
|
package/tools.ts
DELETED