@pageai/ralph-loop 1.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.
Files changed (120) hide show
  1. package/.agent/PROMPT.md +58 -0
  2. package/.agent/STEERING.md +3 -0
  3. package/.agent/logs/LOG.md +13 -0
  4. package/.agent/prd/.gitkeep +0 -0
  5. package/.agent/screenshots/.gitkeep +0 -0
  6. package/.agent/skills/component-refactoring/SKILL.md +247 -0
  7. package/.agent/skills/component-refactoring/references/complexity-patterns.md +485 -0
  8. package/.agent/skills/component-refactoring/references/component-splitting.md +419 -0
  9. package/.agent/skills/component-refactoring/references/hook-extraction.md +317 -0
  10. package/.agent/skills/e2e-tester/SKILL.md +595 -0
  11. package/.agent/skills/frontend-code-review/SKILL.md +73 -0
  12. package/.agent/skills/frontend-code-review/references/code-quality.md +28 -0
  13. package/.agent/skills/frontend-code-review/references/performance.md +36 -0
  14. package/.agent/skills/frontend-testing/SKILL.md +316 -0
  15. package/.agent/skills/frontend-testing/assets/component-test.template.tsx +293 -0
  16. package/.agent/skills/frontend-testing/assets/hook-test.template.ts +207 -0
  17. package/.agent/skills/frontend-testing/assets/utility-test.template.ts +154 -0
  18. package/.agent/skills/frontend-testing/references/async-testing.md +345 -0
  19. package/.agent/skills/frontend-testing/references/checklist.md +188 -0
  20. package/.agent/skills/frontend-testing/references/common-patterns.md +449 -0
  21. package/.agent/skills/frontend-testing/references/mocking.md +289 -0
  22. package/.agent/skills/frontend-testing/references/workflow.md +265 -0
  23. package/.agent/skills/prd-creator/JSON.md +613 -0
  24. package/.agent/skills/prd-creator/PRD.md +196 -0
  25. package/.agent/skills/prd-creator/SKILL.md +143 -0
  26. package/.agent/skills/skill-creator/SKILL.md +355 -0
  27. package/.agent/skills/skill-creator/references/output-patterns.md +86 -0
  28. package/.agent/skills/skill-creator/references/workflows.md +28 -0
  29. package/.agent/skills/skill-creator/scripts/init_skill.py +300 -0
  30. package/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
  31. package/.agent/skills/vercel-react-best-practices/AGENTS.md +2249 -0
  32. package/.agent/skills/vercel-react-best-practices/SKILL.md +125 -0
  33. package/.agent/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  34. package/.agent/skills/vercel-react-best-practices/rules/advanced-use-latest.md +49 -0
  35. package/.agent/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  36. package/.agent/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  37. package/.agent/skills/vercel-react-best-practices/rules/async-dependencies.md +36 -0
  38. package/.agent/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  39. package/.agent/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  40. package/.agent/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  41. package/.agent/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  42. package/.agent/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  43. package/.agent/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  44. package/.agent/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  45. package/.agent/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  46. package/.agent/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  47. package/.agent/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +82 -0
  48. package/.agent/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  49. package/.agent/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  50. package/.agent/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  51. package/.agent/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  52. package/.agent/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  53. package/.agent/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  54. package/.agent/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  55. package/.agent/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  56. package/.agent/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  57. package/.agent/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  58. package/.agent/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  59. package/.agent/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  60. package/.agent/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  61. package/.agent/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  62. package/.agent/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  63. package/.agent/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  64. package/.agent/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  65. package/.agent/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  66. package/.agent/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  67. package/.agent/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  68. package/.agent/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  69. package/.agent/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  70. package/.agent/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  71. package/.agent/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  72. package/.agent/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  73. package/.agent/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  74. package/.agent/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  75. package/.agent/skills/vercel-react-best-practices/rules/server-cache-react.md +26 -0
  76. package/.agent/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +79 -0
  77. package/.agent/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  78. package/.agent/skills/vitest-best-practices/AGENTS.md +84 -0
  79. package/.agent/skills/vitest-best-practices/SKILL.md +130 -0
  80. package/.agent/skills/vitest-best-practices/references/aaa-pattern.md +260 -0
  81. package/.agent/skills/vitest-best-practices/references/assertions.md +393 -0
  82. package/.agent/skills/vitest-best-practices/references/async-testing.md +454 -0
  83. package/.agent/skills/vitest-best-practices/references/error-handling.md +382 -0
  84. package/.agent/skills/vitest-best-practices/references/organization.md +212 -0
  85. package/.agent/skills/vitest-best-practices/references/parameterized-tests.md +297 -0
  86. package/.agent/skills/vitest-best-practices/references/performance.md +528 -0
  87. package/.agent/skills/vitest-best-practices/references/snapshot-testing.md +483 -0
  88. package/.agent/skills/vitest-best-practices/references/test-doubles.md +499 -0
  89. package/.agent/skills/vitest-best-practices/references/vitest-features.md +529 -0
  90. package/.agent/skills/web-design-guidelines/SKILL.md +39 -0
  91. package/.agent/tasks/.gitkeep +0 -0
  92. package/.agent/tasks.json +1 -0
  93. package/.claude/agents/code-reviewer.md +172 -0
  94. package/.claude/commands/aw.md +50 -0
  95. package/.claude/hooks/play-sound.js +87 -0
  96. package/.claude/hooks/pre-tool-use.js +40 -0
  97. package/.claude/settings.json +54 -0
  98. package/.claude/settings.local.json +13 -0
  99. package/.mcp.json +31 -0
  100. package/AGENTS.md +44 -0
  101. package/CLAUDE.md +1 -0
  102. package/README.md +236 -0
  103. package/bin/cli.js +156 -0
  104. package/bin/lib/copy.js +149 -0
  105. package/bin/lib/display.js +137 -0
  106. package/package.json +65 -0
  107. package/ralph.sh +333 -0
  108. package/scripts/lib/args.sh +44 -0
  109. package/scripts/lib/cleanup.sh +53 -0
  110. package/scripts/lib/constants.sh +25 -0
  111. package/scripts/lib/display.sh +196 -0
  112. package/scripts/lib/logging.sh +30 -0
  113. package/scripts/lib/notify.sh +41 -0
  114. package/scripts/lib/output.sh +147 -0
  115. package/scripts/lib/preflight.sh +57 -0
  116. package/scripts/lib/preview.sh +77 -0
  117. package/scripts/lib/promise.sh +76 -0
  118. package/scripts/lib/spinner.sh +85 -0
  119. package/scripts/lib/terminal.sh +57 -0
  120. package/scripts/lib/timing.sh +223 -0
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # Ralph Loop
2
+
3
+ A long-running AI agent loop. Ralph automates software development tasks by iteratively working through a task list until completion.
4
+
5
+ This is a hackable script so you can configure it to your env and favorite agentic AI CLI. It's set up by default to use Claude Code in a Docker sandbox.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ # Run the agent loop (default: 10 iterations)
11
+ ./ralph.sh
12
+
13
+ # Run with custom iteration limit
14
+ ./ralph.sh 5
15
+ ./ralph.sh -n 5
16
+ ./ralph.sh --max-iterations 5
17
+
18
+ # Run exactly one iteration
19
+ ./ralph.sh --once
20
+
21
+ # Show help
22
+ ./ralph.sh --help
23
+ ```
24
+
25
+ > NB: you might need to run `chmod +x ralph.sh` to make the script executable.
26
+
27
+ ## How It Works
28
+
29
+ Each iteration, Ralph will:
30
+ 1. Find the highest-priority incomplete task from `.agent/tasks.json`
31
+ 2. Work through the task steps defined in `.agent/tasks/TASK-{ID}.json`
32
+ 3. Run tests, linting, and type checking
33
+ 4. Update task status and commit changes
34
+ 5. Repeat until all tasks pass or max iterations reached
35
+
36
+ ## How Is This Different from Other Ralphs?
37
+
38
+ This was kept hackable so you can make it your own.<br/>
39
+ The script follows the original concepts of the Ralph Wiggum Loop, working with fresh contexts and providing clear verifiable feedback.
40
+
41
+ It also works generically with any task set.
42
+
43
+ Besides that:
44
+
45
+ - it allows you to dump unstructured requirements and have the agent create a PRD and task list for you.
46
+ - it uses a task lookup table with individual detailed steps -> more scalable as you get 100s of tasks done.
47
+ - it's sandboxed and more secure
48
+ - it shows progress and stats so you can keep an eye on what's been done
49
+ - it instructs the agent to write and run automated tests and screenshots per task
50
+ - it provides observability and traceability of the agent's work, showing a stream of output and capturing full historical logs per iteration
51
+
52
+ ## Getting Started
53
+
54
+ ### Step 1: Create a PRD
55
+
56
+ Use the `prd-creator` skill to generate a PRD from your requirements:
57
+
58
+ ```
59
+ Use the prd-creator skill to help me create a PRD and task list for these requirements:
60
+
61
+ - A SaaS product that helps users manage their finances.
62
+ - Target audience: Small business owners and freelancers.
63
+ - Core features:
64
+ - Track income and expenses.
65
+ - Create and send invoices.
66
+ - Track payments and receipts.
67
+ - Generate reports and insights.
68
+ - Connect to bank accounts and credit cards.
69
+ - Connect to accounting software.
70
+ - Connect to payment processors.
71
+ - Next.js web app with Tailwind CSS and TypeScript.
72
+ - Use the shadcn/ui library for components.
73
+
74
+ // etc.
75
+ ```
76
+
77
+ Follow the skill's instructions and verify the PRD and then tasks.
78
+ **It is highly recommended that you review individual task requirements before starting the loop. Review EACH TASK INDIVIDUALLY.**
79
+
80
+ ### Step 3: Set up the agent inside Docker sandbox
81
+
82
+ Authenticate inside the Docker sandbox before running Ralph. Run:
83
+
84
+ ```bash
85
+ docker sandbox run --credentials host claude
86
+ ```
87
+
88
+ And follow the instructions to log in into Claude Code.
89
+
90
+ > Answer "Yes" to "Bypass Permissions mode", that's the exact reason why you are using the Docker sandbox.
91
+
92
+ ### Step 4: Run Ralph
93
+
94
+ ```bash
95
+ ./ralph.sh -n 50 # Run Ralph Loop with 50 iterations
96
+ ```
97
+
98
+ ### Adjusting to your language/framework
99
+
100
+ This script assumes the following are installed:
101
+ - [Playwright](https://playwright.dev/) for e2e testing
102
+ - [Vitest](https://vitest.dev/) for unit testing
103
+ - [TypeScript](https://www.typescriptlang.org/) for type checking
104
+ - [ESLint](https://eslint.org/) for linting
105
+ - [Prettier](https://prettier.io/) for formatting
106
+
107
+ I recommend using a CLI to bootstrap your project with the necessary tools and dependencies, e.g.:
108
+
109
+ ```bash
110
+ npx create-vite@latest my-app --template react-ts
111
+ # or
112
+ npx create-next-app@latest my-app
113
+ ```
114
+
115
+ If you must start from a blank slate, which is not recommended, you can use the following commands to install the necessary tools and dependencies:
116
+
117
+ Install with:
118
+
119
+ ```bash
120
+ npm i @playwright/test vitest jsdom typescript eslint prettier -D
121
+
122
+ # If using React, also recommend installing:
123
+ npm i @vitejs/plugin-react @testing-library/dom @testing-library/jest-dom @testing-library/react @testing-library/user-event -D
124
+ ```
125
+
126
+ --------------------------------
127
+
128
+ ⚠️ If you are using a different language or testing framework, please adjust `.agent/PROMPT.md` to reflect your setup, server ports and startup commands etc.
129
+
130
+ ⚠️ The default "mode" is "implementation". Depending on your use case, you might want to change `.agent/PROMPT.md` to a different mode, e.g. "refactor", "review", "test" etc.
131
+
132
+ ## Steering the Agent
133
+
134
+ In some cases, you might notice the agent is having trouble, slowed down or struggling to overcome a blocker.
135
+
136
+ While the loop is running, you can edit the `.agent/STEERING.md` file to add critical work that needs to be done before the loop can continue.
137
+
138
+ The agent will check this file each iteration and if it finds any critical work, it will skip tasks and complete the critical work first.
139
+
140
+ ## Features
141
+
142
+ - **PRD generation** - Creates a PRD and task list from requirements
143
+ - **Task lookup table generation** - Creates a task lookup table from the PRD
144
+ - **Task breakdown + step generation** - Breaks down each task into manageable steps
145
+ - **Iteration tracking** - Shows progress through iterations with timing
146
+ - **Stream preview** - Shows live output from the Agent
147
+ - **Step detection** - Identifies current activity (Thinking, Implementing, Testing, etc.)
148
+ - **Screenshot capture** - Captures a screenshot of the current screen
149
+ - **Notifications** - Alerts when human input is needed
150
+ - **History logging** - Saves clean output from each iteration
151
+ - **Timing** - Shows timing metrics for each iteration and total time
152
+
153
+ ## Support
154
+
155
+ The `ralph.sh` script is designed to be hackable.
156
+ It is configured to use Claude Code in a Docker sandbox by default, but with a one-liner change you can change it to use any other agentic AI CLI.
157
+
158
+ Check the `ralph.sh` script around `# 👉 This is the main command loop.` for the main command loop.
159
+
160
+ > NB: skills are supported by all major agentic AI CLIs via symlinks.
161
+
162
+ ### Promise Tags
163
+
164
+ Ralph uses semantic tags to communicate status:
165
+ - `<promise>COMPLETE</promise>` - All tasks finished successfully
166
+ - `<promise>BLOCKED:reason</promise>` - Agent needs human help
167
+ - `<promise>DECIDE:question</promise>` - Agent needs a decision
168
+
169
+ ### Exit Codes
170
+
171
+ | Code | Meaning |
172
+ | ---- | ------------------------------ |
173
+ | 0 | COMPLETE - All tasks finished |
174
+ | 1 | MAX_ITERATIONS - Reached limit |
175
+ | 2 | BLOCKED - Needs human help |
176
+ | 3 | DECIDE - Needs human decision |
177
+
178
+ ## Structure
179
+
180
+ ```
181
+ .agent/
182
+ ├── PROMPT.md # Prompt sent to Agent each iteration
183
+ ├── tasks.json # Task lookup table (required)
184
+ ├── tasks/ # Individual task specs (TASK-{ID}.json)
185
+ ├── prd/
186
+ │ ├── PRD.md # Product requirements document
187
+ │ └── SUMMARY.md # Short project overview sent to Agent each iteration
188
+ ├── logs/
189
+ │ └── LOG.md # Progress log (auto-created)
190
+ ├── history/ # Iteration output logs
191
+ └── skills/ # Shared skills (source of truth)
192
+ ```
193
+
194
+ ## Skills
195
+
196
+ Skills are reusable agent capabilities that provide specialized knowledge and workflows. The canonical source is `.agent/skills/`, which is symlinked to multiple agent tool directories for compatibility.
197
+
198
+ ### Available Skills
199
+
200
+ | Skill | Description |
201
+ | ----------------------------- | ------------------------------------------------------- |
202
+ | `component-refactoring` | Patterns for splitting and refactoring React components |
203
+ | `e2e-tester` | End-to-end testing workflows |
204
+ | `frontend-code-review` | Code quality and performance review guidelines |
205
+ | `frontend-testing` | Unit and integration testing patterns |
206
+ | `prd-creator` | Create PRDs and task breakdowns for Ralph |
207
+ | `skill-creator` | Create new skills |
208
+ | `vercel-react-best-practices` | React/Next.js performance patterns |
209
+ | `web-design-guidelines` | UI/UX design principles |
210
+
211
+ ### Skills Directory Structure
212
+
213
+ Skills are symlinked from `.agent/skills/` to multiple locations for cross-tool compatibility:
214
+
215
+ ```
216
+ # Source of truth
217
+ .agent/skills/
218
+ ├── component-refactoring/
219
+ ├── e2e-tester/
220
+ ├── frontend-code-review/
221
+ ├── frontend-testing/
222
+ ├── prd-creator/
223
+ ├── skill-creator/
224
+ ├── vercel-react-best-practices/
225
+ └── web-design-guidelines/
226
+
227
+ # Symlinks -> .agent/skills/*
228
+ .agents/skills/
229
+ .claude/skills/
230
+ .codex/skills/
231
+ .cursor/skills/
232
+ ```
233
+
234
+ ## License
235
+
236
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI script for setting up a Ralph Loop in the current directory.
5
+ * Copies required files and folders, preserving symlinks.
6
+ * Merges directories that should preserve existing user files.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const display = require('./lib/display');
12
+ const { copyFile, copyDir, mergeDir, exists } = require('./lib/copy');
13
+
14
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
15
+ const TARGET_DIR = process.cwd();
16
+
17
+ // Files to copy (always overwrite)
18
+ const FILES_TO_COPY = [
19
+ 'ralph.sh',
20
+ 'CLAUDE.md',
21
+ 'AGENTS.md',
22
+ '.mcp.json',
23
+ ];
24
+
25
+ // Single files within directories (always overwrite)
26
+ const CONFIG_FILES = [
27
+ '.agent/PROMPT.md',
28
+ '.agent/STEERING.md',
29
+ '.agent/tasks.json',
30
+ '.agent/history/.gitignore',
31
+ ];
32
+
33
+ // Directories to fully copy (overwrite)
34
+ const DIRS_TO_COPY = [
35
+ 'scripts',
36
+ '.agent/logs',
37
+ '.agent/prd',
38
+ '.agent/screenshots',
39
+ '.agent/tasks',
40
+ ];
41
+
42
+ // Directories to merge (preserve existing files, add missing ones)
43
+ const DIRS_TO_MERGE = [
44
+ '.agent/skills',
45
+ '.agents/skills',
46
+ '.claude/skills',
47
+ '.claude/agents',
48
+ '.claude/commands',
49
+ '.claude/hooks',
50
+ '.codex/skills',
51
+ '.cursor/skills',
52
+ ];
53
+
54
+ // Other files in these directories to copy (with exclusions)
55
+ const EXTRA_DIR_FILES = [
56
+ { dir: '.claude', exclude: ['settings.local.json', 'skills', 'agents', 'commands', 'hooks'] },
57
+ ];
58
+
59
+ function main() {
60
+ display.showRalph();
61
+
62
+ // Check if we're in the package directory itself
63
+ if (path.resolve(TARGET_DIR) === path.resolve(PACKAGE_ROOT)) {
64
+ display.printError('Cannot run setup in the package directory itself.');
65
+ process.exit(1);
66
+ }
67
+
68
+ display.printLocation(TARGET_DIR);
69
+
70
+ // Copy individual files
71
+ display.printStep('📄', 'Core files');
72
+ for (const file of FILES_TO_COPY) {
73
+ const src = path.join(PACKAGE_ROOT, file);
74
+ const dest = path.join(TARGET_DIR, file);
75
+ if (exists(src)) {
76
+ copyFile(src, dest);
77
+ display.printSuccess(file);
78
+ } else {
79
+ display.printWarning(`${file} not found, skipping`);
80
+ }
81
+ }
82
+
83
+ // Copy config files
84
+ console.log();
85
+ display.printStep('⚙️ ', 'Config files');
86
+ for (const file of CONFIG_FILES) {
87
+ const src = path.join(PACKAGE_ROOT, file);
88
+ const dest = path.join(TARGET_DIR, file);
89
+ if (exists(src)) {
90
+ copyFile(src, dest);
91
+ display.printSuccess(file);
92
+ } else {
93
+ display.printWarning(`${file} not found, skipping`);
94
+ }
95
+ }
96
+
97
+ // Copy directories (full overwrite)
98
+ console.log();
99
+ display.printStep('📁', 'Directories');
100
+ for (const dir of DIRS_TO_COPY) {
101
+ const src = path.join(PACKAGE_ROOT, dir);
102
+ const dest = path.join(TARGET_DIR, dir);
103
+ if (exists(src)) {
104
+ copyDir(src, dest);
105
+ display.printSuccess(`${dir}/`);
106
+ } else {
107
+ display.printWarning(`${dir}/ not found, skipping`);
108
+ }
109
+ }
110
+
111
+ // Merge directories (preserve existing, add missing)
112
+ console.log();
113
+ display.printStep('🔗', 'Skills & tools (merge)');
114
+ for (const dir of DIRS_TO_MERGE) {
115
+ const src = path.join(PACKAGE_ROOT, dir);
116
+ const dest = path.join(TARGET_DIR, dir);
117
+ if (exists(src)) {
118
+ const result = mergeDir(src, dest);
119
+ if (result.skipped > 0 && result.copied === 0) {
120
+ display.printSkipped(`${dir}/`);
121
+ } else if (result.skipped > 0) {
122
+ display.printSuccess(`${dir}/ (+${result.copied} new, ${result.skipped} kept)`);
123
+ } else {
124
+ display.printSuccess(`${dir}/`);
125
+ }
126
+ } else {
127
+ display.printWarning(`${dir}/ not found, skipping`);
128
+ }
129
+ }
130
+
131
+ // Copy extra files from directories (with exclusions)
132
+ console.log();
133
+ display.printStep('📋', 'Extra config files');
134
+ for (const { dir, exclude } of EXTRA_DIR_FILES) {
135
+ const src = path.join(PACKAGE_ROOT, dir);
136
+ const dest = path.join(TARGET_DIR, dir);
137
+ if (exists(src)) {
138
+ const entries = fs.readdirSync(src, { withFileTypes: true });
139
+ for (const entry of entries) {
140
+ if (entry.name === '.DS_Store') continue;
141
+ if (exclude.includes(entry.name)) continue;
142
+ if (entry.isDirectory()) continue; // Skip subdirs, handled separately
143
+
144
+ const srcFile = path.join(src, entry.name);
145
+ const destFile = path.join(dest, entry.name);
146
+ copyFile(srcFile, destFile);
147
+ display.printSuccess(`${dir}/${entry.name}`);
148
+ }
149
+ }
150
+ }
151
+
152
+ // Success message
153
+ display.showComplete();
154
+ }
155
+
156
+ main();
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Copy module for Ralph Loop CLI
3
+ * Handles file/directory copying with merge support
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Ensures a directory exists, creating it recursively if needed
11
+ */
12
+ function ensureDir(dirPath) {
13
+ if (!fs.existsSync(dirPath)) {
14
+ fs.mkdirSync(dirPath, { recursive: true });
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Copies a single file, preserving executable permissions
20
+ * @param {string} src - Source file path
21
+ * @param {string} dest - Destination file path
22
+ * @param {boolean} overwrite - If false, skip if destination exists
23
+ * @returns {{ copied: boolean, skipped: boolean }}
24
+ */
25
+ function copyFile(src, dest, overwrite = true) {
26
+ if (!overwrite && fs.existsSync(dest)) {
27
+ return { copied: false, skipped: true };
28
+ }
29
+
30
+ ensureDir(path.dirname(dest));
31
+ fs.copyFileSync(src, dest);
32
+
33
+ // Preserve executable permission
34
+ const stats = fs.statSync(src);
35
+ if (stats.mode & 0o111) {
36
+ fs.chmodSync(dest, stats.mode);
37
+ }
38
+
39
+ return { copied: true, skipped: false };
40
+ }
41
+
42
+ /**
43
+ * Copies a symlink
44
+ * @param {string} src - Source symlink path
45
+ * @param {string} dest - Destination path
46
+ * @param {boolean} overwrite - If false, skip if destination exists
47
+ * @returns {{ copied: boolean, skipped: boolean }}
48
+ */
49
+ function copySymlink(src, dest, overwrite = true) {
50
+ // Check if dest exists (as file or symlink)
51
+ let destExists = false;
52
+ try {
53
+ fs.lstatSync(dest);
54
+ destExists = true;
55
+ } catch {
56
+ destExists = false;
57
+ }
58
+
59
+ if (!overwrite && destExists) {
60
+ return { copied: false, skipped: true };
61
+ }
62
+
63
+ const linkTarget = fs.readlinkSync(src);
64
+
65
+ if (destExists) {
66
+ fs.unlinkSync(dest);
67
+ }
68
+
69
+ ensureDir(path.dirname(dest));
70
+ fs.symlinkSync(linkTarget, dest);
71
+
72
+ return { copied: true, skipped: false };
73
+ }
74
+
75
+ /**
76
+ * Copies a directory recursively
77
+ * @param {string} src - Source directory path
78
+ * @param {string} dest - Destination directory path
79
+ * @param {Object} options - Copy options
80
+ * @param {boolean} options.overwrite - If false, skip existing files (default: true)
81
+ * @param {string[]} options.exclude - Files/dirs to exclude
82
+ * @returns {{ copied: number, skipped: number }}
83
+ */
84
+ function copyDir(src, dest, options = {}) {
85
+ const { overwrite = true, exclude = [] } = options;
86
+ let copied = 0;
87
+ let skipped = 0;
88
+
89
+ ensureDir(dest);
90
+ const entries = fs.readdirSync(src, { withFileTypes: true });
91
+
92
+ for (const entry of entries) {
93
+ // Skip .DS_Store and excluded items
94
+ if (entry.name === '.DS_Store') continue;
95
+ if (exclude.includes(entry.name)) continue;
96
+
97
+ const srcPath = path.join(src, entry.name);
98
+ const destPath = path.join(dest, entry.name);
99
+
100
+ if (entry.isDirectory()) {
101
+ const result = copyDir(srcPath, destPath, options);
102
+ copied += result.copied;
103
+ skipped += result.skipped;
104
+ } else if (entry.isSymbolicLink()) {
105
+ const result = copySymlink(srcPath, destPath, overwrite);
106
+ if (result.copied) copied++;
107
+ if (result.skipped) skipped++;
108
+ } else {
109
+ const result = copyFile(srcPath, destPath, overwrite);
110
+ if (result.copied) copied++;
111
+ if (result.skipped) skipped++;
112
+ }
113
+ }
114
+
115
+ return { copied, skipped };
116
+ }
117
+
118
+ /**
119
+ * Merges a directory - copies only missing files/subdirs
120
+ * Existing files are preserved, new ones are added
121
+ * @param {string} src - Source directory path
122
+ * @param {string} dest - Destination directory path
123
+ * @param {string[]} exclude - Files/dirs to exclude
124
+ * @returns {{ copied: number, skipped: number }}
125
+ */
126
+ function mergeDir(src, dest, exclude = []) {
127
+ return copyDir(src, dest, { overwrite: false, exclude });
128
+ }
129
+
130
+ /**
131
+ * Check if a path exists (file, directory, or symlink)
132
+ */
133
+ function exists(filePath) {
134
+ try {
135
+ fs.lstatSync(filePath);
136
+ return true;
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+
142
+ module.exports = {
143
+ ensureDir,
144
+ copyFile,
145
+ copySymlink,
146
+ copyDir,
147
+ mergeDir,
148
+ exists,
149
+ };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Display module for Ralph Loop CLI
3
+ * ASCII art, colors, and UI elements
4
+ */
5
+
6
+ // ANSI color codes
7
+ const colors = {
8
+ reset: '\x1b[0m',
9
+ yellow: '\x1b[33m',
10
+ green: '\x1b[32m',
11
+ cyan: '\x1b[36m',
12
+ red: '\x1b[31m',
13
+ dim: '\x1b[2m',
14
+ bold: '\x1b[1m',
15
+ };
16
+
17
+ const { reset: R, yellow: Y, green: G, cyan: C, red: RD, dim: D, bold: B } = colors;
18
+
19
+ // Line width for catchphrases (matching display.sh)
20
+ const LINE_WIDTH = 59;
21
+
22
+ /**
23
+ * Creates a catchphrase line with centered text and ▒ padding
24
+ * Format: ■▒...text...▒■▒▒▓■ (59 chars total)
25
+ */
26
+ function formatCatchphrase(text) {
27
+ const prefix = '■▒';
28
+ const suffix = '■▒▒▓■';
29
+ const contentWidth = LINE_WIDTH - prefix.length - suffix.length; // 52 chars for content
30
+
31
+ const padding = contentWidth - text.length;
32
+ const leftPad = Math.floor(padding / 2);
33
+ const rightPad = padding - leftPad;
34
+
35
+ return prefix + '▒'.repeat(leftPad) + text + '▒'.repeat(rightPad) + suffix;
36
+ }
37
+
38
+ // Catchphrases (text only, will be formatted)
39
+ const catchphraseTexts = [
40
+ "I'm helping!",
41
+ "I'm doing my best!",
42
+ "I'm in danger!",
43
+ "I'm learnding!",
44
+ "My cat's breath smells like cat food.",
45
+ "Me fail English? That's unpossible!",
46
+ "I'm asking Claude to cook pasta!",
47
+ "I found a moon rock in my nose!",
48
+ "It tastes like burning!",
49
+ "When I grow up, I want to be a computer!",
50
+ "I'm a develotron!",
51
+ "I'm helpding AI!",
52
+ "I'm essential!",
53
+ ];
54
+
55
+ function getRandomCatchphrase() {
56
+ const text = catchphraseTexts[Math.floor(Math.random() * catchphraseTexts.length)];
57
+ return formatCatchphrase(text);
58
+ }
59
+
60
+ function showRalph() {
61
+ const emptyLine = formatCatchphrase('');
62
+ const catchphrase = getRandomCatchphrase();
63
+
64
+ console.log(`
65
+ ${Y}██████╗ █████╗ ██╗ ██████╗ ██╗ ██╗
66
+ ██╔══██╗██╔══██╗██║ ██╔══██╗██║ ██║
67
+ ██████╔╝███████║██║ ██████╔╝███████║
68
+ ██╔══██╗██╔══██║██║ ██╔═══╝ ██╔══██║
69
+ ██║ ██║██║ ██║███████╗██║ ██║ ██║
70
+ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝
71
+
72
+ ██╗ ██████╗ ██████╗ ██████╗
73
+ ██║ ██╔═══██╗██╔═══██╗██╔══██╗
74
+ ██║ ██║ ██║██║ ██║██████╔╝
75
+ ██║ ██║ ██║██║ ██║██╔═══╝
76
+ ███████╗╚██████╔╝╚██████╔╝██║
77
+ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝${R}
78
+
79
+ ${D}${emptyLine}${R}
80
+ ${Y}${catchphrase}${R}
81
+ ${D}${emptyLine}${R}
82
+ ${D}═══════════════════════════════════════════════════════════${R}
83
+ ${C}Ralph Wiggum Loop${R} ${D}・${R} Long-running AI agents
84
+ `);
85
+ }
86
+
87
+ function showComplete() {
88
+ console.log(`
89
+ ${D}═══════════════════════════════════════════════════════════${R}
90
+ ${G} ✓ Ralph Loop setup complete!${R}
91
+ ${D}═══════════════════════════════════════════════════════════${R}
92
+
93
+ ${Y}Next steps:${R}
94
+ ${C}1.${R} Review the copied files
95
+ ${C}2.${R} Create a PRD using the ${G}prd-creator${R} skill
96
+ ${C}3.${R} Run ${G}./ralph.sh${R} to start the loop
97
+
98
+ ${D}Learn more: ${C}https://github.com/pageai-pro/ralph-loop/${R}
99
+ `);
100
+ }
101
+
102
+ function printStep(icon, text) {
103
+ console.log(` ${icon} ${B}${text}${R}`);
104
+ }
105
+
106
+ function printSuccess(text) {
107
+ console.log(` ${G}✓${R} ${D}${text}${R}`);
108
+ }
109
+
110
+ function printSkipped(text) {
111
+ console.log(` ${D}○ ${text} (exists)${R}`);
112
+ }
113
+
114
+ function printWarning(text) {
115
+ console.log(` ${Y}⚠${R} ${D}${text}${R}`);
116
+ }
117
+
118
+ function printError(text) {
119
+ console.log(`${RD} ✗ Error: ${text}${R}\n`);
120
+ }
121
+
122
+ function printLocation(targetDir) {
123
+ console.log(`${B}Setting up Ralph Loop in:${R}`);
124
+ console.log(` ${C}${targetDir}${R}\n`);
125
+ }
126
+
127
+ module.exports = {
128
+ colors,
129
+ showRalph,
130
+ showComplete,
131
+ printStep,
132
+ printSuccess,
133
+ printSkipped,
134
+ printWarning,
135
+ printError,
136
+ printLocation,
137
+ };