@goplausible/openclaw-algorand-plugin 1.9.4 → 2.0.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/index.ts +74 -529
- package/lib/mcporter.ts +75 -0
- package/lib/workspace.ts +216 -0
- package/memory/MEMORY.md +6 -0
- package/memory/algorand-plugin.md +11 -0
- package/openclaw.plugin.json +25 -2
- package/package.json +12 -3
- package/setup.ts +7 -23
- package/skills/algorand-interaction/SKILL.md +22 -1
- package/skills/algorand-interaction/references/examples-algorand-mcp.md +50 -3
- package/skills/alpha-arcade-interaction/SKILL.md +15 -0
- package/skills/haystack-router-interaction/SKILL.md +9 -0
- package/scripts/backup-keyring.js +0 -117
- package/scripts/setup-keyring.sh +0 -428
package/index.ts
CHANGED
|
@@ -1,369 +1,45 @@
|
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
1
5
|
import { ALGORAND_MCP, GOPLAUSIBLE_SERVICES } from "./lib/mcp-servers.js";
|
|
2
6
|
import { runSetup, type AlgorandPluginConfig } from "./setup.js";
|
|
3
7
|
import { x402Fetch } from "./lib/x402-fetch.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import {
|
|
9
|
+
getMcpBinaryPath,
|
|
10
|
+
isMcpBinaryBundled,
|
|
11
|
+
isMcporterConfigured,
|
|
12
|
+
mcporterConfigPath,
|
|
13
|
+
upsertMcporterConfig,
|
|
14
|
+
} from "./lib/mcporter.js";
|
|
15
|
+
import {
|
|
16
|
+
ensureWorkspaceMemoryIndex,
|
|
17
|
+
resolveWorkspaceDir,
|
|
18
|
+
runFirstLoadInit,
|
|
19
|
+
writeMemoryFile,
|
|
20
|
+
writePluginConfig,
|
|
21
|
+
type WorkspaceApi,
|
|
22
|
+
} from "./lib/workspace.js";
|
|
23
|
+
|
|
24
|
+
const PLUGIN_ROOT = dirname(fileURLToPath(import.meta.url));
|
|
11
25
|
const PLUGIN_ID = "openclaw-algorand-plugin";
|
|
12
26
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
config?: Partial<AlgorandPluginConfig>;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
agents?: {
|
|
23
|
-
defaults?: {
|
|
24
|
-
workspace?: string;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
logger: {
|
|
29
|
-
info: (msg: string) => void;
|
|
30
|
-
warn: (msg: string) => void;
|
|
31
|
-
error: (msg: string) => void;
|
|
32
|
-
};
|
|
33
|
-
runtime: {
|
|
34
|
-
openUrl: (url: string) => Promise<void>;
|
|
35
|
-
};
|
|
27
|
+
type OpenClawPluginApi = WorkspaceApi & {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
version?: string;
|
|
31
|
+
pluginConfig?: Partial<AlgorandPluginConfig>;
|
|
36
32
|
registerTool: (tool: object, options?: object) => void;
|
|
37
33
|
registerCli: (fn: (ctx: { program: any }) => void, options: { commands: string[] }) => void;
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getWorkspacePath(api: PluginApi): string {
|
|
42
|
-
return api.config.agents?.defaults?.workspace ||
|
|
43
|
-
join(homedir(), ".openclaw", "workspace");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getConfigPath(): string {
|
|
47
|
-
return join(homedir(), ".openclaw", "openclaw.json");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function getMcpBinaryPath(): string {
|
|
51
|
-
// Check plugin's node_modules first
|
|
52
|
-
const pluginBin = join(__dirname, "node_modules", ".bin", "algorand-mcp");
|
|
53
|
-
if (existsSync(pluginBin)) {
|
|
54
|
-
return pluginBin;
|
|
55
|
-
}
|
|
56
|
-
// Fall back to npx
|
|
57
|
-
return "npx algorand-mcp";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function updatePluginConfig(newConfig: AlgorandPluginConfig): { success: boolean; error?: string } {
|
|
61
|
-
try {
|
|
62
|
-
const configPath = getConfigPath();
|
|
63
|
-
const configDir = dirname(configPath);
|
|
64
|
-
|
|
65
|
-
// Create ~/.openclaw directory if it doesn't exist
|
|
66
|
-
if (!existsSync(configDir)) {
|
|
67
|
-
mkdirSync(configDir, { recursive: true });
|
|
68
|
-
}
|
|
34
|
+
};
|
|
69
35
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const rawConfig = readFileSync(configPath, "utf-8");
|
|
74
|
-
config = JSON.parse(rawConfig);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Ensure plugins structure exists
|
|
78
|
-
if (!config.plugins) config.plugins = {};
|
|
79
|
-
if (!config.plugins.entries) config.plugins.entries = {};
|
|
80
|
-
if (!config.plugins.entries[PLUGIN_ID]) {
|
|
81
|
-
config.plugins.entries[PLUGIN_ID] = {};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Update the plugin config
|
|
85
|
-
config.plugins.entries[PLUGIN_ID].config = newConfig;
|
|
86
|
-
|
|
87
|
-
// Add to plugins.allow if not already there
|
|
88
|
-
if (!config.plugins.allow) config.plugins.allow = [];
|
|
89
|
-
if (!config.plugins.allow.includes(PLUGIN_ID)) {
|
|
90
|
-
config.plugins.allow.push(PLUGIN_ID);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Write back with pretty formatting
|
|
94
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
95
|
-
|
|
96
|
-
return { success: true };
|
|
97
|
-
} catch (err) {
|
|
98
|
-
return { success: false, error: String(err) };
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function stopExistingMcpProcesses(): { stopped: number; message: string } {
|
|
103
|
-
try {
|
|
104
|
-
// Find all running algorand-mcp processes
|
|
105
|
-
const psOutput = execSync("ps aux 2>/dev/null || tasklist 2>/dev/null || echo ''", { encoding: "utf-8" });
|
|
106
|
-
const lines = psOutput.split("\n").filter((line: string) => line.includes("algorand-mcp") && !line.includes("grep"));
|
|
107
|
-
|
|
108
|
-
if (lines.length === 0) {
|
|
109
|
-
return { stopped: 0, message: "No existing algorand-mcp processes found" };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Extract PIDs and kill them
|
|
113
|
-
let stopped = 0;
|
|
114
|
-
for (const line of lines) {
|
|
115
|
-
const parts = line.trim().split(/\s+/);
|
|
116
|
-
const pid = parts[1]; // PID is second column in ps aux
|
|
117
|
-
if (pid && /^\d+$/.test(pid)) {
|
|
118
|
-
try {
|
|
119
|
-
execSync(`kill ${pid} 2>/dev/null`, { encoding: "utf-8" });
|
|
120
|
-
stopped++;
|
|
121
|
-
} catch {
|
|
122
|
-
// Process may have already exited
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Brief wait for processes to terminate
|
|
128
|
-
if (stopped > 0) {
|
|
129
|
-
execSync("sleep 1");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return { stopped, message: `Stopped ${stopped} existing algorand-mcp process${stopped !== 1 ? "es" : ""}` };
|
|
133
|
-
} catch {
|
|
134
|
-
return { stopped: 0, message: "Could not check for existing processes" };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
36
|
+
function register(api: OpenClawPluginApi) {
|
|
37
|
+
const pluginConfig: Partial<AlgorandPluginConfig> = api.pluginConfig ?? {};
|
|
38
|
+
const workspacePath = resolveWorkspaceDir(api);
|
|
137
39
|
|
|
138
|
-
|
|
139
|
-
|
|
40
|
+
try { runFirstLoadInit(api, PLUGIN_ROOT, workspacePath); }
|
|
41
|
+
catch (err) { api.logger.warn(`[algorand-plugin] first-load init failed: ${err}`); }
|
|
140
42
|
|
|
141
|
-
try {
|
|
142
|
-
// Check if mcporter is available
|
|
143
|
-
execSync("which mcporter", { encoding: "utf-8" });
|
|
144
|
-
} catch {
|
|
145
|
-
return { success: false, message: "mcporter not installed. Install with: npm install -g mcporter" };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
// Check if algorand server already configured
|
|
150
|
-
const listOutput = execSync("mcporter config list 2>/dev/null || echo ''", { encoding: "utf-8" });
|
|
151
|
-
if (listOutput.includes("algorand-mcp")) {
|
|
152
|
-
return { success: true, message: "algorand-mcp server already configured in mcporter" };
|
|
153
|
-
}
|
|
154
|
-
} catch {
|
|
155
|
-
// Continue to add
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
// Add algorand server to mcporter config (home scope for global access)
|
|
160
|
-
const cmd = `mcporter config add algorand-mcp --command "${mcpCommand}" --scope home --description "Algorand blockchain MCP (GoPlausible)"`;
|
|
161
|
-
execSync(cmd, { encoding: "utf-8" });
|
|
162
|
-
return { success: true, message: `algorand server added to mcporter (command: ${mcpCommand})` };
|
|
163
|
-
} catch (err) {
|
|
164
|
-
return { success: false, message: `Failed to configure mcporter: ${err}` };
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function writeMemoryFile(workspacePath: string): { success: boolean; message: string } {
|
|
169
|
-
const sourceFile = join(__dirname, "memory", "algorand-plugin.md");
|
|
170
|
-
const memoryDir = join(workspacePath, "memory");
|
|
171
|
-
const targetFile = join(memoryDir, "algorand-plugin.md");
|
|
172
|
-
|
|
173
|
-
if (!existsSync(sourceFile)) {
|
|
174
|
-
return { success: false, message: `Source memory/algorand-plugin.md not found at ${sourceFile}` };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!existsSync(memoryDir)) {
|
|
178
|
-
mkdirSync(memoryDir, { recursive: true });
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const content = readFileSync(sourceFile, "utf-8");
|
|
182
|
-
writeFileSync(targetFile, content);
|
|
183
|
-
|
|
184
|
-
return { success: true, message: `Plugin memory written to ${targetFile}` };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function ensureWorkspaceMemoryIndex(workspacePath: string): { success: boolean; message: string } {
|
|
188
|
-
const templateFile = join(__dirname, "memory", "MEMORY.md");
|
|
189
|
-
|
|
190
|
-
if (!existsSync(templateFile)) {
|
|
191
|
-
return { success: false, message: "Template MEMORY.md not found in plugin" };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const templateContent = readFileSync(templateFile, "utf-8");
|
|
195
|
-
|
|
196
|
-
// Extract NEVER FORGET section from template (everything between ## NEVER FORGET and next ## or EOF)
|
|
197
|
-
const neverForgetMatch = templateContent.match(/## NEVER FORGET\n([\s\S]*?)(?=\n## (?!NEVER)|$)/);
|
|
198
|
-
if (!neverForgetMatch) {
|
|
199
|
-
return { success: false, message: "No NEVER FORGET section found in template MEMORY.md" };
|
|
200
|
-
}
|
|
201
|
-
const templateNeverForget = neverForgetMatch[1].trimEnd();
|
|
202
|
-
|
|
203
|
-
// Check for MEMORY.md or memory.md at workspace root
|
|
204
|
-
const memoryMdPath = join(workspacePath, "MEMORY.md");
|
|
205
|
-
const memoryMdLower = join(workspacePath, "memory.md");
|
|
206
|
-
|
|
207
|
-
const existingPath = existsSync(memoryMdPath) ? memoryMdPath
|
|
208
|
-
: existsSync(memoryMdLower) ? memoryMdLower
|
|
209
|
-
: null;
|
|
210
|
-
|
|
211
|
-
if (!existingPath) {
|
|
212
|
-
// No MEMORY.md exists — create from template
|
|
213
|
-
writeFileSync(memoryMdPath, templateContent);
|
|
214
|
-
return { success: true, message: `Created ${memoryMdPath} with NEVER FORGET section` };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// MEMORY.md exists — check for NEVER FORGET header
|
|
218
|
-
let existing = readFileSync(existingPath, "utf-8");
|
|
219
|
-
|
|
220
|
-
if (!/## NEVER FORGET/i.test(existing)) {
|
|
221
|
-
// No NEVER FORGET section — insert after first # heading
|
|
222
|
-
const firstHeadingEnd = existing.match(/^# .+\n/m);
|
|
223
|
-
if (firstHeadingEnd) {
|
|
224
|
-
const insertPos = (firstHeadingEnd.index ?? 0) + firstHeadingEnd[0].length;
|
|
225
|
-
existing = existing.slice(0, insertPos) + "\n## NEVER FORGET\n" + templateNeverForget + "\n\n" + existing.slice(insertPos);
|
|
226
|
-
} else {
|
|
227
|
-
// No heading at all — prepend
|
|
228
|
-
existing = "# OpenClaw Agent Long-Term Memory\n\n## NEVER FORGET\n" + templateNeverForget + "\n\n" + existing;
|
|
229
|
-
}
|
|
230
|
-
writeFileSync(existingPath, existing);
|
|
231
|
-
return { success: true, message: `Added NEVER FORGET section to ${existingPath}` };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// NEVER FORGET exists — update each subsection individually
|
|
235
|
-
// Parse template into subsections: { header: string, content: string }[]
|
|
236
|
-
const parseSubsections = (text: string): { header: string; content: string }[] => {
|
|
237
|
-
const sections: { header: string; content: string }[] = [];
|
|
238
|
-
const lines = text.split("\n");
|
|
239
|
-
let currentHeader = "";
|
|
240
|
-
let currentLines: string[] = [];
|
|
241
|
-
|
|
242
|
-
for (const line of lines) {
|
|
243
|
-
if (line.startsWith("### ")) {
|
|
244
|
-
if (currentHeader) {
|
|
245
|
-
sections.push({ header: currentHeader, content: currentLines.join("\n").trimEnd() });
|
|
246
|
-
}
|
|
247
|
-
currentHeader = line;
|
|
248
|
-
currentLines = [];
|
|
249
|
-
} else if (currentHeader) {
|
|
250
|
-
currentLines.push(line);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (currentHeader) {
|
|
254
|
-
sections.push({ header: currentHeader, content: currentLines.join("\n").trimEnd() });
|
|
255
|
-
}
|
|
256
|
-
return sections;
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const templateSections = parseSubsections(templateNeverForget);
|
|
260
|
-
|
|
261
|
-
// Extract existing NEVER FORGET section boundaries
|
|
262
|
-
const nfSectionMatch = existing.match(/(## NEVER FORGET\n)([\s\S]*?)(?=\n## (?!#)|$)/);
|
|
263
|
-
if (!nfSectionMatch) {
|
|
264
|
-
return { success: true, message: `NEVER FORGET section in ${existingPath} is up to date` };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
let nfContent = nfSectionMatch[2];
|
|
268
|
-
let updated = false;
|
|
269
|
-
|
|
270
|
-
for (const templateSec of templateSections) {
|
|
271
|
-
if (templateSec.header === "### Never Do This") {
|
|
272
|
-
// Special handling: merge individual bullet items
|
|
273
|
-
const neverDoRegex = new RegExp(
|
|
274
|
-
"(### Never Do This\\n)([\\s\\S]*?)(?=\\n### |$)"
|
|
275
|
-
);
|
|
276
|
-
const existingNeverDoMatch = nfContent.match(neverDoRegex);
|
|
277
|
-
|
|
278
|
-
if (!existingNeverDoMatch) {
|
|
279
|
-
// Section doesn't exist — append it
|
|
280
|
-
nfContent = nfContent.trimEnd() + "\n\n" + templateSec.header + "\n" + templateSec.content + "\n";
|
|
281
|
-
updated = true;
|
|
282
|
-
} else {
|
|
283
|
-
// Section exists — check each bullet item
|
|
284
|
-
let existingBullets = existingNeverDoMatch[2];
|
|
285
|
-
const templateBullets = templateSec.content.split("\n").filter((l: string) => l.startsWith("* "));
|
|
286
|
-
const existingBulletLines = existingBullets.split("\n").filter((l: string) => l.startsWith("* "));
|
|
287
|
-
|
|
288
|
-
for (const bullet of templateBullets) {
|
|
289
|
-
// Use first 50 chars after "* " as fingerprint for matching
|
|
290
|
-
const fingerprint = bullet.slice(2, 52).trim();
|
|
291
|
-
const existingMatch = existingBulletLines.find(l => l.includes(fingerprint));
|
|
292
|
-
|
|
293
|
-
if (existingMatch) {
|
|
294
|
-
// Item exists — overwrite with template version
|
|
295
|
-
if (existingMatch !== bullet) {
|
|
296
|
-
nfContent = nfContent.replace(existingMatch, bullet);
|
|
297
|
-
updated = true;
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
// Item doesn't exist — append it
|
|
301
|
-
existingBullets = existingBullets.trimEnd() + "\n" + bullet;
|
|
302
|
-
nfContent = nfContent.replace(existingNeverDoMatch[2], existingBullets);
|
|
303
|
-
updated = true;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
} else {
|
|
308
|
-
// Non-"Never Do This" subsections: overwrite entire subsection if exists, add if not
|
|
309
|
-
const sectionRegex = new RegExp(
|
|
310
|
-
"(" + templateSec.header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\n)([\\s\\S]*?)(?=\\n### |$)"
|
|
311
|
-
);
|
|
312
|
-
const existingSecMatch = nfContent.match(sectionRegex);
|
|
313
|
-
|
|
314
|
-
if (existingSecMatch) {
|
|
315
|
-
// Section exists — overwrite its content
|
|
316
|
-
if (existingSecMatch[2].trimEnd() !== templateSec.content) {
|
|
317
|
-
nfContent = nfContent.replace(
|
|
318
|
-
existingSecMatch[0],
|
|
319
|
-
templateSec.header + "\n" + templateSec.content
|
|
320
|
-
);
|
|
321
|
-
updated = true;
|
|
322
|
-
}
|
|
323
|
-
} else {
|
|
324
|
-
// Section doesn't exist — insert before "### Never Do This" or append
|
|
325
|
-
const neverDoPos = nfContent.indexOf("### Never Do This");
|
|
326
|
-
if (neverDoPos !== -1) {
|
|
327
|
-
nfContent = nfContent.slice(0, neverDoPos) + templateSec.header + "\n" + templateSec.content + "\n\n" + nfContent.slice(neverDoPos);
|
|
328
|
-
} else {
|
|
329
|
-
nfContent = nfContent.trimEnd() + "\n\n" + templateSec.header + "\n" + templateSec.content + "\n";
|
|
330
|
-
}
|
|
331
|
-
updated = true;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (!updated) {
|
|
337
|
-
return { success: true, message: `NEVER FORGET section in ${existingPath} is up to date` };
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Replace the NEVER FORGET content in the full file
|
|
341
|
-
existing = existing.replace(nfSectionMatch[2], nfContent);
|
|
342
|
-
writeFileSync(existingPath, existing);
|
|
343
|
-
|
|
344
|
-
return { success: true, message: `Updated NEVER FORGET subsections in ${existingPath}` };
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function checkMcpBinary(): { available: boolean; path?: string } {
|
|
348
|
-
try {
|
|
349
|
-
const path = execSync("which algorand-mcp", { encoding: "utf-8" }).trim();
|
|
350
|
-
return { available: true, path };
|
|
351
|
-
} catch {
|
|
352
|
-
// Check plugin's node_modules
|
|
353
|
-
const pluginBin = join(__dirname, "node_modules", ".bin", "algorand-mcp");
|
|
354
|
-
if (existsSync(pluginBin)) {
|
|
355
|
-
return { available: true, path: pluginBin };
|
|
356
|
-
}
|
|
357
|
-
return { available: false };
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
export default function register(api: PluginApi) {
|
|
362
|
-
const pluginConfig = api.config.plugins?.entries?.[PLUGIN_ID]?.config ?? {};
|
|
363
|
-
|
|
364
|
-
// ─────────────────────────────────────────────────────────────
|
|
365
|
-
// x402 Fetch Tool
|
|
366
|
-
// ─────────────────────────────────────────────────────────────
|
|
367
43
|
if (pluginConfig.enableX402 !== false) {
|
|
368
44
|
api.registerTool(
|
|
369
45
|
{
|
|
@@ -373,10 +49,7 @@ export default function register(api: PluginApi) {
|
|
|
373
49
|
parameters: {
|
|
374
50
|
type: "object",
|
|
375
51
|
properties: {
|
|
376
|
-
url: {
|
|
377
|
-
type: "string",
|
|
378
|
-
description: "The URL to fetch",
|
|
379
|
-
},
|
|
52
|
+
url: { type: "string", description: "The URL to fetch" },
|
|
380
53
|
method: {
|
|
381
54
|
type: "string",
|
|
382
55
|
description: "HTTP method (default: GET)",
|
|
@@ -388,25 +61,18 @@ export default function register(api: PluginApi) {
|
|
|
388
61
|
description: "Additional request headers as key-value pairs",
|
|
389
62
|
additionalProperties: { type: "string" },
|
|
390
63
|
},
|
|
391
|
-
body: {
|
|
392
|
-
type: "string",
|
|
393
|
-
description: "Request body (for POST/PUT/PATCH)",
|
|
394
|
-
},
|
|
64
|
+
body: { type: "string", description: "Request body (for POST/PUT/PATCH)" },
|
|
395
65
|
paymentHeader: {
|
|
396
66
|
type: "string",
|
|
397
|
-
description:
|
|
398
|
-
"JSON string for X-PAYMENT header — the signed payment payload from the x402 payment flow",
|
|
67
|
+
description: "JSON string for X-PAYMENT header — the signed payment payload from the x402 payment flow",
|
|
399
68
|
},
|
|
400
69
|
},
|
|
401
70
|
required: ["url"],
|
|
402
71
|
},
|
|
403
|
-
async execute(
|
|
404
|
-
|
|
405
|
-
method?: string;
|
|
406
|
-
|
|
407
|
-
body?: string;
|
|
408
|
-
paymentHeader?: string;
|
|
409
|
-
}) {
|
|
72
|
+
async execute(
|
|
73
|
+
_id: string,
|
|
74
|
+
params: { url: string; method?: string; headers?: Record<string, string>; body?: string; paymentHeader?: string },
|
|
75
|
+
) {
|
|
410
76
|
const result = await x402Fetch(params);
|
|
411
77
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
412
78
|
},
|
|
@@ -415,129 +81,63 @@ export default function register(api: PluginApi) {
|
|
|
415
81
|
);
|
|
416
82
|
}
|
|
417
83
|
|
|
418
|
-
// ─────────────────────────────────────────────────────────────
|
|
419
|
-
// CLI Commands
|
|
420
|
-
// ─────────────────────────────────────────────────────────────
|
|
421
84
|
api.registerCli(
|
|
422
85
|
({ program }) => {
|
|
423
86
|
const algorand = program
|
|
424
87
|
.command("algorand-plugin")
|
|
425
88
|
.description("Algorand blockchain integration (GoPlausible)");
|
|
426
89
|
|
|
427
|
-
// Setup command - initializes (memory + mcporter) then runs interactive config
|
|
428
90
|
algorand
|
|
429
91
|
.command("setup")
|
|
430
|
-
.description("
|
|
92
|
+
.description("Reconfigure Algorand plugin (interactive)")
|
|
431
93
|
.action(async () => {
|
|
432
|
-
console.log("\n🔷
|
|
433
|
-
|
|
434
|
-
const workspacePath = getWorkspacePath(api);
|
|
94
|
+
console.log("\n🔷 Reconfiguring Algorand plugin...\n");
|
|
435
95
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (memResult.success) {
|
|
439
|
-
console.log(` ✅ ${memResult.message}`);
|
|
440
|
-
} else {
|
|
441
|
-
console.error(` ❌ ${memResult.message}`);
|
|
442
|
-
}
|
|
96
|
+
const mem = writeMemoryFile(PLUGIN_ROOT, workspacePath);
|
|
97
|
+
console.log(` ${mem.success ? "✅" : "❌"} ${mem.message}`);
|
|
443
98
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (memIndexResult.success) {
|
|
447
|
-
console.log(` ✅ ${memIndexResult.message}`);
|
|
448
|
-
} else {
|
|
449
|
-
console.error(` ❌ ${memIndexResult.message}`);
|
|
450
|
-
}
|
|
99
|
+
const memIdx = ensureWorkspaceMemoryIndex(PLUGIN_ROOT, workspacePath);
|
|
100
|
+
console.log(` ${memIdx.success ? "✅" : "❌"} ${memIdx.message}`);
|
|
451
101
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (stopResult.stopped > 0) {
|
|
455
|
-
console.log(` ✅ ${stopResult.message}`);
|
|
456
|
-
} else {
|
|
457
|
-
console.log(` ℹ️ ${stopResult.message}`);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Step 3: Configure mcporter
|
|
461
|
-
console.log("");
|
|
462
|
-
const mcpResult = configureMcporter();
|
|
463
|
-
if (mcpResult.success) {
|
|
464
|
-
console.log(` ✅ ${mcpResult.message}`);
|
|
465
|
-
} else {
|
|
466
|
-
console.log(` ⚠️ ${mcpResult.message}`);
|
|
467
|
-
}
|
|
102
|
+
const mcp = upsertMcporterConfig(PLUGIN_ROOT);
|
|
103
|
+
console.log(` ${mcp.success ? "✅" : "⚠️"} ${mcp.message}`);
|
|
468
104
|
|
|
469
|
-
// Step 4: Interactive config
|
|
470
105
|
console.log("");
|
|
471
106
|
const newConfig = await runSetup(pluginConfig);
|
|
472
107
|
if (newConfig) {
|
|
473
|
-
const result =
|
|
108
|
+
const result = writePluginConfig(newConfig as unknown as Record<string, unknown>);
|
|
474
109
|
if (result.success) {
|
|
475
110
|
console.log("\n✅ Config saved to ~/.openclaw/openclaw.json");
|
|
476
|
-
console.log("
|
|
477
|
-
console.log(" Restart gateway to apply changes: openclaw gateway restart\n");
|
|
111
|
+
console.log(" Restart gateway to apply: openclaw gateway restart\n");
|
|
478
112
|
} else {
|
|
479
113
|
console.error(`\n❌ Failed to save config: ${result.error}`);
|
|
480
|
-
console.log(" You can manually add to ~/.openclaw/openclaw.json:");
|
|
481
|
-
console.log(` "plugins": { "allow": ["${PLUGIN_ID}"], "entries": { "${PLUGIN_ID}": { "config": ${JSON.stringify(newConfig)} } } }\n`);
|
|
482
114
|
}
|
|
483
115
|
}
|
|
484
|
-
|
|
485
|
-
console.log(" Run `mcporter list algorand-mcp --schema` to verify MCP tools.\n");
|
|
486
116
|
});
|
|
487
117
|
|
|
488
|
-
// Status command
|
|
489
118
|
algorand
|
|
490
119
|
.command("status")
|
|
491
120
|
.description("Show Algorand plugin status")
|
|
492
121
|
.action(() => {
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
let mcporterConfigured = false;
|
|
497
|
-
try {
|
|
498
|
-
const list = execSync("mcporter config list 2>/dev/null || echo ''", { encoding: "utf-8" });
|
|
499
|
-
mcporterConfigured = list.includes("algorand-mcp");
|
|
500
|
-
} catch { }
|
|
122
|
+
const mcpBinary = getMcpBinaryPath(PLUGIN_ROOT);
|
|
123
|
+
const bundled = isMcpBinaryBundled(PLUGIN_ROOT);
|
|
124
|
+
const mcporterOk = isMcporterConfigured();
|
|
501
125
|
|
|
502
126
|
console.log("\n🔷 Algorand Plugin Status\n");
|
|
503
127
|
console.log(" Skills:");
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
console.log(
|
|
509
|
-
console.log(" • algorand-x402-python");
|
|
128
|
+
for (const s of [
|
|
129
|
+
"algorand-development", "algorand-typescript", "algorand-python",
|
|
130
|
+
"algorand-interaction", "algorand-x402-typescript", "algorand-x402-python",
|
|
131
|
+
"haystack-router-development", "haystack-router-interaction", "alpha-arcade-interaction",
|
|
132
|
+
]) console.log(` • ${s}`);
|
|
510
133
|
console.log("");
|
|
511
134
|
console.log(" MCP Server:");
|
|
512
|
-
console.log(` Binary: ${
|
|
513
|
-
console.log(` mcporter: ${
|
|
135
|
+
console.log(` Binary: ${bundled ? `✅ ${mcpBinary}` : "⚠️ Not bundled (reinstall plugin)"}`);
|
|
136
|
+
console.log(` mcporter: ${mcporterOk ? `✅ Configured (${mcporterConfigPath()})` : "⚠️ Not configured (run setup)"}`);
|
|
514
137
|
console.log("");
|
|
515
138
|
console.log(" Config:");
|
|
516
139
|
console.log(` x402: ${pluginConfig.enableX402 !== false ? "Enabled" : "Disabled"}`);
|
|
517
140
|
console.log("");
|
|
518
|
-
|
|
519
|
-
// Keyring status
|
|
520
|
-
try {
|
|
521
|
-
const scriptPath = join(__dirname, "scripts", "setup-keyring.sh");
|
|
522
|
-
const keyringOutput = execSync(`bash "${scriptPath}" --detect`, { encoding: "utf-8", timeout: 5000 });
|
|
523
|
-
const vars = Object.fromEntries(
|
|
524
|
-
keyringOutput.trim().split("\n").map((line: string) => line.split("=", 2))
|
|
525
|
-
);
|
|
526
|
-
console.log(" Keyring:");
|
|
527
|
-
if (vars.PERSISTENT === "true") {
|
|
528
|
-
console.log(` Storage: ✅ ${vars.BACKEND}`);
|
|
529
|
-
console.log(` Wallets: ${vars.WALLET_DB_COUNT} account(s) in wallet.db`);
|
|
530
|
-
} else {
|
|
531
|
-
console.log(` Storage: ⚠️ ${vars.BACKEND} — run \`openclaw algorand-plugin setup\``);
|
|
532
|
-
if (parseInt(vars.WALLET_DB_COUNT) > 0) {
|
|
533
|
-
console.log(` Wallets: ⚠️ ${vars.WALLET_DB_COUNT} account(s) with mnemonics in volatile storage!`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
} catch {
|
|
537
|
-
console.log(" Keyring:");
|
|
538
|
-
console.log(" Storage: ❓ Could not detect");
|
|
539
|
-
}
|
|
540
|
-
console.log("");
|
|
541
141
|
console.log(" Links:");
|
|
542
142
|
console.log(` GoPlausible: ${GOPLAUSIBLE_SERVICES.website}`);
|
|
543
143
|
console.log(` Algorand x402: ${GOPLAUSIBLE_SERVICES.x402}`);
|
|
@@ -546,18 +146,16 @@ export default function register(api: PluginApi) {
|
|
|
546
146
|
console.log("");
|
|
547
147
|
});
|
|
548
148
|
|
|
549
|
-
// MCP config helper (for external coding agents)
|
|
550
149
|
algorand
|
|
551
150
|
.command("mcp-config")
|
|
552
|
-
.description("Show MCP config snippet for coding agents (Claude Code, Cursor, etc.)")
|
|
151
|
+
.description("Show MCP config snippet for external coding agents (Claude Code, Cursor, etc.)")
|
|
553
152
|
.action(() => {
|
|
554
|
-
const
|
|
555
|
-
const command = mcp.path || "npx algorand-mcp";
|
|
153
|
+
const command = getMcpBinaryPath(PLUGIN_ROOT);
|
|
556
154
|
|
|
557
155
|
console.log("\n🔷 Algorand MCP Configuration\n");
|
|
558
156
|
console.log(" For external coding agents, add this to their MCP config:\n");
|
|
559
|
-
console.log(" Claude Code (.mcp.json):");
|
|
560
|
-
console.log("
|
|
157
|
+
console.log(" Claude Code (.mcp.json) / Cursor (.cursor/mcp.json):");
|
|
158
|
+
console.log(" ──────────────────────────────────────────────────");
|
|
561
159
|
console.log(` {`);
|
|
562
160
|
console.log(` "mcpServers": {`);
|
|
563
161
|
console.log(` "algorand": {`);
|
|
@@ -566,75 +164,22 @@ export default function register(api: PluginApi) {
|
|
|
566
164
|
console.log(` }`);
|
|
567
165
|
console.log(` }`);
|
|
568
166
|
console.log(` }\n`);
|
|
569
|
-
console.log(
|
|
570
|
-
console.log(
|
|
167
|
+
console.log(` OpenClaw uses mcporter (~/.mcporter/mcporter.json); the plugin registers`);
|
|
168
|
+
console.log(` algorand-mcp automatically on first load.\n`);
|
|
571
169
|
});
|
|
572
170
|
},
|
|
573
|
-
{ commands: ["algorand-plugin"] }
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
// ─────────────────────────────────────────────────────────────
|
|
577
|
-
// Post-install hook
|
|
578
|
-
// ─────────────────────────────────────────────────────────────
|
|
579
|
-
api.registerHook(
|
|
580
|
-
"plugin:post-install",
|
|
581
|
-
async () => {
|
|
582
|
-
console.log("\n🔷 Algorand plugin installed!\n");
|
|
583
|
-
console.log(" This plugin provides:");
|
|
584
|
-
console.log(" • 9 Algorand skills (Algorand development in TS and Python, x402, MCP interaction, alpha arcade interaction, haystack router development and interaction)");
|
|
585
|
-
console.log(" • algorand-mcp server (~100 blockchain tools via mcporter)\n");
|
|
586
|
-
|
|
587
|
-
// Ensure MEMORY.md exists at workspace root
|
|
588
|
-
const workspacePath = getWorkspacePath(api);
|
|
589
|
-
const memIndexResult = ensureWorkspaceMemoryIndex(workspacePath);
|
|
590
|
-
if (memIndexResult.success) {
|
|
591
|
-
console.log(` ✅ ${memIndexResult.message}`);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Keyring persistence warning for headless Linux
|
|
595
|
-
try {
|
|
596
|
-
const scriptPath = join(__dirname, "scripts", "setup-keyring.sh");
|
|
597
|
-
const keyringOutput = execSync(`bash "${scriptPath}" --detect`, { encoding: "utf-8", timeout: 5000 });
|
|
598
|
-
if (keyringOutput.includes("PERSISTENT=false")) {
|
|
599
|
-
console.log(" ⚠️ Headless Linux detected — wallet keys use in-memory storage.");
|
|
600
|
-
console.log(" Run `openclaw algorand-plugin setup` for persistent storage.\n");
|
|
601
|
-
}
|
|
602
|
-
} catch { /* ignore on install */ }
|
|
603
|
-
|
|
604
|
-
console.log(" Next steps:");
|
|
605
|
-
console.log(" 1. Run `openclaw algorand-plugin setup` — initialize + configure plugin");
|
|
606
|
-
console.log(" 2. Restart OpenClaw gateway\n");
|
|
607
|
-
console.log(` Docs: ${GOPLAUSIBLE_SERVICES.website}\n`);
|
|
608
|
-
},
|
|
609
|
-
{ name: "algorand.post-install", description: "Show setup instructions on install" }
|
|
171
|
+
{ commands: ["algorand-plugin"] },
|
|
610
172
|
);
|
|
611
173
|
|
|
612
|
-
|
|
613
|
-
// Post-update hook
|
|
614
|
-
// ─────────────────────────────────────────────────────────────
|
|
615
|
-
api.registerHook(
|
|
616
|
-
"plugin:post-update",
|
|
617
|
-
async () => {
|
|
618
|
-
console.log("\n🔷 Algorand plugin updated!\n");
|
|
619
|
-
|
|
620
|
-
// Ensure MEMORY.md and memory files are up to date
|
|
621
|
-
const workspacePath = getWorkspacePath(api);
|
|
622
|
-
const memResult = writeMemoryFile(workspacePath);
|
|
623
|
-
if (memResult.success) {
|
|
624
|
-
console.log(` ✅ ${memResult.message}`);
|
|
625
|
-
}
|
|
626
|
-
const memIndexResult = ensureWorkspaceMemoryIndex(workspacePath);
|
|
627
|
-
if (memIndexResult.success) {
|
|
628
|
-
console.log(` ✅ ${memIndexResult.message}`);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
console.log("\n Restart OpenClaw gateway to apply changes.\n");
|
|
632
|
-
},
|
|
633
|
-
{ name: "algorand.post-update", description: "Update memory files on plugin update" }
|
|
634
|
-
);
|
|
635
|
-
|
|
636
|
-
api.logger.info(`Algorand plugin registered (skills: 6, MCP: ${ALGORAND_MCP.name})`);
|
|
174
|
+
api.logger.info(`Algorand plugin registered (skills: 9, MCP: ${ALGORAND_MCP.name})`);
|
|
637
175
|
}
|
|
638
176
|
|
|
177
|
+
export default definePluginEntry({
|
|
178
|
+
id: PLUGIN_ID,
|
|
179
|
+
name: "Algorand Integration",
|
|
180
|
+
description: "Algorand blockchain integration with MCP and skills — by GoPlausible",
|
|
181
|
+
register,
|
|
182
|
+
});
|
|
183
|
+
|
|
639
184
|
export const id = PLUGIN_ID;
|
|
640
185
|
export const name = "Algorand Integration";
|