@coolclaw/coolclaw 0.1.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.
@@ -0,0 +1,289 @@
1
+ import {
2
+ coolclawChannelPlugin,
3
+ defaultBindingFile,
4
+ defaultOpenClawConfigFile,
5
+ defaultTokenFile,
6
+ loadBinding,
7
+ normalizeGatewayUrl,
8
+ readTokenRef,
9
+ saveAgentToken,
10
+ saveBinding,
11
+ touchBinding
12
+ } from "./chunk-4WOJKMUY.js";
13
+
14
+ // src/identity.ts
15
+ import { mkdir, readFile, writeFile } from "fs/promises";
16
+ import path from "path";
17
+ var BEGIN_MARKER = "<!-- BEGIN_RIDDLE_IDENTITY -->";
18
+ var END_MARKER = "<!-- END_RIDDLE_IDENTITY -->";
19
+ async function updateRiddleIdentitySummary(summary) {
20
+ await mkdir(summary.workspaceDir, { recursive: true });
21
+ const identityFile = path.join(summary.workspaceDir, "IDENTITY.md");
22
+ const existing = await readExisting(identityFile);
23
+ const block = [
24
+ BEGIN_MARKER,
25
+ "## Riddle Platform",
26
+ "",
27
+ `Riddle Agent ID: ${summary.agentId}`,
28
+ `Riddle Gateway: ${normalizeGatewayUrl(summary.gatewayUrl)}`,
29
+ `Riddle Binding: ${summary.bindingFile}`,
30
+ "Messaging: handled by the CoolClaw OpenClaw channel.",
31
+ "Non-message platform actions: handled by the Riddle skill.",
32
+ END_MARKER
33
+ ].join("\n");
34
+ const next = replaceBlock(existing, block);
35
+ await writeFile(identityFile, next.endsWith("\n") ? next : `${next}
36
+ `);
37
+ }
38
+ async function readExisting(identityFile) {
39
+ try {
40
+ return await readFile(identityFile, "utf8");
41
+ } catch {
42
+ return "";
43
+ }
44
+ }
45
+ function replaceBlock(existing, block) {
46
+ const start = existing.indexOf(BEGIN_MARKER);
47
+ const end = existing.indexOf(END_MARKER);
48
+ if (start >= 0 && end >= start) {
49
+ return `${existing.slice(0, start).trimEnd()}
50
+
51
+ ${block}
52
+ ${existing.slice(end + END_MARKER.length).trimStart()}`;
53
+ }
54
+ return existing.trim() ? `${existing.trimEnd()}
55
+
56
+ ${block}
57
+ ` : `${block}
58
+ `;
59
+ }
60
+
61
+ // src/openclaw-config.ts
62
+ import { mkdir as mkdir2, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
63
+ import path2 from "path";
64
+ async function patchCoolclawAccountConfig(patch) {
65
+ const config = await readOpenClawConfig(patch.configPath);
66
+ const channels = ensureRecord(config, "channels");
67
+ const coolclaw = ensureRecord(channels, "coolclaw");
68
+ const accounts = ensureRecord(coolclaw, "accounts");
69
+ const account = {
70
+ name: `Riddle Agent ${patch.agentId}`,
71
+ enabled: true,
72
+ gatewayUrl: normalizeGatewayUrl(patch.gatewayUrl),
73
+ agentId: patch.agentId,
74
+ tokenSecretRef: patch.tokenSecretRef,
75
+ dmPolicy: patch.dmPolicy ?? "allowlist"
76
+ };
77
+ if (patch.allowFrom && patch.allowFrom.length > 0) {
78
+ account.allowFrom = patch.allowFrom;
79
+ }
80
+ accounts.default = account;
81
+ await writeOpenClawConfig(patch.configPath, config);
82
+ }
83
+ async function readOpenClawConfig(configPath) {
84
+ try {
85
+ const parsed = JSON.parse(await readFile2(configPath, "utf8"));
86
+ return isRecord(parsed) ? parsed : {};
87
+ } catch {
88
+ return {};
89
+ }
90
+ }
91
+ async function writeOpenClawConfig(configPath, config) {
92
+ await mkdir2(path2.dirname(configPath), { recursive: true });
93
+ const tmpFile = `${configPath}.${process.pid}.tmp`;
94
+ await writeFile2(tmpFile, `${JSON.stringify(config, null, 2)}
95
+ `);
96
+ await rename(tmpFile, configPath);
97
+ }
98
+ function ensureRecord(parent, key) {
99
+ const existing = parent[key];
100
+ if (isRecord(existing)) return existing;
101
+ const created = {};
102
+ parent[key] = created;
103
+ return created;
104
+ }
105
+ function isRecord(value) {
106
+ return typeof value === "object" && value !== null && !Array.isArray(value);
107
+ }
108
+
109
+ // src/setup.ts
110
+ async function registerAgent(baseUrl, input) {
111
+ const res = await fetch(`${normalizeGatewayUrl(baseUrl)}/api/agent/register`, {
112
+ method: "POST",
113
+ headers: { "Content-Type": "application/json" },
114
+ body: JSON.stringify(input)
115
+ });
116
+ const body = await res.json();
117
+ if (!res.ok || body.code !== 200) {
118
+ throw new Error(body.message ?? `Agent register failed: ${res.status}`);
119
+ }
120
+ if (!body.data?.agentId || !body.data.token) {
121
+ throw new Error("Agent register response missing agentId or token");
122
+ }
123
+ return {
124
+ agentId: String(body.data.agentId),
125
+ token: String(body.data.token)
126
+ };
127
+ }
128
+ async function validateAgentToken(baseUrl, token) {
129
+ const res = await fetch(`${normalizeGatewayUrl(baseUrl)}/api/users/me`, {
130
+ headers: { Authorization: `Bearer ${token}` }
131
+ });
132
+ if (!res.ok) return false;
133
+ const body = await res.json();
134
+ return body.code === 200 && body.data?.identityType === "AGENT";
135
+ }
136
+ async function runCoolclawSetup(options = {}) {
137
+ const gatewayUrl = normalizeGatewayUrl(options.gatewayUrl ?? "https://agits-xa.baidu.com/riddle");
138
+ const bindingFile = options.bindingFile ?? defaultBindingFile();
139
+ const openclawConfigPath = options.openclawConfigPath ?? defaultOpenClawConfigFile();
140
+ const existingBinding = await loadBinding(bindingFile);
141
+ const existingToken = await readTokenRef(existingBinding.tokenRef);
142
+ if (options.dryRun) {
143
+ return {
144
+ mode: "dry-run",
145
+ gatewayUrl,
146
+ agentId: existingBinding.agentId,
147
+ bindingFile,
148
+ openclawConfigPath,
149
+ tokenSavedTo: tokenFileFromBinding(existingBinding, bindingFile)
150
+ };
151
+ }
152
+ const canReuse = !options.forceRegister && existingBinding.agentId.length > 0 && Boolean(existingToken) && await validateAgentToken(gatewayUrl, existingToken);
153
+ const binding = canReuse ? existingBinding : await registerAndPersistBinding({
154
+ gatewayUrl,
155
+ bindingFile,
156
+ registration: options.registration ?? defaultRegistrationInput()
157
+ });
158
+ const tokenFile = tokenFileFromBinding(binding, bindingFile);
159
+ if (!options.dryRun) {
160
+ await patchCoolclawAccountConfig({
161
+ configPath: openclawConfigPath,
162
+ gatewayUrl,
163
+ agentId: binding.agentId,
164
+ tokenSecretRef: binding.tokenRef ?? `file://${tokenFile}`,
165
+ allowFrom: options.allowFrom,
166
+ dmPolicy: options.dmPolicy
167
+ });
168
+ if (options.workspaceDir) {
169
+ await updateRiddleIdentitySummary({
170
+ workspaceDir: options.workspaceDir,
171
+ agentId: binding.agentId,
172
+ gatewayUrl,
173
+ bindingFile,
174
+ tokenRef: binding.tokenRef ?? void 0
175
+ });
176
+ }
177
+ }
178
+ return {
179
+ mode: options.dryRun ? "dry-run" : canReuse ? "reuse" : "register",
180
+ gatewayUrl,
181
+ agentId: binding.agentId,
182
+ bindingFile,
183
+ openclawConfigPath,
184
+ tokenSavedTo: tokenFile
185
+ };
186
+ }
187
+ async function registerAndPersistBinding(input) {
188
+ const registered = await registerAgent(input.gatewayUrl, input.registration);
189
+ const valid = await validateAgentToken(input.gatewayUrl, registered.token);
190
+ if (!valid) {
191
+ throw new Error("Newly registered Riddle token did not validate as an AGENT token");
192
+ }
193
+ const tokenFile = defaultTokenFile(input.bindingFile, registered.agentId);
194
+ await saveAgentToken(tokenFile, registered.token);
195
+ const binding = touchBinding({
196
+ agentId: registered.agentId,
197
+ tokenRef: `file://${tokenFile}`,
198
+ runtimeType: "openclaw",
199
+ lastAckedSeq: 0
200
+ });
201
+ await saveBinding(input.bindingFile, binding);
202
+ return binding;
203
+ }
204
+ function tokenFileFromBinding(binding, bindingFile) {
205
+ if (binding.tokenRef?.startsWith("file://")) {
206
+ return binding.tokenRef.slice("file://".length);
207
+ }
208
+ return defaultTokenFile(bindingFile, binding.agentId);
209
+ }
210
+ function defaultRegistrationInput() {
211
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(4, 12);
212
+ return {
213
+ name: `RiddleAgent-${stamp}`,
214
+ bio: "OpenClaw agent connected through CoolClaw channel.",
215
+ tags: JSON.stringify(["assistant", "openclaw", "coolclaw"])
216
+ };
217
+ }
218
+
219
+ // src/cli.ts
220
+ function registerCoolclawCli(options) {
221
+ const setup = options.setup ?? runCoolclawSetup;
222
+ const coolclaw = options.program.command("coolclaw").description("Manage the CoolClaw/Riddle channel");
223
+ coolclaw.command("setup").description("Register or reuse a Riddle agent and configure the CoolClaw channel").option("--gateway-url <url>", "Riddle gateway URL", "https://agits-xa.baidu.com/riddle").option("--binding-file <path>", "Shared Riddle binding file", defaultBindingFile()).option("--openclaw-config <path>", "OpenClaw config file").option("--workspace-dir <path>", "OpenClaw agent workspace directory", options.workspaceDir).option("--name <name>", "Riddle agent display name").option("--bio <bio>", "Riddle agent bio").option("--tags <tags>", "Riddle agent tags").option("--allow-from <items>", "Comma-separated DM allowlist").option("--dm-policy <policy>", "DM policy: allowlist or pairing", "allowlist").option("--force-register", "Register a new Riddle agent even if a valid binding exists", false).option("--dry-run", "Resolve setup inputs without writing local files", false).option("-y, --yes", "Accept defaults for non-interactive setup", false).action(async (...args) => {
224
+ const rawOptions = readActionOptions(args);
225
+ const result = await setup(toSetupOptions(rawOptions));
226
+ console.log(JSON.stringify(result, null, 2));
227
+ });
228
+ coolclaw.command("status").description("Show the shared Riddle binding location used by CoolClaw").action(() => {
229
+ console.log(JSON.stringify({ bindingFile: defaultBindingFile() }, null, 2));
230
+ });
231
+ }
232
+ function readActionOptions(args) {
233
+ const candidate = args.at(-1);
234
+ return typeof candidate === "object" && candidate !== null ? candidate : {};
235
+ }
236
+ function toSetupOptions(raw) {
237
+ return {
238
+ gatewayUrl: stringOption(raw.gatewayUrl),
239
+ bindingFile: stringOption(raw.bindingFile),
240
+ openclawConfigPath: stringOption(raw.openclawConfig),
241
+ workspaceDir: stringOption(raw.workspaceDir),
242
+ registration: {
243
+ name: stringOption(raw.name) ?? "CoolClaw Agent",
244
+ bio: stringOption(raw.bio) ?? "OpenClaw agent connected through CoolClaw channel.",
245
+ tags: stringOption(raw.tags) ?? JSON.stringify(["assistant", "openclaw", "coolclaw"])
246
+ },
247
+ allowFrom: splitCsv(stringOption(raw.allowFrom)),
248
+ dmPolicy: raw.dmPolicy === "pairing" ? "pairing" : "allowlist",
249
+ forceRegister: Boolean(raw.forceRegister),
250
+ dryRun: Boolean(raw.dryRun),
251
+ yes: Boolean(raw.yes)
252
+ };
253
+ }
254
+ function stringOption(value) {
255
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
256
+ }
257
+ function splitCsv(value) {
258
+ if (!value) return void 0;
259
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
260
+ return items.length > 0 ? items : void 0;
261
+ }
262
+
263
+ // index.ts
264
+ var index_default = {
265
+ id: "coolclaw",
266
+ name: "CoolClaw Channel",
267
+ description: "CoolClaw/Riddle messaging channel for OpenClaw",
268
+ register(api) {
269
+ api.registerChannel({ plugin: coolclawChannelPlugin });
270
+ api.registerCli?.(
271
+ ({ program, workspaceDir }) => {
272
+ registerCoolclawCli({ program, workspaceDir });
273
+ },
274
+ {
275
+ descriptors: [
276
+ {
277
+ name: "coolclaw",
278
+ description: "Manage the CoolClaw/Riddle channel",
279
+ hasSubcommands: true
280
+ }
281
+ ]
282
+ }
283
+ );
284
+ }
285
+ };
286
+
287
+ export {
288
+ index_default
289
+ };
@@ -0,0 +1,6 @@
1
+ import _default from './index.js';
2
+ import './channel-BozkcSms.js';
3
+
4
+
5
+
6
+ export { _default as default };
@@ -0,0 +1,10 @@
1
+ import {
2
+ index_default
3
+ } from "./chunk-LUUSK4S5.js";
4
+ import "./chunk-4WOJKMUY.js";
5
+
6
+ // cli-metadata.ts
7
+ var cli_metadata_default = index_default;
8
+ export {
9
+ cli_metadata_default as default
10
+ };
@@ -0,0 +1,25 @@
1
+ import { c as coolclawChannelPlugin } from './channel-BozkcSms.js';
2
+
3
+ type OpenClawPluginApi = {
4
+ registerChannel(input: {
5
+ plugin: typeof coolclawChannelPlugin;
6
+ }): void;
7
+ registerCli?(registrar: (ctx: {
8
+ program: unknown;
9
+ workspaceDir?: string;
10
+ }) => void | Promise<void>, opts?: {
11
+ descriptors?: Array<{
12
+ name: string;
13
+ description: string;
14
+ hasSubcommands: boolean;
15
+ }>;
16
+ }): void;
17
+ };
18
+ declare const _default: {
19
+ id: string;
20
+ name: string;
21
+ description: string;
22
+ register(api: OpenClawPluginApi): void;
23
+ };
24
+
25
+ export { _default as default };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ index_default
3
+ } from "./chunk-LUUSK4S5.js";
4
+ import "./chunk-4WOJKMUY.js";
5
+ export {
6
+ index_default as default
7
+ };
@@ -0,0 +1,5 @@
1
+ import { c as coolclawChannelPlugin } from './channel-BozkcSms.js';
2
+
3
+
4
+
5
+ export { coolclawChannelPlugin as default };
@@ -0,0 +1,9 @@
1
+ import {
2
+ coolclawChannelPlugin
3
+ } from "./chunk-4WOJKMUY.js";
4
+
5
+ // setup-entry.ts
6
+ var setup_entry_default = coolclawChannelPlugin;
7
+ export {
8
+ setup_entry_default as default
9
+ };
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "coolclaw",
3
+ "channels": [
4
+ "coolclaw"
5
+ ],
6
+ "channelEnvVars": {
7
+ "coolclaw": [
8
+ "COOLCLAW_GATEWAY_URL",
9
+ "COOLCLAW_AGENT_ID",
10
+ "COOLCLAW_AGENT_TOKEN",
11
+ "COOLCLAW_AGENT_TOKEN_SECRET_REF",
12
+ "COOLCLAW_ALLOW_FROM",
13
+ "COOLCLAW_DM_POLICY"
14
+ ]
15
+ },
16
+ "configSchema": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "properties": {}
20
+ }
21
+ }
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@coolclaw/coolclaw",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw native channel plugin for Riddle/CoolClaw chat.",
5
+ "type": "module",
6
+ "files": [
7
+ "dist",
8
+ "openclaw.plugin.json",
9
+ "README.md"
10
+ ],
11
+ "keywords": [
12
+ "openclaw",
13
+ "openclaw-plugin",
14
+ "openclaw-channel",
15
+ "coolclaw",
16
+ "riddle",
17
+ "chat",
18
+ "websocket"
19
+ ],
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/coolclaw/riddle.git",
24
+ "directory": "plugins/openclaw-coolclaw-channel"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup index.ts setup-entry.ts cli-metadata.ts --format esm --dts --out-dir dist --clean",
28
+ "test": "vitest run",
29
+ "lint": "tsc --noEmit"
30
+ },
31
+ "dependencies": {
32
+ "ws": "latest",
33
+ "zod": "latest"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "latest",
37
+ "@types/ws": "latest",
38
+ "tsup": "latest",
39
+ "typescript": "latest",
40
+ "vitest": "latest"
41
+ },
42
+ "peerDependencies": {
43
+ "openclaw": ">=2026.3.13"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "openclaw": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "openclaw": {
51
+ "extensions": [
52
+ "./index.ts"
53
+ ],
54
+ "runtimeExtensions": [
55
+ "./dist/index.js"
56
+ ],
57
+ "setupEntry": "./setup-entry.ts",
58
+ "runtimeSetupEntry": "./dist/setup-entry.js",
59
+ "install": {
60
+ "npmSpec": "@coolclaw/coolclaw",
61
+ "defaultChoice": "npm",
62
+ "minHostVersion": ">=2026.3.13"
63
+ },
64
+ "channel": {
65
+ "id": "coolclaw",
66
+ "label": "CoolClaw",
67
+ "docsPath": "/plugins/coolclaw",
68
+ "blurb": "Connect OpenClaw to the CoolClaw/Riddle chat platform."
69
+ },
70
+ "compat": {
71
+ "pluginApi": ">=2026.3.13",
72
+ "minGatewayVersion": "2026.3.13"
73
+ },
74
+ "build": {
75
+ "openclawVersion": "2026.4.20",
76
+ "pluginSdkVersion": "2026.4.20"
77
+ }
78
+ }
79
+ }