import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { execFileSync } from "node:child_process"; import path from "node:path"; import os from "node:os"; import fs from "node:fs"; function getSkillBaseDir(api: OpenClawPluginApi): string { return (api.config as Record)?.["dirigentStateDir"] as string || path.join(os.homedir(), ".openclaw"); } function parseGuildTable(skillMdContent: string): Array<{ guildId: string; description: string }> { const lines = skillMdContent.split("\n"); const rows: Array<{ guildId: string; description: string }> = []; let inTable = false; for (const line of lines) { // Detect table header if (line.includes("guild-id") && line.includes("description")) { inTable = true; continue; } // Skip separator line if (inTable && /^\|[-\s|]+\|$/.test(line)) { continue; } // Parse data rows if (inTable) { const match = line.match(/^\| \s*(\d+) \s*\| \s*(.+?) \s*\|$/); if (match) { rows.push({ guildId: match[1].trim(), description: match[2].trim() }); } } } return rows; } export function registerAddGuildCommand(api: OpenClawPluginApi): void { // Register add-guild command api.registerCommand({ name: "add-guild", description: "Add a Discord guild to the discord-guilds skill", acceptsArgs: true, handler: async (cmdCtx) => { const args = (cmdCtx.args || "").trim(); if (!args) { return { text: "Usage: /add-guild \nExample: /add-guild 123456789012345678 \"Production server\"", isError: true, }; } const parts = args.split(/\s+/); if (parts.length < 2) { return { text: "Error: Both guild-id and description are required.\nUsage: /add-guild ", isError: true, }; } const guildId = parts[0]; const description = parts.slice(1).join(" "); // Validate guild ID if (!/^\d+$/.test(guildId)) { return { text: "Error: guild-id must be a numeric Discord snowflake (e.g., 123456789012345678)", isError: true, }; } // Resolve the skill script path const openClawDir = getSkillBaseDir(api); const scriptPath = path.join(openClawDir, "skills", "discord-guilds", "scripts", "add-guild"); try { const result = execFileSync(process.execPath, [scriptPath, guildId, description], { encoding: "utf8", timeout: 5000, }); return { text: result.trim() }; } catch (e: any) { const stderr = e?.stderr?.toString?.() || ""; const stdout = e?.stdout?.toString?.() || ""; return { text: `Failed to add guild: ${stderr || stdout || String(e)}`, isError: true, }; } }, }); // Register list-guilds command api.registerCommand({ name: "list-guilds", description: "List all Discord guilds in the discord-guilds skill", acceptsArgs: false, handler: async () => { const openClawDir = getSkillBaseDir(api); const skillMdPath = path.join(openClawDir, "skills", "discord-guilds", "SKILL.md"); if (!fs.existsSync(skillMdPath)) { return { text: "Error: discord-guilds skill not found. Run Dirigent install first.", isError: true, }; } try { const content = fs.readFileSync(skillMdPath, "utf8"); const guilds = parseGuildTable(content); if (guilds.length === 0) { return { text: "No guilds configured yet.\n\nUse /add-guild to add one." }; } const lines = [ `**Available Guilds (${guilds.length}):**`, "", "| guild-id | description |", "|----------|-------------|", ...guilds.map(g => `| ${g.guildId} | ${g.description} |`), "", "Use /add-guild to add more.", ]; return { text: lines.join("\n") }; } catch (e: any) { return { text: `Failed to read guild list: ${String(e)}`, isError: true, }; } }, }); }