openclaw-cloudflare-plugin 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +222 -0
- package/openclaw.plugin.json +19 -0
- package/package.json +47 -0
- package/skills/cloudflare-kv/SKILL.md +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# openclaw-cloudflare-plugin
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
Cloudflare API wrapper plugin for **OpenClaw**, built on the Cloudflare TypeScript SDK.
|
|
10
|
+
|
|
11
|
+
This plugin allows OpenClaw to interact with your Cloudflare account via chat commands.
|
|
12
|
+
|
|
13
|
+
## Features (Current)
|
|
14
|
+
|
|
15
|
+
Currently supported:
|
|
16
|
+
|
|
17
|
+
* **KV**
|
|
18
|
+
|
|
19
|
+
* List KV namespaces
|
|
20
|
+
|
|
21
|
+
> More features such as D1 and Workers management will be added in the future.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Installation
|
|
26
|
+
|
|
27
|
+
You need a **Cloudflare API Token** before installing.
|
|
28
|
+
|
|
29
|
+
Create one here:
|
|
30
|
+
https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Step 1 — Install Plugin
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
openclaw install openclaw-cloudflare-plugin
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Step 2 — Login with Cloudflare Token
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
openclaw cloudflare login --token <YOUR_API_TOKEN>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
openclaw cloudflare login --token abc123
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Step 3 — Verify Login (Optional)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
openclaw cloudflare status
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
# Usage
|
|
65
|
+
|
|
66
|
+
1. Configure a **chat channel** supported by OpenClaw
|
|
67
|
+
Examples:
|
|
68
|
+
|
|
69
|
+
* Discord
|
|
70
|
+
* Telegram
|
|
71
|
+
* Wecom Bot (企业微信机器人)
|
|
72
|
+
|
|
73
|
+
2. Send a command in your chat channel
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Please show my Cloudflare KV list
|
|
79
|
+
And:
|
|
80
|
+
Get top 5 keys in first namespace
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
3. OpenClaw will query Cloudflare and return your KV namespaces.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
# Roadmap
|
|
88
|
+
|
|
89
|
+
Planned features:
|
|
90
|
+
|
|
91
|
+
* KV CRUD
|
|
92
|
+
* D1 database operations
|
|
93
|
+
* Workers management
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
# License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import Cloudflare from "cloudflare";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
//#region src/client.ts
|
|
4
|
+
let clientCache = null;
|
|
5
|
+
let lastApiToken = null;
|
|
6
|
+
let resolvedAccountId = null;
|
|
7
|
+
function getCloudflareClient(apiToken) {
|
|
8
|
+
if (clientCache && lastApiToken === apiToken) return clientCache;
|
|
9
|
+
clientCache = new Cloudflare({ apiToken });
|
|
10
|
+
lastApiToken = apiToken;
|
|
11
|
+
resolvedAccountId = null;
|
|
12
|
+
return clientCache;
|
|
13
|
+
}
|
|
14
|
+
async function getOrResolveAccountId(client, configuredAccountId) {
|
|
15
|
+
if (configuredAccountId) return configuredAccountId;
|
|
16
|
+
if (resolvedAccountId) return resolvedAccountId;
|
|
17
|
+
const firstAccount = (await client.accounts.list()).result?.[0];
|
|
18
|
+
if (!firstAccount?.id) throw new Error("No Cloudflare accounts found for this token.");
|
|
19
|
+
resolvedAccountId = firstAccount.id;
|
|
20
|
+
return resolvedAccountId;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/probe.ts
|
|
24
|
+
async function probeCloudflare(apiToken, configuredAccountId) {
|
|
25
|
+
if (!apiToken) return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: "Missing API Token"
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
const client = getCloudflareClient(apiToken);
|
|
31
|
+
const verify = await client.user.tokens.verify();
|
|
32
|
+
if (verify.status !== "active") return {
|
|
33
|
+
ok: false,
|
|
34
|
+
error: `Token status: ${verify.status}`
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
ok: true,
|
|
38
|
+
accountId: await getOrResolveAccountId(client, configuredAccountId)
|
|
39
|
+
};
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
error: err instanceof Error ? err.message : String(err)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/config.ts
|
|
49
|
+
const PLUGIN_NAME = "openclaw-cloudflare-plugin";
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/cli.ts
|
|
52
|
+
function registerCloudflareCli(api, program) {
|
|
53
|
+
const cf = program.command("cloudflare").description("Cloudflare plugin management");
|
|
54
|
+
cf.command("login").description("Save Cloudflare API Token to configuration").option("--token <token>", "Cloudflare API Token").action(async (options) => {
|
|
55
|
+
const token = options.token;
|
|
56
|
+
if (!token || token.trim() === "") {
|
|
57
|
+
api.logger.error("please input token.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const probe = await probeCloudflare(token.trim(), options.account);
|
|
61
|
+
if (!probe.ok) {
|
|
62
|
+
api.logger.error(`❌ Error: ${probe.error}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
api.logger.info(`✅ Token is active. Auto-detected Account ID: ${probe.accountId}`);
|
|
66
|
+
if (!options.account) {}
|
|
67
|
+
const currentConfig = await api.runtime.config.loadConfig();
|
|
68
|
+
const nextConfig = {
|
|
69
|
+
...currentConfig,
|
|
70
|
+
plugins: {
|
|
71
|
+
...currentConfig.plugins,
|
|
72
|
+
entries: {
|
|
73
|
+
...currentConfig.plugins?.entries,
|
|
74
|
+
[PLUGIN_NAME]: {
|
|
75
|
+
enabled: true,
|
|
76
|
+
config: {
|
|
77
|
+
...currentConfig.plugins?.entries?.["openclaw-cloudflare-plugin"]?.config || {},
|
|
78
|
+
apiToken: token.trim(),
|
|
79
|
+
accountId: probe.accountId?.trim() || void 0
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
await api.runtime.config.writeConfigFile(nextConfig);
|
|
86
|
+
api.logger.info("✅ Cloudflare configuration has been saved.");
|
|
87
|
+
});
|
|
88
|
+
cf.command("status").description("Check Cloudflare configuration status").action(async () => {
|
|
89
|
+
const cfg = api.runtime.config.loadConfig().plugins?.entries?.[PLUGIN_NAME]?.config;
|
|
90
|
+
if (!cfg || !cfg.apiToken) {
|
|
91
|
+
api.logger.error("❌ Cloudflare is not configured. Run 'openclaw cloudflare login' first.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const probe = await probeCloudflare(cfg.apiToken);
|
|
95
|
+
if (probe.ok) {
|
|
96
|
+
api.logger.info("✅ Cloudflare is configured and active.");
|
|
97
|
+
api.logger.info(` Account ID: ${probe.accountId}`);
|
|
98
|
+
} else api.logger.error(`❌ Cloudflare configuration error: ${probe.error}`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/kv.ts
|
|
103
|
+
const ListKvNamespacesSchema = Type.Object({
|
|
104
|
+
page: Type.Optional(Type.Number({
|
|
105
|
+
description: "Page number of paginated results",
|
|
106
|
+
default: 1
|
|
107
|
+
})),
|
|
108
|
+
per_page: Type.Optional(Type.Number({
|
|
109
|
+
description: "Number of namespaces per page",
|
|
110
|
+
default: 10,
|
|
111
|
+
maximum: 50
|
|
112
|
+
}))
|
|
113
|
+
});
|
|
114
|
+
const ListKvKeysSchema = Type.Object({
|
|
115
|
+
namespace_id: Type.String({ description: "Cloudflare KV Namespace ID" }),
|
|
116
|
+
limit: Type.Optional(Type.Number({
|
|
117
|
+
description: "Maximum number of keys to return",
|
|
118
|
+
default: 10,
|
|
119
|
+
maximum: 50
|
|
120
|
+
})),
|
|
121
|
+
prefix: Type.Optional(Type.String({ description: "Filter keys by prefix" })),
|
|
122
|
+
cursor: Type.Optional(Type.String({ description: "Pagination cursor" }))
|
|
123
|
+
});
|
|
124
|
+
const GetKvValueSchema = Type.Object({
|
|
125
|
+
namespace_id: Type.String({ description: "Cloudflare KV Namespace ID" }),
|
|
126
|
+
key: Type.String({ description: "Key name" })
|
|
127
|
+
});
|
|
128
|
+
function json(data) {
|
|
129
|
+
return {
|
|
130
|
+
content: [{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: JSON.stringify(data, null, 2)
|
|
133
|
+
}],
|
|
134
|
+
details: data
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function registerCloudflareKvTools(api) {
|
|
138
|
+
const getClientAndAccount = async () => {
|
|
139
|
+
const { apiToken, accountId: configuredAccountId } = api.config.plugins?.entries?.["openclaw-cloudflare-plugin"]?.config || {};
|
|
140
|
+
if (!apiToken) {
|
|
141
|
+
api.logger.error?.("Cloudflare apiToken not configured");
|
|
142
|
+
throw new Error("Cloudflare apiToken not configured");
|
|
143
|
+
}
|
|
144
|
+
const client = getCloudflareClient(apiToken);
|
|
145
|
+
return {
|
|
146
|
+
client,
|
|
147
|
+
accountId: await getOrResolveAccountId(client, configuredAccountId)
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
api.registerTool({
|
|
151
|
+
name: "cloudflare_kv_list_namespaces",
|
|
152
|
+
label: "Cloudflare List KV Namespaces",
|
|
153
|
+
description: "List all KV namespaces in the configured Cloudflare account",
|
|
154
|
+
parameters: ListKvNamespacesSchema,
|
|
155
|
+
async execute(_toolCallId, params) {
|
|
156
|
+
try {
|
|
157
|
+
const { client, accountId } = await getClientAndAccount();
|
|
158
|
+
return json(await client.kv.namespaces.list({
|
|
159
|
+
account_id: accountId,
|
|
160
|
+
page: params.page,
|
|
161
|
+
per_page: params.per_page
|
|
162
|
+
}));
|
|
163
|
+
} catch (err) {
|
|
164
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}, { name: "cloudflare_kv_list_namespaces" });
|
|
168
|
+
api.registerTool({
|
|
169
|
+
name: "cloudflare_kv_list_keys",
|
|
170
|
+
label: "Cloudflare List KV Keys",
|
|
171
|
+
description: "List keys in a specific Cloudflare KV namespace",
|
|
172
|
+
parameters: ListKvKeysSchema,
|
|
173
|
+
async execute(_toolCallId, params) {
|
|
174
|
+
try {
|
|
175
|
+
const { client, accountId } = await getClientAndAccount();
|
|
176
|
+
return json(await client.kv.namespaces.keys.list(params.namespace_id, {
|
|
177
|
+
account_id: accountId,
|
|
178
|
+
limit: params.limit,
|
|
179
|
+
prefix: params.prefix,
|
|
180
|
+
cursor: params.cursor
|
|
181
|
+
}));
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}, { name: "cloudflare_kv_list_keys" });
|
|
187
|
+
api.registerTool({
|
|
188
|
+
name: "cloudflare_kv_get_value",
|
|
189
|
+
label: "Cloudflare Get KV Value",
|
|
190
|
+
description: "Get the value of a specific key in a Cloudflare KV namespace",
|
|
191
|
+
parameters: GetKvValueSchema,
|
|
192
|
+
async execute(_toolCallId, params) {
|
|
193
|
+
try {
|
|
194
|
+
const { client, accountId } = await getClientAndAccount();
|
|
195
|
+
const res = await client.kv.namespaces.values.get(params.namespace_id, params.key, { account_id: accountId });
|
|
196
|
+
const value = typeof res === "string" ? res : await res.text();
|
|
197
|
+
return json({
|
|
198
|
+
key: params.key,
|
|
199
|
+
value
|
|
200
|
+
});
|
|
201
|
+
} catch (err) {
|
|
202
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}, { name: "cloudflare_kv_get_value" });
|
|
206
|
+
api.logger.info?.("Cloudflare tools registered");
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region index.ts
|
|
210
|
+
const plugin = {
|
|
211
|
+
id: "openclaw-cloudflare-plugin",
|
|
212
|
+
name: "openclaw-cloudflare-plugin",
|
|
213
|
+
description: "Cloudflare wrapper for OpenClaw (KV, Workers, etc.)",
|
|
214
|
+
register(api) {
|
|
215
|
+
registerCloudflareKvTools(api);
|
|
216
|
+
api.registerCli(({ program }) => {
|
|
217
|
+
registerCloudflareCli(api, program);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
//#endregion
|
|
222
|
+
export { plugin as default };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "openclaw-cloudflare-plugin",
|
|
3
|
+
"name": "openclaw-cloudflare-plugin",
|
|
4
|
+
"skills": ["./skills"],
|
|
5
|
+
"configSchema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"apiToken": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Cloudflare API token"
|
|
12
|
+
},
|
|
13
|
+
"accountId": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Cloudflare account ID"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclaw-cloudflare-plugin",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Cloudflare wrapper for OpenClaw (KV, Workers, etc.)",
|
|
5
|
+
"author": "tardis",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.mjs",
|
|
8
|
+
"bin": {
|
|
9
|
+
"cf": "./dist/cli.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"openclaw.plugin.json",
|
|
14
|
+
"skills"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsdown",
|
|
18
|
+
"dev": "tsdown --watch",
|
|
19
|
+
"cli": "tsx cli.ts",
|
|
20
|
+
"publish": "pnpm run build && bumpp",
|
|
21
|
+
"lint": "biome lint .",
|
|
22
|
+
"format": "biome format . --write",
|
|
23
|
+
"check": "biome check --write ."
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"cloudflare": "^4.0.0",
|
|
27
|
+
"typebox": "^1.1.6",
|
|
28
|
+
"zod": "^4.3.6"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"commander": "^14.0.3",
|
|
32
|
+
"openclaw": "^2026.3.8"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@biomejs/biome": "^2.4.6",
|
|
36
|
+
"bumpp": "^10.4.1",
|
|
37
|
+
"cac": "^7.0.0",
|
|
38
|
+
"tsdown": "0.21.2",
|
|
39
|
+
"tsx": "^4.21.0",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"openclaw": {
|
|
43
|
+
"extensions": [
|
|
44
|
+
"./dist/index.mjs"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cloudflare-kv
|
|
3
|
+
description: |
|
|
4
|
+
Cloudflare KV (Key-Value) storage management. Use this skill to list namespaces, keys, and retrieve values from Cloudflare.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cloudflare KV Tool
|
|
8
|
+
|
|
9
|
+
Use the following tools to manage Cloudflare KV storage.
|
|
10
|
+
|
|
11
|
+
## Actions
|
|
12
|
+
|
|
13
|
+
### List Namespaces
|
|
14
|
+
List all available KV namespaces. Use this to find the `namespace_id` for other tools.
|
|
15
|
+
`cloudflare_kv_list_namespaces { "page": 1, "per_page": 20 }`
|
|
16
|
+
|
|
17
|
+
### List Keys
|
|
18
|
+
List keys within a specific namespace.
|
|
19
|
+
`cloudflare_kv_list_keys { "namespace_id": "YOUR_NAMESPACE_ID", "limit": 10 }`
|
|
20
|
+
|
|
21
|
+
### Get Value
|
|
22
|
+
Retrieve the text value of a specific key.
|
|
23
|
+
`cloudflare_kv_get_value { "namespace_id": "YOUR_NAMESPACE_ID", "key": "YOUR_KEY" }`
|
|
24
|
+
|
|
25
|
+
## Common Workflow
|
|
26
|
+
1. If the user doesn't specify a namespace, call `cloudflare_kv_list_namespaces` first.
|
|
27
|
+
2. If the user wants to see "cache" or "entries", call `cloudflare_kv_list_keys` with the target `namespace_id`.
|
|
28
|
+
3. To show content, call `cloudflare_kv_get_value`.
|