create-merlin-brain 2.3.3 → 2.5.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.
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Loop - Timeout Handling
4
+ # Provides timeout with countdown for pause points, enabling AFK operation
5
+ #
6
+
7
+ # ═══════════════════════════════════════════════════════════════════════════════
8
+ # Timeout Configuration
9
+ # ═══════════════════════════════════════════════════════════════════════════════
10
+
11
+ # Default timeout: 5 minutes (300 seconds)
12
+ PAUSE_TIMEOUT="${MERLIN_PAUSE_TIMEOUT:-300}"
13
+
14
+ # Default action when timeout expires
15
+ DEFAULT_TIMEOUT_ACTION="${MERLIN_DEFAULT_ACTION:-continue}"
16
+
17
+ # Whether to show countdown (can be disabled for scripted use)
18
+ SHOW_COUNTDOWN="${MERLIN_SHOW_COUNTDOWN:-true}"
19
+
20
+ # Whether timeout is enabled
21
+ TIMEOUT_ENABLED="${MERLIN_TIMEOUT_ENABLED:-true}"
22
+
23
+ # ═══════════════════════════════════════════════════════════════════════════════
24
+ # Timeout Control
25
+ # ═══════════════════════════════════════════════════════════════════════════════
26
+
27
+ # Disable timeout (for interactive mode)
28
+ disable_timeout() {
29
+ TIMEOUT_ENABLED="false"
30
+ }
31
+
32
+ # Enable timeout
33
+ enable_timeout() {
34
+ TIMEOUT_ENABLED="true"
35
+ }
36
+
37
+ # Set timeout value
38
+ set_timeout() {
39
+ local seconds="$1"
40
+ if [[ "$seconds" =~ ^[0-9]+$ ]]; then
41
+ PAUSE_TIMEOUT="$seconds"
42
+ else
43
+ echo -e "${RED}Invalid timeout value: $seconds${RESET}" >&2
44
+ return 1
45
+ fi
46
+ }
47
+
48
+ # ═══════════════════════════════════════════════════════════════════════════════
49
+ # Countdown Display
50
+ # ═══════════════════════════════════════════════════════════════════════════════
51
+
52
+ # Format seconds as M:SS
53
+ format_time() {
54
+ local seconds="$1"
55
+ local minutes=$((seconds / 60))
56
+ local secs=$((seconds % 60))
57
+ printf "%d:%02d" "$minutes" "$secs"
58
+ }
59
+
60
+ # Display countdown timer (updates in place)
61
+ countdown_display() {
62
+ local remaining="$1"
63
+ local default_action="$2"
64
+ local formatted
65
+ formatted=$(format_time "$remaining")
66
+
67
+ # Use carriage return to update in place
68
+ printf "\r⏱️ Auto-%s in %s... Press any key to respond " "$default_action" "$formatted"
69
+ }
70
+
71
+ # Clear the countdown line
72
+ clear_countdown() {
73
+ # Clear to end of line
74
+ printf "\r\033[K"
75
+ }
76
+
77
+ # ═══════════════════════════════════════════════════════════════════════════════
78
+ # Timeout Read
79
+ # ═══════════════════════════════════════════════════════════════════════════════
80
+
81
+ # Read with timeout and countdown
82
+ # Args: $1 = prompt, $2 = default value, $3 = custom timeout (optional)
83
+ # Returns: User input or default value via stdout
84
+ # Exit code: 0 = user input, 1 = timeout (default used)
85
+ read_with_timeout() {
86
+ local prompt="$1"
87
+ local default="$2"
88
+ local timeout="${3:-$PAUSE_TIMEOUT}"
89
+
90
+ # If timeout disabled, use regular read
91
+ if [ "$TIMEOUT_ENABLED" != "true" ] || [ "$timeout" -eq 0 ]; then
92
+ echo -n "$prompt"
93
+ read -r response
94
+ echo "${response:-$default}"
95
+ return 0
96
+ fi
97
+
98
+ # Show what will happen on timeout
99
+ echo ""
100
+ echo -e "${YELLOW}⏱️ Will auto-${default} in $(format_time "$timeout") if no response${RESET}"
101
+ echo ""
102
+ echo -n "$prompt"
103
+
104
+ local remaining="$timeout"
105
+ local response=""
106
+ local timed_out=false
107
+
108
+ # Save cursor position
109
+ tput sc 2>/dev/null || true
110
+
111
+ while [ "$remaining" -gt 0 ]; do
112
+ # Try to read with 1-second timeout
113
+ if read -t 1 -r response 2>/dev/null; then
114
+ clear_countdown
115
+ # User provided input
116
+ echo "${response:-$default}"
117
+ return 0
118
+ fi
119
+
120
+ # Decrement and show countdown
121
+ ((remaining--))
122
+
123
+ if [ "$SHOW_COUNTDOWN" = "true" ] && [ "$remaining" -gt 0 ]; then
124
+ # Restore cursor and show countdown
125
+ tput rc 2>/dev/null || printf "\r"
126
+ countdown_display "$remaining" "$default"
127
+ fi
128
+ done
129
+
130
+ # Timeout reached
131
+ timed_out=true
132
+ clear_countdown
133
+ echo ""
134
+ echo -e "${YELLOW}⏱️ Timeout reached. Auto-${default}.${RESET}"
135
+
136
+ # Log timeout event
137
+ log_timeout_event "$default"
138
+
139
+ echo "$default"
140
+ return 1
141
+ }
142
+
143
+ # ═══════════════════════════════════════════════════════════════════════════════
144
+ # Timeout Logging
145
+ # ═══════════════════════════════════════════════════════════════════════════════
146
+
147
+ # Log a timeout event to history
148
+ log_timeout_event() {
149
+ local action="$1"
150
+ local now
151
+ now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
152
+
153
+ if [ -n "$HISTORY_FILE" ] && [ -f "$HISTORY_FILE" ]; then
154
+ echo "{\"type\":\"timeout\",\"action\":\"$action\",\"timeout\":$PAUSE_TIMEOUT,\"timestamp\":\"$now\"}" >> "$HISTORY_FILE"
155
+ fi
156
+ }
157
+
158
+ # ═══════════════════════════════════════════════════════════════════════════════
159
+ # Get Default Action for Pause Reason
160
+ # ═══════════════════════════════════════════════════════════════════════════════
161
+
162
+ # Determine the appropriate default action based on pause reason
163
+ # Args: $1 = pause reason (from LAST_PAUSE_REASON)
164
+ # Returns: default action string
165
+ get_default_for_pause() {
166
+ local reason="${1:-unknown}"
167
+
168
+ case "$reason" in
169
+ "checkpoint")
170
+ # Explicit checkpoints should NOT auto-continue - they're deliberate
171
+ echo "quit"
172
+ ;;
173
+ "decision")
174
+ # Decisions: proceed with Claude's recommendation
175
+ echo "continue"
176
+ ;;
177
+ "low_confidence")
178
+ # Low confidence: trust Claude's judgment, continue
179
+ echo "continue"
180
+ ;;
181
+ "periodic")
182
+ # Periodic checkpoints: continue is fine
183
+ echo "continue"
184
+ ;;
185
+ "interactive")
186
+ # Interactive mode: continue (though usually timeout is disabled)
187
+ echo "continue"
188
+ ;;
189
+ *)
190
+ # Unknown: default to continue
191
+ echo "continue"
192
+ ;;
193
+ esac
194
+ }
195
+
196
+ # ═══════════════════════════════════════════════════════════════════════════════
197
+ # Status Display
198
+ # ═══════════════════════════════════════════════════════════════════════════════
199
+
200
+ # Get timeout status for display
201
+ get_timeout_status() {
202
+ if [ "$TIMEOUT_ENABLED" = "true" ]; then
203
+ echo "$(format_time "$PAUSE_TIMEOUT") (enabled)"
204
+ else
205
+ echo "Disabled (waits indefinitely)"
206
+ fi
207
+ }
@@ -0,0 +1,388 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Merlin Loop - Interactive TUI
4
+ # Beautiful terminal interface for pause/continue control
5
+ # Part of Merlin Pro v1.0
6
+ #
7
+
8
+ # ═══════════════════════════════════════════════════════════════════════════════
9
+ # ANSI Colors and Styles
10
+ # ═══════════════════════════════════════════════════════════════════════════════
11
+
12
+ # Colors (if not already defined)
13
+ : "${RESET:=\033[0m}"
14
+ : "${BOLD:=\033[1m}"
15
+ : "${DIM:=\033[2m}"
16
+ : "${ITALIC:=\033[3m}"
17
+ : "${RED:=\033[31m}"
18
+ : "${GREEN:=\033[32m}"
19
+ : "${YELLOW:=\033[33m}"
20
+ : "${BLUE:=\033[34m}"
21
+ : "${MAGENTA:=\033[35m}"
22
+ : "${CYAN:=\033[36m}"
23
+ : "${WHITE:=\033[37m}"
24
+ : "${BG_BLACK:=\033[40m}"
25
+ : "${BG_BLUE:=\033[44m}"
26
+ : "${BG_GREEN:=\033[42m}"
27
+
28
+ # ═══════════════════════════════════════════════════════════════════════════════
29
+ # Progress Bar
30
+ # ═══════════════════════════════════════════════════════════════════════════════
31
+
32
+ # Draw a progress bar
33
+ # Usage: draw_progress_bar 5 10 40 (5 of 10, width 40)
34
+ draw_progress_bar() {
35
+ local current=${1:-0}
36
+ local total=${2:-100}
37
+ local width=${3:-40}
38
+ local label=${4:-"Progress"}
39
+
40
+ # Calculate percentage
41
+ local percent=0
42
+ if [ "$total" -gt 0 ]; then
43
+ percent=$((current * 100 / total))
44
+ fi
45
+
46
+ # Calculate filled width
47
+ local filled=$((current * width / total))
48
+ if [ "$filled" -gt "$width" ]; then
49
+ filled=$width
50
+ fi
51
+ local empty=$((width - filled))
52
+
53
+ # Build the bar
54
+ local bar=""
55
+ for ((i=0; i<filled; i++)); do
56
+ bar="${bar}█"
57
+ done
58
+ for ((i=0; i<empty; i++)); do
59
+ bar="${bar}░"
60
+ done
61
+
62
+ # Color based on progress
63
+ local color="${CYAN}"
64
+ if [ "$percent" -ge 80 ]; then
65
+ color="${GREEN}"
66
+ elif [ "$percent" -ge 50 ]; then
67
+ color="${YELLOW}"
68
+ fi
69
+
70
+ echo -e "${label}: ${color}${bar}${RESET} ${percent}% (${current}/${total})"
71
+ }
72
+
73
+ # ═══════════════════════════════════════════════════════════════════════════════
74
+ # Spinner Animation
75
+ # ═══════════════════════════════════════════════════════════════════════════════
76
+
77
+ SPINNER_PID=""
78
+ SPINNER_FRAMES=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
79
+
80
+ # Start spinner with message
81
+ # Usage: start_spinner "Loading..."
82
+ start_spinner() {
83
+ local message="${1:-Working...}"
84
+
85
+ (
86
+ local i=0
87
+ while true; do
88
+ printf "\r${CYAN}${SPINNER_FRAMES[$i]}${RESET} %s" "$message"
89
+ i=$(( (i+1) % ${#SPINNER_FRAMES[@]} ))
90
+ sleep 0.1
91
+ done
92
+ ) &
93
+
94
+ SPINNER_PID=$!
95
+ disown $SPINNER_PID 2>/dev/null || true
96
+ }
97
+
98
+ # Stop spinner with result
99
+ # Usage: stop_spinner "Done!" or stop_spinner "Failed!" "error"
100
+ stop_spinner() {
101
+ local message="${1:-Done}"
102
+ local status="${2:-success}"
103
+
104
+ if [ -n "$SPINNER_PID" ] && kill -0 "$SPINNER_PID" 2>/dev/null; then
105
+ kill "$SPINNER_PID" 2>/dev/null
106
+ wait "$SPINNER_PID" 2>/dev/null || true
107
+ SPINNER_PID=""
108
+ fi
109
+
110
+ # Clear the line and print result
111
+ printf "\r%-60s\r" " "
112
+
113
+ if [ "$status" == "success" ]; then
114
+ echo -e "${GREEN}✓${RESET} $message"
115
+ elif [ "$status" == "error" ]; then
116
+ echo -e "${RED}✗${RESET} $message"
117
+ elif [ "$status" == "warning" ]; then
118
+ echo -e "${YELLOW}⚠${RESET} $message"
119
+ else
120
+ echo -e "$message"
121
+ fi
122
+ }
123
+
124
+ # ═══════════════════════════════════════════════════════════════════════════════
125
+ # Interactive Pause Menu
126
+ # ═══════════════════════════════════════════════════════════════════════════════
127
+
128
+ TUI_LAST_ACTION=""
129
+
130
+ # Show the pause menu and wait for input
131
+ # Returns: 0 for continue, 1 for quit, 2 for pause
132
+ show_pause_menu() {
133
+ local task_completed=${1:-0}
134
+ local task_total=${2:-0}
135
+ local current_task_name="${3:-Next task}"
136
+ local timeout=${4:-$PAUSE_TIMEOUT}
137
+
138
+ echo ""
139
+ echo -e "${MAGENTA}╔═══════════════════════════════════════════════════════════════════╗${RESET}"
140
+ echo -e "${MAGENTA}║${RESET} ${BOLD}🔮 MERLIN LOOP${RESET} ${MAGENTA}║${RESET}"
141
+ echo -e "${MAGENTA}╠═══════════════════════════════════════════════════════════════════╣${RESET}"
142
+ echo -e "${MAGENTA}║${RESET} ${MAGENTA}║${RESET}"
143
+
144
+ # Progress
145
+ if [ "$task_total" -gt 0 ]; then
146
+ local progress_bar
147
+ progress_bar=$(draw_progress_bar "$task_completed" "$task_total" 30 "Tasks")
148
+ printf "${MAGENTA}║${RESET} %-65s ${MAGENTA}║${RESET}\n" "$progress_bar"
149
+ fi
150
+
151
+ # Next task
152
+ printf "${MAGENTA}║${RESET} ${CYAN}Next:${RESET} %-58s ${MAGENTA}║${RESET}\n" "${current_task_name:0:58}"
153
+
154
+ echo -e "${MAGENTA}║${RESET} ${MAGENTA}║${RESET}"
155
+ echo -e "${MAGENTA}╠═══════════════════════════════════════════════════════════════════╣${RESET}"
156
+ echo -e "${MAGENTA}║${RESET} ${MAGENTA}║${RESET}"
157
+ echo -e "${MAGENTA}║${RESET} ${GREEN}[Enter]${RESET} Continue ${YELLOW}[p]${RESET} Pause ${RED}[q]${RESET} Quit ${CYAN}[s]${RESET} Status ${MAGENTA}║${RESET}"
158
+ echo -e "${MAGENTA}║${RESET} ${MAGENTA}║${RESET}"
159
+ echo -e "${MAGENTA}╚═══════════════════════════════════════════════════════════════════╝${RESET}"
160
+ echo ""
161
+
162
+ # Show timeout if enabled
163
+ if [ "$TIMEOUT_ENABLED" == "true" ] && [ "$timeout" -gt 0 ]; then
164
+ echo -e "${DIM}Auto-continue in ${timeout}s (press any key to interact)${RESET}"
165
+ fi
166
+
167
+ # Read input with timeout
168
+ local input=""
169
+ if [ "$TIMEOUT_ENABLED" == "true" ] && [ "$timeout" -gt 0 ]; then
170
+ read -t "$timeout" -n 1 input 2>/dev/null || true
171
+ else
172
+ read -n 1 input 2>/dev/null || true
173
+ fi
174
+
175
+ # Handle input
176
+ case "$input" in
177
+ ""|$'\n')
178
+ # Enter or timeout - continue
179
+ TUI_LAST_ACTION="continue"
180
+ return 0
181
+ ;;
182
+ "q"|"Q")
183
+ # Quit
184
+ TUI_LAST_ACTION="quit"
185
+ echo ""
186
+ echo -e "${YELLOW}Quitting gracefully...${RESET}"
187
+ return 1
188
+ ;;
189
+ "p"|"P")
190
+ # Pause
191
+ TUI_LAST_ACTION="pause"
192
+ show_pause_screen
193
+ return 2
194
+ ;;
195
+ "s"|"S")
196
+ # Status
197
+ TUI_LAST_ACTION="status"
198
+ show_status_screen
199
+ show_pause_menu "$task_completed" "$task_total" "$current_task_name" "$timeout"
200
+ return $?
201
+ ;;
202
+ *)
203
+ # Unknown input, continue
204
+ TUI_LAST_ACTION="continue"
205
+ return 0
206
+ ;;
207
+ esac
208
+ }
209
+
210
+ # ═══════════════════════════════════════════════════════════════════════════════
211
+ # Pause Screen
212
+ # ═══════════════════════════════════════════════════════════════════════════════
213
+
214
+ show_pause_screen() {
215
+ clear 2>/dev/null || true
216
+
217
+ echo ""
218
+ echo -e "${YELLOW}╔═══════════════════════════════════════════════════════════════════╗${RESET}"
219
+ echo -e "${YELLOW}║${RESET} ${YELLOW}║${RESET}"
220
+ echo -e "${YELLOW}║${RESET} ${BOLD}⏸️ MERLIN LOOP PAUSED${RESET} ${YELLOW}║${RESET}"
221
+ echo -e "${YELLOW}║${RESET} ${YELLOW}║${RESET}"
222
+ echo -e "${YELLOW}║${RESET} Your work has been saved. You can safely: ${YELLOW}║${RESET}"
223
+ echo -e "${YELLOW}║${RESET} • Close this terminal ${YELLOW}║${RESET}"
224
+ echo -e "${YELLOW}║${RESET} • Come back later ${YELLOW}║${RESET}"
225
+ echo -e "${YELLOW}║${RESET} • Resume with: ${CYAN}merlin-loop resume${RESET} ${YELLOW}║${RESET}"
226
+ echo -e "${YELLOW}║${RESET} ${YELLOW}║${RESET}"
227
+ echo -e "${YELLOW}╠═══════════════════════════════════════════════════════════════════╣${RESET}"
228
+ echo -e "${YELLOW}║${RESET} ${YELLOW}║${RESET}"
229
+ echo -e "${YELLOW}║${RESET} ${GREEN}[Enter]${RESET} Resume now ${RED}[q]${RESET} Quit completely ${YELLOW}║${RESET}"
230
+ echo -e "${YELLOW}║${RESET} ${YELLOW}║${RESET}"
231
+ echo -e "${YELLOW}╚═══════════════════════════════════════════════════════════════════╝${RESET}"
232
+ echo ""
233
+
234
+ # Save checkpoint before pausing
235
+ if type save_checkpoint &> /dev/null; then
236
+ save_checkpoint "Loop paused by user" "" "" "[]" "merlin-loop" 2>/dev/null || true
237
+ fi
238
+
239
+ read -n 1 input
240
+ case "$input" in
241
+ "q"|"Q")
242
+ echo ""
243
+ echo -e "${YELLOW}Exiting. Resume later with: ${CYAN}merlin-loop resume${RESET}"
244
+ exit 0
245
+ ;;
246
+ *)
247
+ echo ""
248
+ echo -e "${GREEN}Resuming...${RESET}"
249
+ ;;
250
+ esac
251
+ }
252
+
253
+ # ═══════════════════════════════════════════════════════════════════════════════
254
+ # Status Screen
255
+ # ═══════════════════════════════════════════════════════════════════════════════
256
+
257
+ show_status_screen() {
258
+ echo ""
259
+ echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${RESET}"
260
+ echo -e "${BOLD}📊 LOOP STATUS${RESET}"
261
+ echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${RESET}"
262
+ echo ""
263
+
264
+ # Read state file
265
+ if [ -f "$STATE_FILE" ] && command -v jq &> /dev/null; then
266
+ local iteration completed errors
267
+ iteration=$(jq -r '.iteration // 0' "$STATE_FILE" 2>/dev/null || echo "0")
268
+ completed=$(jq -r '.tasks_completed // 0' "$STATE_FILE" 2>/dev/null || echo "0")
269
+ errors=$(jq -r '.consecutive_errors // 0' "$STATE_FILE" 2>/dev/null || echo "0")
270
+ local phase=$(jq -r '.current_phase // "N/A"' "$STATE_FILE" 2>/dev/null || echo "N/A")
271
+
272
+ echo -e " ${CYAN}Iterations:${RESET} $iteration"
273
+ echo -e " ${CYAN}Tasks Completed:${RESET} $completed"
274
+ echo -e " ${CYAN}Current Phase:${RESET} $phase"
275
+ echo -e " ${CYAN}Errors:${RESET} $errors"
276
+ else
277
+ echo -e " ${DIM}State file not available${RESET}"
278
+ fi
279
+
280
+ # Sights status
281
+ echo ""
282
+ if type check_sights_connection &> /dev/null; then
283
+ local sights_status
284
+ sights_status=$(check_sights_connection 2>/dev/null || echo "unknown")
285
+ case "$sights_status" in
286
+ "connected")
287
+ echo -e " ${GREEN}🔮 Sights: Connected${RESET}"
288
+ ;;
289
+ *)
290
+ echo -e " ${YELLOW}🔮 Sights: $sights_status${RESET}"
291
+ ;;
292
+ esac
293
+ fi
294
+
295
+ # Git status
296
+ echo ""
297
+ local git_status
298
+ git_status=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
299
+ if [ "$git_status" -eq 0 ]; then
300
+ echo -e " ${GREEN}📁 Git: Clean${RESET}"
301
+ else
302
+ echo -e " ${YELLOW}📁 Git: $git_status uncommitted changes${RESET}"
303
+ fi
304
+
305
+ echo ""
306
+ echo -e "${CYAN}═══════════════════════════════════════════════════════════════════${RESET}"
307
+ echo ""
308
+ echo -e "${DIM}Press any key to continue...${RESET}"
309
+ read -n 1
310
+ }
311
+
312
+ # ═══════════════════════════════════════════════════════════════════════════════
313
+ # Task Header
314
+ # ═══════════════════════════════════════════════════════════════════════════════
315
+
316
+ # Show task header when starting a new task
317
+ show_task_header() {
318
+ local task_num=${1:-1}
319
+ local task_total=${2:-1}
320
+ local task_name="${3:-Task}"
321
+ local agent_name="${4:-claude}"
322
+
323
+ echo ""
324
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
325
+ echo -e "${BOLD}▶️ TASK ${task_num}/${task_total}${RESET}: ${task_name}"
326
+ echo -e "${DIM}Agent: ${agent_name}${RESET}"
327
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
328
+ echo ""
329
+ }
330
+
331
+ # Show task completion
332
+ show_task_complete() {
333
+ local task_name="${1:-Task}"
334
+ local duration="${2:-0}"
335
+ local status="${3:-success}"
336
+
337
+ echo ""
338
+
339
+ if [ "$status" == "success" ]; then
340
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
341
+ echo -e "${GREEN}✅ COMPLETE:${RESET} ${task_name}"
342
+ echo -e "${DIM}Duration: ${duration}s${RESET}"
343
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
344
+ else
345
+ echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
346
+ echo -e "${RED}❌ FAILED:${RESET} ${task_name}"
347
+ echo -e "${DIM}Duration: ${duration}s${RESET}"
348
+ echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
349
+ fi
350
+
351
+ echo ""
352
+ }
353
+
354
+ # ═══════════════════════════════════════════════════════════════════════════════
355
+ # Loop Complete Banner
356
+ # ═══════════════════════════════════════════════════════════════════════════════
357
+
358
+ show_loop_complete() {
359
+ local tasks_completed=${1:-0}
360
+ local total_duration=${2:-0}
361
+ local errors=${3:-0}
362
+
363
+ echo ""
364
+ echo -e "${GREEN}╔═══════════════════════════════════════════════════════════════════╗${RESET}"
365
+ echo -e "${GREEN}║${RESET} ${GREEN}║${RESET}"
366
+ echo -e "${GREEN}║${RESET} ${BOLD}🎉 MERLIN LOOP COMPLETE!${RESET} ${GREEN}║${RESET}"
367
+ echo -e "${GREEN}║${RESET} ${GREEN}║${RESET}"
368
+ echo -e "${GREEN}╠═══════════════════════════════════════════════════════════════════╣${RESET}"
369
+ echo -e "${GREEN}║${RESET} ${GREEN}║${RESET}"
370
+ printf "${GREEN}║${RESET} ${CYAN}Tasks Completed:${RESET} %-45s ${GREEN}║${RESET}\n" "$tasks_completed"
371
+ printf "${GREEN}║${RESET} ${CYAN}Total Duration:${RESET} %-45s ${GREEN}║${RESET}\n" "${total_duration}s"
372
+ printf "${GREEN}║${RESET} ${CYAN}Errors:${RESET} %-45s ${GREEN}║${RESET}\n" "$errors"
373
+ echo -e "${GREEN}║${RESET} ${GREEN}║${RESET}"
374
+ echo -e "${GREEN}╚═══════════════════════════════════════════════════════════════════╝${RESET}"
375
+ echo ""
376
+ }
377
+
378
+ # ═══════════════════════════════════════════════════════════════════════════════
379
+ # Exports
380
+ # ═══════════════════════════════════════════════════════════════════════════════
381
+
382
+ export -f draw_progress_bar 2>/dev/null || true
383
+ export -f start_spinner 2>/dev/null || true
384
+ export -f stop_spinner 2>/dev/null || true
385
+ export -f show_pause_menu 2>/dev/null || true
386
+ export -f show_task_header 2>/dev/null || true
387
+ export -f show_task_complete 2>/dev/null || true
388
+ export -f show_loop_complete 2>/dev/null || true