@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/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@pageai/ralph-loop",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "A long-running AI agent loop. Ralph automates software development tasks by iteratively working through a task list until completion.",
8
+ "main": "bin/cli.js",
9
+ "bin": {
10
+ "ralph-loop": "bin/cli.js"
11
+ },
12
+ "directories": {
13
+ "test": "tests"
14
+ },
15
+ "scripts": {
16
+ "test": "echo \"Error: no test specified\" && exit 1"
17
+ },
18
+ "files": [
19
+ "bin/",
20
+ "scripts/",
21
+ "ralph.sh",
22
+ "CLAUDE.md",
23
+ "AGENTS.md",
24
+ ".mcp.json",
25
+ ".agent/PROMPT.md",
26
+ ".agent/STEERING.md",
27
+ ".agent/tasks.json",
28
+ ".agent/history/.gitignore",
29
+ ".agent/logs/",
30
+ ".agent/prd/",
31
+ ".agent/screenshots/",
32
+ ".agent/skills/",
33
+ ".agent/tasks/",
34
+ ".claude/",
35
+ ".codex/",
36
+ ".cursor/",
37
+ ".agents/"
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/pageai-pro/ralph-loop.git"
42
+ },
43
+ "keywords": [
44
+ "ralph",
45
+ "ralph-loop",
46
+ "ralph-wiggum-loop",
47
+ "agentic-ai",
48
+ "ai",
49
+ "ai-agent",
50
+ "cli",
51
+ "claude-code",
52
+ "cc",
53
+ "openai",
54
+ "codex",
55
+ "gemini",
56
+ "ampcode",
57
+ "ralph-plugin"
58
+ ],
59
+ "author": "PageAI",
60
+ "license": "MIT",
61
+ "bugs": {
62
+ "url": "https://github.com/pageai-pro/ralph-loop/issues"
63
+ },
64
+ "homepage": "https://github.com/pageai-pro/ralph-loop#readme"
65
+ }
package/ralph.sh ADDED
@@ -0,0 +1,333 @@
1
+ #!/bin/bash
2
+ # Ralph Wiggum - Long-running AI agent loop
3
+ # Usage: ./ralph.sh [--help] [--once] [--max-iterations N] [N]
4
+
5
+ set -e
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ cd "$SCRIPT_DIR"
9
+
10
+ PRD_FILE="$SCRIPT_DIR/.agent/prd/PRD.md"
11
+ PROGRESS_FILE="$SCRIPT_DIR/.agent/logs/LOG.md"
12
+ HISTORY_DIR="$SCRIPT_DIR/.agent/history"
13
+
14
+ source "$SCRIPT_DIR/scripts/lib/constants.sh"
15
+ source "$SCRIPT_DIR/scripts/lib/logging.sh"
16
+ source "$SCRIPT_DIR/scripts/lib/preflight.sh"
17
+ source "$SCRIPT_DIR/scripts/lib/timing.sh"
18
+ source "$SCRIPT_DIR/scripts/lib/terminal.sh"
19
+ source "$SCRIPT_DIR/scripts/lib/spinner.sh"
20
+ source "$SCRIPT_DIR/scripts/lib/preview.sh"
21
+ source "$SCRIPT_DIR/scripts/lib/output.sh"
22
+ source "$SCRIPT_DIR/scripts/lib/cleanup.sh"
23
+ source "$SCRIPT_DIR/scripts/lib/promise.sh"
24
+ source "$SCRIPT_DIR/scripts/lib/notify.sh"
25
+ source "$SCRIPT_DIR/scripts/lib/display.sh"
26
+ source "$SCRIPT_DIR/scripts/lib/args.sh"
27
+
28
+ # Timing
29
+ START_TIME=$(date +%s)
30
+ ITERATION_TIMES=()
31
+ TOTAL_ITERATION_TIME=0
32
+ PREV_ITERATION_TIME=0
33
+
34
+ # Session ID for unique history file naming (YYYYMMDD-HHMMSS format). This is used to prevent overwrites between runs.
35
+ SESSION_ID=$(date +%Y%m%d-%H%M%S)
36
+
37
+ # Temporary files for spinner communication
38
+ STEP_FILE=$(mktemp)
39
+ PREVIEW_LINE_FILE=$(mktemp)
40
+
41
+ # Background process tracking for cleanup
42
+ AGENT_PID=""
43
+ OUTPUT_FILE=""
44
+ FULL_OUTPUT_FILE=""
45
+
46
+ # Set up traps
47
+ trap cleanup EXIT
48
+ trap handle_interrupt INT
49
+
50
+ # Parse arguments (sets MAX_ITERATIONS, ONCE_FLAG)
51
+ parse_arguments "$@"
52
+
53
+ # Initialize progress file if it doesn't exist
54
+ if [ ! -f "$PROGRESS_FILE" ]; then
55
+ echo "# Ralph Progress Log" > "$PROGRESS_FILE"
56
+ echo "Started: $(date)" >> "$PROGRESS_FILE"
57
+ echo "---" >> "$PROGRESS_FILE"
58
+ fi
59
+
60
+ # Pre-flight checks
61
+ check_git_repo
62
+ check_required_files
63
+ check_history_dir
64
+ check_ansi_support
65
+
66
+ show_ralph
67
+ echo -e " ${C}Starting Ralph${R} ・ ${Y}v$VERSION${R} ・ Max iterations: ${Y}$MAX_ITERATIONS${R}"
68
+ echo ""
69
+
70
+ for i in $(seq 1 $MAX_ITERATIONS); do
71
+ ITERATION_START=$(date +%s)
72
+
73
+ # Initialize step timing for this iteration
74
+ init_iteration_step_times
75
+
76
+ echo -e "${B}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
77
+ echo -e " ↪ ${R}Iteration ${Y}$i${R} of ${Y}$MAX_ITERATIONS${R}"
78
+ echo -e "${B}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
79
+ echo -e ""
80
+
81
+ # Run Agent with the ralph prompt (prepend PROJECT_ROOT)
82
+ PROMPT_CONTENT="PROJECT_ROOT=$SCRIPT_DIR
83
+
84
+ $(cat $SCRIPT_DIR/.agent/PROMPT.md)"
85
+
86
+ # Start spinner
87
+ start_spinner
88
+
89
+ # Initialize rolling preview
90
+ init_rolling_preview
91
+
92
+ # Run Agent and capture output while updating spinner and preview
93
+ OUTPUT_FILE=$(mktemp)
94
+ FULL_OUTPUT_FILE=$(mktemp)
95
+
96
+ # Use script to provide pseudo-TTY for docker sandbox.
97
+ # This is the main command loop.
98
+ export PROMPT_CONTENT
99
+ export DOCKER_DEFAULT_PLATFORM=linux/amd64 # Needed for Playwright.
100
+
101
+ script -q "$OUTPUT_FILE" bash -c 'docker sandbox run --credentials host claude --model opus --output-format stream-json --verbose -p "$PROMPT_CONTENT"' >/dev/null 2>&1 &
102
+ AGENT_PID=$!
103
+
104
+ # Track position in output file for incremental reading
105
+ LAST_POS=0
106
+
107
+ # Monitor output and update spinner step and rolling preview
108
+ while kill -0 "$AGENT_PID" 2>/dev/null; do
109
+ if [ -f "$OUTPUT_FILE" ]; then
110
+ # Get current file size
111
+ CURRENT_SIZE=$(stat -f%z "$OUTPUT_FILE" 2>/dev/null || stat -c%s "$OUTPUT_FILE" 2>/dev/null || echo "0")
112
+
113
+ # Read new content if file has grown
114
+ if [ "$CURRENT_SIZE" -gt "$LAST_POS" ]; then
115
+ # Read new lines
116
+ tail -c +$((LAST_POS + 1)) "$OUTPUT_FILE" 2>/dev/null | while IFS= read -r line; do
117
+ if [ -n "$line" ]; then
118
+ # Parse JSON and extract text content
119
+ parsed=$(parse_json_content "$line")
120
+ if [ -n "$parsed" ]; then
121
+ # Save to full output
122
+ echo "$parsed" >> "$FULL_OUTPUT_FILE"
123
+ # Update spinner step
124
+ update_spinner_step "$parsed"
125
+ # Update preview line under spinner
126
+ update_preview_line "$parsed"
127
+ fi
128
+ fi
129
+ done
130
+ LAST_POS=$CURRENT_SIZE
131
+ fi
132
+ fi
133
+ sleep 0.2
134
+ done
135
+
136
+ wait "$AGENT_PID" || true
137
+ AGENT_PID="" # Clear PID after process exits
138
+
139
+ # Process any remaining output
140
+ if [ -f "$OUTPUT_FILE" ]; then
141
+ CURRENT_SIZE=$(stat -f%z "$OUTPUT_FILE" 2>/dev/null || stat -c%s "$OUTPUT_FILE" 2>/dev/null || echo "0")
142
+ if [ "$CURRENT_SIZE" -gt "$LAST_POS" ]; then
143
+ tail -c +$((LAST_POS + 1)) "$OUTPUT_FILE" 2>/dev/null | while IFS= read -r line; do
144
+ if [ -n "$line" ]; then
145
+ parsed=$(parse_json_content "$line")
146
+ if [ -n "$parsed" ]; then
147
+ echo "$parsed" >> "$FULL_OUTPUT_FILE"
148
+ fi
149
+ fi
150
+ done
151
+ fi
152
+ fi
153
+
154
+ OUTPUT=$(cat "$FULL_OUTPUT_FILE" 2>/dev/null || cat "$OUTPUT_FILE")
155
+
156
+ # Check for Docker daemon not ready error
157
+ if echo "$OUTPUT" | grep -q "docker daemon not ready"; then
158
+ stop_spinner
159
+ clear_rolling_preview
160
+ echo ""
161
+ echo -e "${RD}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
162
+ echo -e " ❌ ${RD}Docker Error${R}"
163
+ echo -e " Docker daemon is not ready. Please ensure Docker is running."
164
+ echo -e "${RD}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
165
+ rm -f "$OUTPUT_FILE" "$FULL_OUTPUT_FILE"
166
+ exit $EXIT_DOCKER_ERROR
167
+ fi
168
+
169
+ # Check for invalid API key / authentication error
170
+ if echo "$OUTPUT" | grep -q "Invalid API key"; then
171
+ stop_spinner
172
+ clear_rolling_preview
173
+ echo ""
174
+ echo -e "${RD}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
175
+ echo -e " ❌ ${RD}Authentication Error${R}"
176
+ echo -e " Invalid API key. Please authenticate inside the Docker sandbox."
177
+ echo -e ""
178
+ echo -e " Run the following command and follow the login instructions:"
179
+ echo -e " ${C}docker sandbox run --credentials host claude${R}"
180
+ echo -e "${RD}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
181
+ rm -f "$OUTPUT_FILE" "$FULL_OUTPUT_FILE"
182
+ exit $EXIT_AUTH_ERROR
183
+ fi
184
+
185
+ # Save cleaned output to history file (SESSION_ID prevents overwrites between runs)
186
+ # Strip ANSI control characters for clean, readable history
187
+ HISTORY_FILE="$HISTORY_DIR/ITERATION-${SESSION_ID}-${i}.txt"
188
+ strip_ansi_file "$OUTPUT_FILE" "$HISTORY_FILE"
189
+
190
+ # Extract final summary before removing files
191
+ FINAL_SUMMARY=$(extract_final_summary "$OUTPUT_FILE")
192
+
193
+ rm -f "$FULL_OUTPUT_FILE"
194
+ FULL_OUTPUT_FILE="" # Clear after removal
195
+
196
+ # Stop spinner
197
+ stop_spinner
198
+
199
+ # Finalize step timing for this iteration (record time spent in last step)
200
+ record_step_time ""
201
+
202
+ # Clear rolling preview area to make room for summary
203
+ clear_rolling_preview
204
+
205
+ # Display the final summary (persists after iteration)
206
+ if [ -n "$FINAL_SUMMARY" ]; then
207
+ display_final_summary "$FINAL_SUMMARY" 10
208
+ else
209
+ # Fallback: show last 10 lines of parsed output if no result found
210
+ FALLBACK_SUMMARY=$(echo "$OUTPUT" | tail -n 10)
211
+ if [ -n "$FALLBACK_SUMMARY" ]; then
212
+ display_final_summary "$FALLBACK_SUMMARY" 10
213
+ fi
214
+ fi
215
+
216
+ # Clean up the raw output file (history file already saved)
217
+ rm -f "$OUTPUT_FILE"
218
+ OUTPUT_FILE="" # Clear after removal
219
+
220
+ # Calculate iteration duration
221
+ ITERATION_END=$(date +%s)
222
+ ITERATION_DURATION=$((ITERATION_END - ITERATION_START))
223
+ ITERATION_TIMES+=($ITERATION_DURATION)
224
+ TOTAL_ITERATION_TIME=$((TOTAL_ITERATION_TIME + ITERATION_DURATION))
225
+ ITERATION_AVG=$((TOTAL_ITERATION_TIME / ${#ITERATION_TIMES[@]}))
226
+ ITERATION_STR=$(format_duration $ITERATION_DURATION)
227
+ AVG_STR=$(format_duration $ITERATION_AVG)
228
+ DELTA_STR=$(format_delta $ITERATION_DURATION $PREV_ITERATION_TIME)
229
+ PREV_ITERATION_TIME=$ITERATION_DURATION
230
+
231
+ # Check for completion signal
232
+ # Note: We check both $OUTPUT and $FINAL_SUMMARY because the JSON parsing
233
+ # in parse_json_content truncates text at escaped quotes, but FINAL_SUMMARY
234
+ # is correctly extracted using jq from the result message
235
+ if has_complete_tag "$OUTPUT" || has_complete_tag "$FINAL_SUMMARY"; then
236
+ ELAPSED=$(($(date +%s) - START_TIME))
237
+ ELAPSED_STR=$(format_duration $ELAPSED)
238
+ echo ""
239
+ echo -e "${GR}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
240
+ echo -e " 🎉 ${GR}Ralph completed all tasks!${R}"
241
+ echo -e " ✅ Finished at iteration ${GR}$i${R} of ${GR}$MAX_ITERATIONS${R}"
242
+ if [ -n "$DELTA_STR" ]; then
243
+ echo -e " ⏱️ Iteration $i: ${Y}$ITERATION_STR${R} ($DELTA_STR) ${C}│${R} Average: ${Y}$AVG_STR${R}"
244
+ else
245
+ echo -e " ⏱️ Iteration $i: ${Y}$ITERATION_STR${R} ${C}│${R} Average: ${Y}$AVG_STR${R}"
246
+ fi
247
+ echo -e " ⏱️ Total time: ${Y}$ELAPSED_STR${R}"
248
+ display_session_step_totals
249
+ echo -e "${GR}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
250
+ exit $EXIT_COMPLETE
251
+ fi
252
+
253
+ # Check for BLOCKED tag - agent needs human help
254
+ # Check both $OUTPUT and $FINAL_SUMMARY (see completion signal comment above)
255
+ if has_blocked_tag "$OUTPUT" || has_blocked_tag "$FINAL_SUMMARY"; then
256
+ BLOCKED_REASON=$(extract_blocked_reason "$OUTPUT")
257
+ # If not found in OUTPUT, try FINAL_SUMMARY
258
+ [ -z "$BLOCKED_REASON" ] && BLOCKED_REASON=$(extract_blocked_reason "$FINAL_SUMMARY")
259
+ ELAPSED=$(($(date +%s) - START_TIME))
260
+ ELAPSED_STR=$(format_duration $ELAPSED)
261
+ play_notification_sound
262
+ show_notification "Ralph - BLOCKED" "$BLOCKED_REASON"
263
+ display_blocked_message "$BLOCKED_REASON" "$i"
264
+ if [ -n "$DELTA_STR" ]; then
265
+ echo -e " ⏱️ Iteration $i: ${Y}$ITERATION_STR${R} ($DELTA_STR) ${C}│${R} Average: ${Y}$AVG_STR${R}"
266
+ else
267
+ echo -e " ⏱️ Iteration $i: ${Y}$ITERATION_STR${R} ${C}│${R} Average: ${Y}$AVG_STR${R}"
268
+ fi
269
+ echo -e " ⏱️ Total time: ${Y}$ELAPSED_STR${R}"
270
+ display_session_step_totals
271
+ exit $EXIT_BLOCKED
272
+ fi
273
+
274
+ # Check for DECIDE tag - agent needs human decision
275
+ # Check both $OUTPUT and $FINAL_SUMMARY (see completion signal comment above)
276
+ if has_decide_tag "$OUTPUT" || has_decide_tag "$FINAL_SUMMARY"; then
277
+ DECIDE_QUESTION=$(extract_decide_question "$OUTPUT")
278
+ # If not found in OUTPUT, try FINAL_SUMMARY
279
+ [ -z "$DECIDE_QUESTION" ] && DECIDE_QUESTION=$(extract_decide_question "$FINAL_SUMMARY")
280
+ ELAPSED=$(($(date +%s) - START_TIME))
281
+ ELAPSED_STR=$(format_duration $ELAPSED)
282
+ play_notification_sound
283
+ show_notification "Ralph - Decision Needed" "$DECIDE_QUESTION"
284
+ display_decide_message "$DECIDE_QUESTION" "$i"
285
+ if [ -n "$DELTA_STR" ]; then
286
+ echo -e " ⏱️ Iteration $i: ${Y}$ITERATION_STR${R} ($DELTA_STR) ${C}│${R} Average: ${Y}$AVG_STR${R}"
287
+ else
288
+ echo -e " ⏱️ Iteration $i: ${Y}$ITERATION_STR${R} ${C}│${R} Average: ${Y}$AVG_STR${R}"
289
+ fi
290
+ echo -e " ⏱️ Total time: ${Y}$ELAPSED_STR${R}"
291
+ display_session_step_totals
292
+ exit $EXIT_DECIDE
293
+ fi
294
+
295
+ # Calculate elapsed time
296
+ ELAPSED=$(($(date +%s) - START_TIME))
297
+ ELAPSED_STR=$(format_duration $ELAPSED)
298
+
299
+ if [ -n "$DELTA_STR" ]; then
300
+ echo -e "${G} └── ✓ Iteration $i complete${R} ${C}│${R} Iteration: ${Y}$ITERATION_STR${R} ($DELTA_STR) ${C}│${R} Average: ${Y}$AVG_STR${R} ${C}│${R} Total: ${Y}$ELAPSED_STR${R}"
301
+ else
302
+ echo -e "${G} └── ✓ Iteration $i complete${R} ${C}│${R} Iteration: ${Y}$ITERATION_STR${R} ${C}│${R} Average: ${Y}$AVG_STR${R} ${C}│${R} Total: ${Y}$ELAPSED_STR${R}"
303
+ fi
304
+
305
+ # Display per-iteration step times
306
+ STEP_TIMES_OUTPUT=$(format_step_times "ITERATION")
307
+ if [ -n "$STEP_TIMES_OUTPUT" ]; then
308
+ echo -e "${G} └──${R} $STEP_TIMES_OUTPUT"
309
+ fi
310
+ sleep 2
311
+ done
312
+
313
+ # Calculate final elapsed time
314
+ ELAPSED=$(($(date +%s) - START_TIME))
315
+ ELAPSED_STR=$(format_duration $ELAPSED)
316
+
317
+ # Calculate final average (if any iterations completed)
318
+ if [ ${#ITERATION_TIMES[@]} -gt 0 ]; then
319
+ FINAL_AVG=$((TOTAL_ITERATION_TIME / ${#ITERATION_TIMES[@]}))
320
+ FINAL_AVG_STR=$(format_duration $FINAL_AVG)
321
+ fi
322
+
323
+ echo ""
324
+ echo -e "${Y}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
325
+ echo -e " ⚠️ ${Y}Ralph reached max iterations${R} (${M}$MAX_ITERATIONS${R})"
326
+ if [ ${#ITERATION_TIMES[@]} -gt 0 ]; then
327
+ echo -e " ⏱️ Average iteration time: ${Y}$FINAL_AVG_STR${R}"
328
+ fi
329
+ echo -e " ⏱️ Total time: ${Y}$ELAPSED_STR${R}"
330
+ display_session_step_totals
331
+ echo -e " 📋 Check progress: ${G}$PROGRESS_FILE${R}"
332
+ echo -e "${Y}░░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░${R}"
333
+ exit $EXIT_MAX_ITERATIONS
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # Arguments module for ralph.sh
3
+ # CLI argument parsing
4
+ # Dependencies: constants.sh
5
+
6
+ # Parse command line arguments
7
+ # Sets: MAX_ITERATIONS, ONCE_FLAG
8
+ # Usage: parse_arguments "$@"
9
+ parse_arguments() {
10
+ MAX_ITERATIONS=10
11
+ ONCE_FLAG=false
12
+
13
+ while [[ $# -gt 0 ]]; do
14
+ case $1 in
15
+ --help|-h)
16
+ show_help
17
+ ;;
18
+ --once)
19
+ ONCE_FLAG=true
20
+ shift
21
+ ;;
22
+ --max-iterations|-n)
23
+ MAX_ITERATIONS="$2"
24
+ shift 2
25
+ ;;
26
+ --max-iterations=*)
27
+ MAX_ITERATIONS="${1#*=}"
28
+ shift
29
+ ;;
30
+ [0-9]*)
31
+ MAX_ITERATIONS="$1"
32
+ shift
33
+ ;;
34
+ *)
35
+ shift
36
+ ;;
37
+ esac
38
+ done
39
+
40
+ # --once overrides max-iterations
41
+ if [ "$ONCE_FLAG" = true ]; then
42
+ MAX_ITERATIONS=1
43
+ fi
44
+ }
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+ # Cleanup module for ralph.sh
3
+ # Signal handlers and process cleanup
4
+ # Dependencies: constants.sh
5
+ #
6
+ # Properly cleans up all background processes and temporary files on exit.
7
+ # Runs on both normal exit (EXIT trap) and interrupt (called from handle_interrupt).
8
+ # Ensures no orphan processes remain after script exits.
9
+
10
+ # Cleanup function - kills background processes and removes temp files
11
+ cleanup() {
12
+ # Kill spinner background process
13
+ if [ -n "$SPINNER_PID" ] && kill -0 "$SPINNER_PID" 2>/dev/null; then
14
+ kill "$SPINNER_PID" 2>/dev/null || true
15
+ wait "$SPINNER_PID" 2>/dev/null || true
16
+ fi
17
+ SPINNER_PID=""
18
+
19
+ # Kill Agent process if still running
20
+ if [ -n "$AGENT_PID" ] && kill -0 "$AGENT_PID" 2>/dev/null; then
21
+ kill "$AGENT_PID" 2>/dev/null || true
22
+ wait "$AGENT_PID" 2>/dev/null || true
23
+ fi
24
+ AGENT_PID=""
25
+
26
+ # Remove temporary files
27
+ [[ -n "$STEP_FILE" ]] && rm -f "$STEP_FILE" 2>/dev/null || true
28
+ [[ -n "$PREVIEW_LINE_FILE" ]] && rm -f "$PREVIEW_LINE_FILE" 2>/dev/null || true
29
+ [[ -n "$OUTPUT_FILE" ]] && rm -f "$OUTPUT_FILE" 2>/dev/null || true
30
+ [[ -n "$FULL_OUTPUT_FILE" ]] && rm -f "$FULL_OUTPUT_FILE" 2>/dev/null || true
31
+ }
32
+
33
+ # Double CTRL+C to exit
34
+ # Tracks last interrupt time, requires second press within 3 seconds to exit
35
+ LAST_INTERRUPT=0
36
+
37
+ handle_interrupt() {
38
+ local current_time=$(date +%s)
39
+ local time_diff=$((current_time - LAST_INTERRUPT))
40
+
41
+ if [ $time_diff -lt 3 ]; then
42
+ # Second CTRL+C within 3 seconds - exit
43
+ echo ""
44
+ echo -e "${Y}Exiting...${R}"
45
+ cleanup
46
+ exit 130
47
+ else
48
+ # First CTRL+C - show warning and update timestamp
49
+ LAST_INTERRUPT=$current_time
50
+ echo ""
51
+ echo -e "${Y}⚠️ Press CTRL+C again within 3 seconds to exit${R}"
52
+ fi
53
+ }
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+ # Constants module for ralph.sh
3
+ # Contains: Colors, paths, version, exit codes
4
+
5
+ # Version
6
+ VERSION="1.0.0"
7
+
8
+ # Colors (short syntax)
9
+ Y='\033[33m' # Yellow
10
+ B='\033[94m' # Light blue
11
+ G='\033[1;90m' # Grey bold
12
+ C='\033[36m' # Cyan
13
+ M='\033[35m' # Magenta
14
+ GR='\033[32m' # Green
15
+ RD='\033[31m' # Red
16
+ D='\033[2m' # Dim
17
+ R='\033[0m' # Reset
18
+
19
+ # Exit codes
20
+ EXIT_COMPLETE=0
21
+ EXIT_MAX_ITERATIONS=1
22
+ EXIT_BLOCKED=2
23
+ EXIT_DECIDE=3
24
+ EXIT_DOCKER_ERROR=4
25
+ EXIT_AUTH_ERROR=5