opencode-ralph-loop
Advanced tools
| // Non-function constants live here (not in index.ts) so opencode's plugin | ||
| // loader — which iterates Object.values(module) and rejects anything that | ||
| // isn't a function or `{server: fn}` — never sees them as exports of the | ||
| // entrypoint. See AGENTS.md gotcha. | ||
| export const STATE_FILENAME = "ralph-loop.local.md"; | ||
| export const STATE_DIR = ".opencode"; | ||
| export const STATE_PATH = `${STATE_DIR}/${STATE_FILENAME}`; | ||
| export interface RalphCommandDef { | ||
| description: string; | ||
| template: string; | ||
| agent: string; | ||
| } | ||
| // Inline slash-command templates. Mirror what commands/*.md used to be, | ||
| // minus the per-file YAML frontmatter. Registered at runtime via the | ||
| // `config` hook in index.ts. | ||
| export const RALPH_COMMANDS: Record<string, RalphCommandDef> = { | ||
| "ralph-loop": { | ||
| description: "Start Ralph Loop - auto-continues until task completion", | ||
| template: `Start an iterative development loop that automatically continues until the task is complete. | ||
| Create the state file in the project directory: | ||
| \`\`\`bash | ||
| mkdir -p ${STATE_DIR} && cat > ${STATE_PATH} << 'EOF' | ||
| --- | ||
| active: true | ||
| iteration: 0 | ||
| maxIterations: 100 | ||
| --- | ||
| $ARGUMENTS | ||
| EOF | ||
| \`\`\` | ||
| Now begin working on the task: **$ARGUMENTS** | ||
| When the task is FULLY completed, signal completion by outputting: | ||
| \`\`\` | ||
| <promise>DONE</promise> | ||
| \`\`\` | ||
| **IMPORTANT:** ONLY output this when the task is COMPLETELY and VERIFIABLY finished. Do NOT output false promises to escape the loop. | ||
| Use \`/cancel-ralph\` to stop early.`, | ||
| agent: "build", | ||
| }, | ||
| "cancel-ralph": { | ||
| description: "Cancel active Ralph Loop", | ||
| template: `Cancel the active Ralph Loop. | ||
| \`\`\`bash | ||
| if [ -f ${STATE_PATH} ]; then | ||
| grep '^iteration:' ${STATE_PATH} | ||
| rm -f ${STATE_PATH} | ||
| echo "Ralph Loop cancelled." | ||
| else | ||
| echo "No active Ralph Loop to cancel." | ||
| fi | ||
| \`\`\` | ||
| Report the result to the user.`, | ||
| agent: "build", | ||
| }, | ||
| "help": { | ||
| description: "Show Ralph Loop plugin help and available commands", | ||
| template: `# Ralph Loop Help | ||
| ## Available Commands | ||
| - \`/ralph-loop <task>\` - Start an auto-continuation loop for the given task | ||
| - \`/cancel-ralph\` - Stop an active Ralph Loop | ||
| ## Quick Start | ||
| \`\`\` | ||
| /ralph-loop Build a REST API with user authentication | ||
| \`\`\` | ||
| The AI will work on your task and automatically continue until it outputs \`<promise>DONE</promise>\` to signal completion. | ||
| ## How It Works | ||
| 1. Creates state file at \`${STATE_PATH}\` | ||
| 2. Works on task until idle | ||
| 3. If no \`<promise>DONE</promise>\` found, auto-continues | ||
| 4. Repeats until complete or max iterations (100) reached | ||
| For more details, the AI can use the \`help\` skill.`, | ||
| agent: "build", | ||
| }, | ||
| }; |
+1
-1
| { | ||
| "name": "opencode-ralph-loop", | ||
| "version": "1.0.9", | ||
| "version": "1.0.10", | ||
| "description": "Minimal Ralph Loop plugin for opencode - auto-continues until task completion", | ||
@@ -5,0 +5,0 @@ "main": "src/index.ts", |
+33
-40
@@ -7,2 +7,3 @@ import { tool } from "@opencode-ai/plugin"; | ||
| import { COMPLETION_TAG } from "./completion.ts"; | ||
| import { RALPH_COMMANDS, STATE_FILENAME } from "./commands.ts"; | ||
@@ -18,4 +19,2 @@ // Types | ||
| // Constants | ||
| const STATE_FILENAME = "ralph-loop.local.md"; | ||
| const OPENCODE_CONFIG_DIR = join(homedir(), ".config/opencode"); | ||
@@ -35,45 +34,27 @@ | ||
| // Auto-copy skills and commands to opencode config on first run | ||
| function setupSkillsAndCommands(): void { | ||
| // Auto-copy skills to opencode config on first run. opencode reads from | ||
| // ~/.config/opencode/skills/ (plural). Earlier versions wrote to /skill/ | ||
| // (singular) — that path is no longer scanned. Slash commands are NOT | ||
| // copied here anymore; they self-register via the `config` hook below. | ||
| function setupSkills(): void { | ||
| const pluginRoot = getPluginRoot(); | ||
| const skillsDir = join(OPENCODE_CONFIG_DIR, "skill"); | ||
| const commandsDir = join(OPENCODE_CONFIG_DIR, "command"); | ||
| const skillsDir = join(OPENCODE_CONFIG_DIR, "skills"); | ||
| // Copy skills | ||
| const pluginSkillsDir = join(pluginRoot, "skills"); | ||
| if (existsSync(pluginSkillsDir)) { | ||
| const skills = ["ralph-loop", "cancel-ralph", "help"]; | ||
| for (const skill of skills) { | ||
| const srcSkillDir = join(pluginSkillsDir, skill); | ||
| const destSkillDir = join(skillsDir, skill); | ||
| if (!existsSync(pluginSkillsDir)) return; | ||
| if (existsSync(srcSkillDir) && !existsSync(destSkillDir)) { | ||
| try { | ||
| mkdirSync(destSkillDir, { recursive: true }); | ||
| cpSync(srcSkillDir, destSkillDir, { recursive: true }); | ||
| } catch { | ||
| // Silent fail | ||
| } | ||
| // Skills directories are co-located with command names; deriving from | ||
| // RALPH_COMMANDS keeps the two in lockstep when new commands are added. | ||
| for (const skill of Object.keys(RALPH_COMMANDS)) { | ||
| const srcSkillDir = join(pluginSkillsDir, skill); | ||
| const destSkillDir = join(skillsDir, skill); | ||
| if (existsSync(srcSkillDir) && !existsSync(destSkillDir)) { | ||
| try { | ||
| mkdirSync(destSkillDir, { recursive: true }); | ||
| cpSync(srcSkillDir, destSkillDir, { recursive: true }); | ||
| } catch { | ||
| // Silent fail | ||
| } | ||
| } | ||
| } | ||
| // Copy commands | ||
| const pluginCommandsDir = join(pluginRoot, "commands"); | ||
| if (existsSync(pluginCommandsDir)) { | ||
| const commands = ["ralph-loop.md", "cancel-ralph.md", "help.md"]; | ||
| for (const cmd of commands) { | ||
| const srcCmd = join(pluginCommandsDir, cmd); | ||
| const destCmd = join(commandsDir, cmd); | ||
| if (existsSync(srcCmd) && !existsSync(destCmd)) { | ||
| try { | ||
| mkdirSync(commandsDir, { recursive: true }); | ||
| cpSync(srcCmd, destCmd); | ||
| } catch { | ||
| // Silent fail | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
@@ -194,6 +175,18 @@ | ||
| // Auto-setup skills and commands on first run | ||
| setupSkillsAndCommands(); | ||
| // Auto-setup skills on first run. Slash commands self-register via the | ||
| // `config` hook below. | ||
| setupSkills(); | ||
| return { | ||
| // Self-register slash commands. opencode's `config` hook fires while | ||
| // assembling the runtime config; mutating `input.command` adds entries | ||
| // to the command dict, no file copying needed. User-defined entries in | ||
| // opencode.json take precedence (we only set absent names). | ||
| config: async (input: any) => { | ||
| input.command = input.command ?? {}; | ||
| for (const [name, def] of Object.entries(RALPH_COMMANDS)) { | ||
| if (!input.command[name]) input.command[name] = def; | ||
| } | ||
| }, | ||
| // Register tools using the @opencode-ai/plugin SDK format. | ||
@@ -200,0 +193,0 @@ // The `args` field (Zod schema shape) is required — opencode calls |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
26187
12.26%12
9.09%342
24.82%