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 ADDED
@@ -0,0 +1,99 @@
1
+ # openclaw-cloudflare-plugin
2
+
3
+ ![npm version](https://img.shields.io/npm/v/openclaw-cloudflare-plugin)
4
+ ![npm downloads](https://img.shields.io/npm/dm/openclaw-cloudflare-plugin)
5
+ ![license](https://img.shields.io/npm/l/openclaw-cloudflare-plugin)
6
+ ![typescript](https://img.shields.io/badge/TypeScript-supported-blue)
7
+ ![GitHub stars](https://img.shields.io/github/stars/wuyangwang/openclaw-cloudflare-plugin)
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
@@ -0,0 +1,11 @@
1
+ import { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+
3
+ //#region index.d.ts
4
+ declare const plugin: {
5
+ id: string;
6
+ name: string;
7
+ description: string;
8
+ register(api: OpenClawPluginApi): void;
9
+ };
10
+ //#endregion
11
+ export { plugin as default };
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`.