agentk8 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.
package/lib/ui.sh ADDED
@@ -0,0 +1,415 @@
1
+ #!/usr/bin/env bash
2
+ # AGENT-K UI Library
3
+ # Pretty terminal output, spinners, boxes, and status displays
4
+
5
+ set -euo pipefail
6
+
7
+ # Source core for colors
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/core.sh"
10
+
11
+ # =============================================================================
12
+ # UNICODE CHARACTERS
13
+ # =============================================================================
14
+
15
+ # Box drawing
16
+ BOX_TL="╭"
17
+ BOX_TR="╮"
18
+ BOX_BL="╰"
19
+ BOX_BR="╯"
20
+ BOX_H="─"
21
+ BOX_V="│"
22
+ BOX_CROSS="┼"
23
+ BOX_T_DOWN="┬"
24
+ BOX_T_UP="┴"
25
+ BOX_T_RIGHT="├"
26
+ BOX_T_LEFT="┤"
27
+
28
+ # Status indicators
29
+ STATUS_WAITING="[ ]"
30
+ STATUS_WORKING="[◐]"
31
+ STATUS_ACTIVE="[●]"
32
+ STATUS_DONE="[✓]"
33
+ STATUS_FAILED="[✗]"
34
+
35
+ # Spinner frames
36
+ SPINNER_FRAMES=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
37
+
38
+ # Progress bar
39
+ PROGRESS_FILLED="█"
40
+ PROGRESS_EMPTY="░"
41
+
42
+ # =============================================================================
43
+ # SCREEN UTILITIES
44
+ # =============================================================================
45
+
46
+ get_terminal_width() {
47
+ tput cols 2>/dev/null || echo 80
48
+ }
49
+
50
+ get_terminal_height() {
51
+ tput lines 2>/dev/null || echo 24
52
+ }
53
+
54
+ clear_screen() {
55
+ printf "\033[2J\033[H"
56
+ }
57
+
58
+ move_cursor() {
59
+ local row="$1"
60
+ local col="$2"
61
+ printf "\033[%d;%dH" "$row" "$col"
62
+ }
63
+
64
+ hide_cursor() {
65
+ printf "\033[?25l"
66
+ }
67
+
68
+ show_cursor() {
69
+ printf "\033[?25h"
70
+ }
71
+
72
+ save_cursor() {
73
+ printf "\033[s"
74
+ }
75
+
76
+ restore_cursor() {
77
+ printf "\033[u"
78
+ }
79
+
80
+ # =============================================================================
81
+ # BOX DRAWING
82
+ # =============================================================================
83
+
84
+ draw_box() {
85
+ local title="$1"
86
+ local width="${2:-$(get_terminal_width)}"
87
+
88
+ # Ensure minimum width
89
+ [[ $width -lt 20 ]] && width=20
90
+
91
+ local inner_width=$((width - 2))
92
+ local title_len=${#title}
93
+ local padding=$(( (inner_width - title_len) / 2 ))
94
+
95
+ # Top border
96
+ printf "%s" "$BOX_TL"
97
+ printf "%${inner_width}s" | tr ' ' "$BOX_H"
98
+ printf "%s\n" "$BOX_TR"
99
+
100
+ # Title line
101
+ if [[ -n "$title" ]]; then
102
+ printf "%s" "$BOX_V"
103
+ printf "%${padding}s" ""
104
+ printf "${BOLD}%s${RESET}" "$title"
105
+ printf "%$((inner_width - padding - title_len))s" ""
106
+ printf "%s\n" "$BOX_V"
107
+
108
+ # Separator after title
109
+ printf "%s" "$BOX_V"
110
+ printf "%${inner_width}s" | tr ' ' "$BOX_H"
111
+ printf "%s\n" "$BOX_V"
112
+ fi
113
+ }
114
+
115
+ draw_box_line() {
116
+ local content="$1"
117
+ local width="${2:-$(get_terminal_width)}"
118
+ local inner_width=$((width - 4))
119
+
120
+ printf "%s " "$BOX_V"
121
+ printf "%-${inner_width}s" "$content"
122
+ printf " %s\n" "$BOX_V"
123
+ }
124
+
125
+ close_box() {
126
+ local width="${1:-$(get_terminal_width)}"
127
+ local inner_width=$((width - 2))
128
+
129
+ printf "%s" "$BOX_BL"
130
+ printf "%${inner_width}s" | tr ' ' "$BOX_H"
131
+ printf "%s\n" "$BOX_BR"
132
+ }
133
+
134
+ # =============================================================================
135
+ # HEADER / BANNER
136
+ # =============================================================================
137
+
138
+ print_banner() {
139
+ local width
140
+ width=$(get_terminal_width)
141
+ [[ $width -gt 60 ]] && width=60
142
+
143
+ echo
144
+ draw_box "AGENT-K v${AGENTK_VERSION}" "$width"
145
+ close_box "$width"
146
+ echo
147
+ }
148
+
149
+ print_mode_banner() {
150
+ local mode="$1"
151
+ local mode_display
152
+
153
+ case "$mode" in
154
+ dev) mode_display="Software Development Mode" ;;
155
+ ml) mode_display="ML Research & Training Mode" ;;
156
+ *) mode_display="$mode Mode" ;;
157
+ esac
158
+
159
+ echo "${DIM}Mode: ${RESET}${CYAN}$mode_display${RESET}"
160
+ echo
161
+ }
162
+
163
+ # =============================================================================
164
+ # STATUS INDICATORS
165
+ # =============================================================================
166
+
167
+ get_status_indicator() {
168
+ local status="$1"
169
+
170
+ case "$status" in
171
+ waiting|pending) echo "${DIM}${STATUS_WAITING}${RESET}" ;;
172
+ working|in_progress) echo "${YELLOW}${STATUS_WORKING}${RESET}" ;;
173
+ active) echo "${CYAN}${STATUS_ACTIVE}${RESET}" ;;
174
+ done|completed) echo "${GREEN}${STATUS_DONE}${RESET}" ;;
175
+ failed|error) echo "${RED}${STATUS_FAILED}${RESET}" ;;
176
+ *) echo "${STATUS_WAITING}" ;;
177
+ esac
178
+ }
179
+
180
+ print_agent_status() {
181
+ local agent="$1"
182
+ local status="$2"
183
+ local message="${3:-}"
184
+
185
+ local indicator
186
+ indicator=$(get_status_indicator "$status")
187
+ local agent_display
188
+ agent_display=$(printf "%-12s" "$agent")
189
+
190
+ echo "${indicator} ${BOLD}${agent_display}${RESET} ${DIM}${message}${RESET}"
191
+ }
192
+
193
+ print_all_agent_status() {
194
+ local -n agents_ref=$1 # nameref to associative array
195
+
196
+ for agent in "${!agents_ref[@]}"; do
197
+ local status="${agents_ref[$agent]%%:*}"
198
+ local message="${agents_ref[$agent]#*:}"
199
+ print_agent_status "$agent" "$status" "$message"
200
+ done
201
+ }
202
+
203
+ # =============================================================================
204
+ # SPINNER
205
+ # =============================================================================
206
+
207
+ # Global spinner state
208
+ _SPINNER_PID=""
209
+ _SPINNER_MSG=""
210
+
211
+ start_spinner() {
212
+ local message="${1:-Working...}"
213
+ _SPINNER_MSG="$message"
214
+
215
+ hide_cursor
216
+
217
+ (
218
+ local i=0
219
+ while true; do
220
+ printf "\r${YELLOW}%s${RESET} %s" "${SPINNER_FRAMES[$i]}" "$message"
221
+ i=$(( (i + 1) % ${#SPINNER_FRAMES[@]} ))
222
+ sleep 0.1
223
+ done
224
+ ) &
225
+
226
+ _SPINNER_PID=$!
227
+ disown "$_SPINNER_PID" 2>/dev/null || true
228
+ }
229
+
230
+ stop_spinner() {
231
+ local success="${1:-true}"
232
+ local final_message="${2:-$_SPINNER_MSG}"
233
+
234
+ if [[ -n "$_SPINNER_PID" ]]; then
235
+ kill "$_SPINNER_PID" 2>/dev/null || true
236
+ wait "$_SPINNER_PID" 2>/dev/null || true
237
+ _SPINNER_PID=""
238
+ fi
239
+
240
+ # Clear the spinner line
241
+ printf "\r%${COLUMNS:-80}s\r" ""
242
+
243
+ if [[ "$success" == "true" ]]; then
244
+ echo "${GREEN}${STATUS_DONE}${RESET} $final_message"
245
+ else
246
+ echo "${RED}${STATUS_FAILED}${RESET} $final_message"
247
+ fi
248
+
249
+ show_cursor
250
+ }
251
+
252
+ # =============================================================================
253
+ # PROGRESS BAR
254
+ # =============================================================================
255
+
256
+ print_progress_bar() {
257
+ local current="$1"
258
+ local total="$2"
259
+ local width="${3:-40}"
260
+ local label="${4:-Progress}"
261
+
262
+ local percent=$((current * 100 / total))
263
+ local filled=$((current * width / total))
264
+ local empty=$((width - filled))
265
+
266
+ printf "%s: " "$label"
267
+
268
+ # Filled portion
269
+ printf "${GREEN}"
270
+ for ((i = 0; i < filled; i++)); do
271
+ printf "%s" "$PROGRESS_FILLED"
272
+ done
273
+
274
+ # Empty portion
275
+ printf "${DIM}"
276
+ for ((i = 0; i < empty; i++)); do
277
+ printf "%s" "$PROGRESS_EMPTY"
278
+ done
279
+
280
+ printf "${RESET} %3d%%\n" "$percent"
281
+ }
282
+
283
+ # =============================================================================
284
+ # MESSAGES
285
+ # =============================================================================
286
+
287
+ print_task() {
288
+ local task="$1"
289
+ echo
290
+ echo "${BOLD}Task:${RESET} ${CYAN}\"$task\"${RESET}"
291
+ echo
292
+ }
293
+
294
+ print_orchestrator_message() {
295
+ local message="$1"
296
+ echo "${MAGENTA}[Orchestrator]${RESET} $message"
297
+ }
298
+
299
+ print_agent_message() {
300
+ local agent="$1"
301
+ local message="$2"
302
+ echo "${CYAN}[$agent]${RESET} $message"
303
+ }
304
+
305
+ print_user_prompt() {
306
+ printf "\n${GREEN}You:${RESET} "
307
+ }
308
+
309
+ print_focus_prompt() {
310
+ local agent="$1"
311
+ printf "\n${CYAN}[$agent]${RESET} ${GREEN}You:${RESET} "
312
+ }
313
+
314
+ print_task_assignment() {
315
+ local agent="$1"
316
+ local task="$2"
317
+ echo " ${DIM}→${RESET} ${BOLD}$agent:${RESET} $task"
318
+ }
319
+
320
+ # =============================================================================
321
+ # DIVIDERS
322
+ # =============================================================================
323
+
324
+ print_divider() {
325
+ local char="${1:-─}"
326
+ local width
327
+ width=$(get_terminal_width)
328
+ printf "${DIM}"
329
+ printf "%${width}s" | tr ' ' "$char"
330
+ printf "${RESET}\n"
331
+ }
332
+
333
+ print_section() {
334
+ local title="$1"
335
+ echo
336
+ print_divider
337
+ echo "${BOLD}$title${RESET}"
338
+ print_divider
339
+ }
340
+
341
+ # =============================================================================
342
+ # HELP TEXT
343
+ # =============================================================================
344
+
345
+ print_command_help() {
346
+ echo
347
+ echo "${BOLD}Session Commands:${RESET}"
348
+ echo " ${CYAN}/status${RESET} Show all agent states"
349
+ echo " ${CYAN}/logs <agent>${RESET} View agent output"
350
+ echo " ${CYAN}/kill <agent>${RESET} Stop agent(s)"
351
+ echo " ${CYAN}/focus <agent>${RESET} Talk directly to agent"
352
+ echo " ${CYAN}/unfocus${RESET} Return to orchestrator"
353
+ echo " ${CYAN}/visual${RESET} Toggle tmux view"
354
+ echo " ${CYAN}/clear${RESET} Clear screen"
355
+ echo " ${CYAN}/exit${RESET} End session"
356
+ echo
357
+ }
358
+
359
+ print_scout_commands() {
360
+ echo
361
+ echo "${BOLD}Scout Commands:${RESET}"
362
+ echo " ${CYAN}/search <query>${RESET} Web search"
363
+ echo " ${CYAN}/github <query>${RESET} Search GitHub"
364
+ echo " ${CYAN}/papers <topic>${RESET} Search papers"
365
+ echo " ${CYAN}/libs <task>${RESET} Find libraries"
366
+ echo " ${CYAN}/sota <topic>${RESET} State-of-the-art"
367
+ echo
368
+ }
369
+
370
+ print_ml_commands() {
371
+ echo
372
+ echo "${BOLD}ML Commands:${RESET}"
373
+ echo " ${CYAN}/experiment <name>${RESET} Start experiment"
374
+ echo " ${CYAN}/metrics${RESET} Show metrics"
375
+ echo " ${CYAN}/tensorboard${RESET} Open TensorBoard"
376
+ echo " ${CYAN}/checkpoint${RESET} Save model"
377
+ echo " ${CYAN}/compare <e1> <e2>${RESET} Compare experiments"
378
+ echo " ${CYAN}/huggingface <q>${RESET} Search HF Hub"
379
+ echo
380
+ }
381
+
382
+ # =============================================================================
383
+ # ERROR DISPLAY
384
+ # =============================================================================
385
+
386
+ print_error() {
387
+ local message="$1"
388
+ echo "${RED}${BOLD}Error:${RESET} ${RED}$message${RESET}" >&2
389
+ }
390
+
391
+ print_warning() {
392
+ local message="$1"
393
+ echo "${YELLOW}${BOLD}Warning:${RESET} ${YELLOW}$message${RESET}"
394
+ }
395
+
396
+ print_success() {
397
+ local message="$1"
398
+ echo "${GREEN}${BOLD}Success:${RESET} $message"
399
+ }
400
+
401
+ print_info() {
402
+ local message="$1"
403
+ echo "${CYAN}${BOLD}Info:${RESET} $message"
404
+ }
405
+
406
+ # =============================================================================
407
+ # CLEANUP ON EXIT
408
+ # =============================================================================
409
+
410
+ ui_cleanup() {
411
+ show_cursor
412
+ [[ -n "${_SPINNER_PID:-}" ]] && kill "$_SPINNER_PID" 2>/dev/null || true
413
+ }
414
+
415
+ trap ui_cleanup EXIT