@goplausible/openclaw-algorand-plugin 1.9.5 → 2.0.1

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 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 { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
5
- import { dirname, join } from "node:path";
6
- import { homedir } from "node:os";
7
- import { fileURLToPath } from "node:url";
8
- import { execSync } from "node:child_process";
9
-
10
- const __dirname = dirname(fileURLToPath(import.meta.url));
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
- interface PluginApi {
14
- config: {
15
- plugins?: {
16
- entries?: {
17
- "openclaw-algorand-plugin"?: {
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
- registerHook: (event: string, handler: () => Promise<void>, meta: object) => void;
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
- // Create config file with empty object if it doesn't exist
71
- let config: Record<string, any> = {};
72
- if (existsSync(configPath)) {
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
- function configureMcporter(): { success: boolean; message: string } {
139
- const mcpCommand = getMcpBinaryPath();
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(_id: string, params: {
404
- url: string;
405
- method?: string;
406
- headers?: Record<string, string>;
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("Initialize and configure Algorand plugin")
92
+ .description("Reconfigure Algorand plugin (interactive)")
431
93
  .action(async () => {
432
- console.log("\n🔷 Setting up Algorand plugin...\n");
433
-
434
- const workspacePath = getWorkspacePath(api);
94
+ console.log("\n🔷 Reconfiguring Algorand plugin...\n");
435
95
 
436
- // Step 1: Write memory file
437
- const memResult = writeMemoryFile(workspacePath);
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
- // Step 1b: Ensure MEMORY.md exists at workspace root with NEVER FORGET section
445
- const memIndexResult = ensureWorkspaceMemoryIndex(workspacePath);
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
- // Step 2: Stop any existing algorand-mcp processes
453
- const stopResult = stopExistingMcpProcesses();
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 = updatePluginConfig(newConfig);
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(" Plugin added to plugins.allow list.");
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 mcp = checkMcpBinary();
494
-
495
- // Check mcporter config
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
- console.log(" • algorand-development");
505
- console.log(" algorand-typescript");
506
- console.log(" algorand-python");
507
- console.log(" algorand-interaction");
508
- console.log("algorand-x402-typescript");
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: ${mcp.available ? `✅ ${mcp.path}` : "⚠️ Not found"}`);
513
- console.log(` mcporter: ${mcporterConfigured ? "✅ Configured" : "⚠️ Not configured (run setup)"}`);
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 mcp = checkMcpBinary();
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(" Cursor (.cursor/mcp.json) same format\n");
570
- console.log(" Note: OpenClaw uses mcporter. Run `openclaw algorand-plugin setup` to configure.\n");
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";