happy-stacks 0.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/README.md +314 -0
- package/bin/happys.mjs +168 -0
- package/docs/menubar.md +186 -0
- package/docs/mobile-ios.md +134 -0
- package/docs/remote-access.md +43 -0
- package/docs/server-flavors.md +79 -0
- package/docs/stacks.md +218 -0
- package/docs/tauri.md +62 -0
- package/docs/worktrees-and-forks.md +395 -0
- package/extras/swiftbar/auth-login.sh +31 -0
- package/extras/swiftbar/happy-stacks.5s.sh +218 -0
- package/extras/swiftbar/icons/happy-green.png +0 -0
- package/extras/swiftbar/icons/happy-orange.png +0 -0
- package/extras/swiftbar/icons/happy-red.png +0 -0
- package/extras/swiftbar/icons/logo-white.png +0 -0
- package/extras/swiftbar/install.sh +191 -0
- package/extras/swiftbar/lib/git.sh +330 -0
- package/extras/swiftbar/lib/icons.sh +105 -0
- package/extras/swiftbar/lib/render.sh +774 -0
- package/extras/swiftbar/lib/system.sh +190 -0
- package/extras/swiftbar/lib/utils.sh +205 -0
- package/extras/swiftbar/pnpm-term.sh +125 -0
- package/extras/swiftbar/pnpm.sh +21 -0
- package/extras/swiftbar/set-interval.sh +62 -0
- package/extras/swiftbar/set-server-flavor.sh +57 -0
- package/extras/swiftbar/wt-pr.sh +95 -0
- package/package.json +58 -0
- package/scripts/auth.mjs +272 -0
- package/scripts/build.mjs +204 -0
- package/scripts/cli-link.mjs +58 -0
- package/scripts/completion.mjs +364 -0
- package/scripts/daemon.mjs +349 -0
- package/scripts/dev.mjs +181 -0
- package/scripts/doctor.mjs +342 -0
- package/scripts/happy.mjs +79 -0
- package/scripts/init.mjs +232 -0
- package/scripts/install.mjs +379 -0
- package/scripts/menubar.mjs +107 -0
- package/scripts/mobile.mjs +305 -0
- package/scripts/run.mjs +236 -0
- package/scripts/self.mjs +298 -0
- package/scripts/server_flavor.mjs +125 -0
- package/scripts/service.mjs +526 -0
- package/scripts/stack.mjs +815 -0
- package/scripts/tailscale.mjs +278 -0
- package/scripts/uninstall.mjs +190 -0
- package/scripts/utils/args.mjs +17 -0
- package/scripts/utils/cli.mjs +24 -0
- package/scripts/utils/cli_registry.mjs +262 -0
- package/scripts/utils/config.mjs +40 -0
- package/scripts/utils/dotenv.mjs +30 -0
- package/scripts/utils/env.mjs +138 -0
- package/scripts/utils/env_file.mjs +59 -0
- package/scripts/utils/env_local.mjs +25 -0
- package/scripts/utils/fs.mjs +11 -0
- package/scripts/utils/paths.mjs +184 -0
- package/scripts/utils/pm.mjs +294 -0
- package/scripts/utils/ports.mjs +66 -0
- package/scripts/utils/proc.mjs +66 -0
- package/scripts/utils/runtime.mjs +30 -0
- package/scripts/utils/server.mjs +41 -0
- package/scripts/utils/smoke_help.mjs +45 -0
- package/scripts/utils/validate.mjs +47 -0
- package/scripts/utils/wizard.mjs +69 -0
- package/scripts/utils/worktrees.mjs +78 -0
- package/scripts/where.mjs +105 -0
- package/scripts/worktrees.mjs +1721 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Requires:
|
|
4
|
+
# - utils.sh
|
|
5
|
+
# - icons.sh
|
|
6
|
+
# - system.sh
|
|
7
|
+
|
|
8
|
+
level_from_server_daemon() {
|
|
9
|
+
local server_status="$1"
|
|
10
|
+
local daemon_status="$2"
|
|
11
|
+
if [[ "$server_status" == "running" && "$daemon_status" == "running" ]]; then
|
|
12
|
+
echo "green"
|
|
13
|
+
return
|
|
14
|
+
fi
|
|
15
|
+
if [[ "$server_status" == "running" || "$daemon_status" == "running" ]]; then
|
|
16
|
+
echo "orange"
|
|
17
|
+
return
|
|
18
|
+
fi
|
|
19
|
+
echo "red"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
color_for_level() {
|
|
23
|
+
local level="$1"
|
|
24
|
+
if [[ "$level" == "green" ]]; then echo "$GREEN"; return; fi
|
|
25
|
+
if [[ "$level" == "orange" ]]; then echo "$YELLOW"; return; fi
|
|
26
|
+
echo "$RED"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
sf_for_level() {
|
|
30
|
+
local level="$1"
|
|
31
|
+
if [[ "$level" == "green" ]]; then echo "checkmark.circle.fill"; return; fi
|
|
32
|
+
if [[ "$level" == "orange" ]]; then echo "exclamationmark.triangle.fill"; return; fi
|
|
33
|
+
echo "xmark.circle.fill"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
print_item() {
|
|
37
|
+
local prefix="$1"
|
|
38
|
+
shift
|
|
39
|
+
echo "${prefix}$*"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
print_sep() {
|
|
43
|
+
local prefix="$1"
|
|
44
|
+
print_item "$prefix" "---"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
render_component_server() {
|
|
48
|
+
local prefix="$1" # "" for top-level, "--" for stack submenu
|
|
49
|
+
local stack_name="$2" # main | <name>
|
|
50
|
+
local port="$3"
|
|
51
|
+
local server_component="$4"
|
|
52
|
+
local server_status="$5"
|
|
53
|
+
local server_pid="$6"
|
|
54
|
+
local server_metrics="$7"
|
|
55
|
+
local tailscale_url="$8" # main only (optional)
|
|
56
|
+
local launch_label="${9:-}" # optional (com.happy.stacks[.<stack>])
|
|
57
|
+
|
|
58
|
+
local level="red"
|
|
59
|
+
[[ "$server_status" == "running" ]] && level="green"
|
|
60
|
+
|
|
61
|
+
local label="Server (${server_component})"
|
|
62
|
+
local sf="$(sf_for_level "$level")"
|
|
63
|
+
local color="$(color_for_level "$level")"
|
|
64
|
+
print_item "$prefix" "$label | sfimage=$sf color=$color"
|
|
65
|
+
|
|
66
|
+
local p2="${prefix}--"
|
|
67
|
+
print_item "$p2" "Status: $server_status"
|
|
68
|
+
print_item "$p2" "Internal: http://127.0.0.1:${port}"
|
|
69
|
+
if [[ -n "$server_pid" ]]; then
|
|
70
|
+
if [[ -n "$server_metrics" ]]; then
|
|
71
|
+
local cpu mem etime
|
|
72
|
+
cpu="$(echo "$server_metrics" | cut -d'|' -f1)"
|
|
73
|
+
mem="$(echo "$server_metrics" | cut -d'|' -f2)"
|
|
74
|
+
etime="$(echo "$server_metrics" | cut -d'|' -f3)"
|
|
75
|
+
print_item "$p2" "PID: ${server_pid}, CPU: ${cpu}%, RAM: ${mem}MB, Uptime: ${etime}"
|
|
76
|
+
else
|
|
77
|
+
print_item "$p2" "PID: ${server_pid}"
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
print_item "$p2" "Open UI (local) | href=http://localhost:${port}/"
|
|
81
|
+
print_item "$p2" "Open Health | href=http://127.0.0.1:${port}/health"
|
|
82
|
+
if [[ -n "$tailscale_url" ]]; then
|
|
83
|
+
print_item "$p2" "Open UI (Tailscale) | href=$tailscale_url"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Start/stop shortcuts (so you can control from the Server submenu too).
|
|
87
|
+
if [[ -n "$PNPM_BIN" ]]; then
|
|
88
|
+
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
|
|
89
|
+
local plist=""
|
|
90
|
+
local svc_installed="0"
|
|
91
|
+
if [[ -n "$launch_label" ]]; then
|
|
92
|
+
plist="$HOME/Library/LaunchAgents/${launch_label}.plist"
|
|
93
|
+
[[ -f "$plist" ]] && svc_installed="1"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
print_sep "$p2"
|
|
97
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
98
|
+
if [[ "$svc_installed" == "1" ]]; then
|
|
99
|
+
if [[ "$server_status" == "running" ]]; then
|
|
100
|
+
print_item "$p2" "Stop stack (service) | bash=$PNPM_BIN param1=service:stop dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
101
|
+
else
|
|
102
|
+
print_item "$p2" "Start stack (service) | bash=$PNPM_BIN param1=service:start dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
103
|
+
fi
|
|
104
|
+
print_item "$p2" "Restart stack (service) | bash=$PNPM_BIN param1=service:restart dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
105
|
+
else
|
|
106
|
+
if [[ "$server_status" == "running" ]]; then
|
|
107
|
+
print_item "$p2" "Stop (kill port listeners) | bash=$PNPM_TERM param1=stack:fix dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
108
|
+
else
|
|
109
|
+
print_item "$p2" "Start stack (foreground) | bash=$PNPM_TERM param1=start dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
110
|
+
fi
|
|
111
|
+
fi
|
|
112
|
+
else
|
|
113
|
+
if [[ "$svc_installed" == "1" ]]; then
|
|
114
|
+
if [[ "$server_status" == "running" ]]; then
|
|
115
|
+
print_item "$p2" "Stop stack (service) | bash=$PNPM_BIN param1=stack param2=service:stop param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
116
|
+
else
|
|
117
|
+
print_item "$p2" "Start stack (service) | bash=$PNPM_BIN param1=stack param2=service:start param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
118
|
+
fi
|
|
119
|
+
print_item "$p2" "Restart stack (service) | bash=$PNPM_BIN param1=stack param2=service:restart param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
120
|
+
else
|
|
121
|
+
if [[ "$server_status" == "running" ]]; then
|
|
122
|
+
print_item "$p2" "Stop (kill port listeners) | bash=$PNPM_TERM param1=stack param2=fix param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
123
|
+
else
|
|
124
|
+
print_item "$p2" "Start stack (foreground) | bash=$PNPM_TERM param1=stack param2=start param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Flavor switching (status-aware: only show switching to the other option).
|
|
131
|
+
local helper="$HAPPY_LOCAL_DIR/extras/swiftbar/set-server-flavor.sh"
|
|
132
|
+
if [[ -n "$PNPM_BIN" ]]; then
|
|
133
|
+
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
|
|
134
|
+
print_sep "$p2"
|
|
135
|
+
if [[ "$server_component" == "happy-server" ]]; then
|
|
136
|
+
print_item "$p2" "Switch to happy-server-light (restart if service installed) | bash=$helper param1=$stack_name param2=happy-server-light dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
137
|
+
else
|
|
138
|
+
print_item "$p2" "Switch to happy-server (restart if service installed) | bash=$helper param1=$stack_name param2=happy-server dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
139
|
+
fi
|
|
140
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
141
|
+
print_item "$p2" "Show flavor status | bash=$PNPM_TERM param1=srv param2=-- param3=status dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
142
|
+
else
|
|
143
|
+
print_item "$p2" "Show flavor status | bash=$PNPM_TERM param1=stack param2=srv param3=$stack_name param4=-- param5=status dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
render_component_daemon() {
|
|
149
|
+
local prefix="$1"
|
|
150
|
+
local daemon_status="$2" # running|stale|stopped|unknown|running-no-http|auth_required|starting
|
|
151
|
+
local daemon_pid="$3"
|
|
152
|
+
local daemon_metrics="$4"
|
|
153
|
+
local daemon_uptime="$5"
|
|
154
|
+
local last_heartbeat="$6"
|
|
155
|
+
local state_file="$7"
|
|
156
|
+
local stack_name="$8"
|
|
157
|
+
|
|
158
|
+
local level="red"
|
|
159
|
+
if [[ "$daemon_status" == "running" ]]; then level="green"; fi
|
|
160
|
+
if [[ "$daemon_status" == "running-no-http" || "$daemon_status" == "stale" || "$daemon_status" == "auth_required" || "$daemon_status" == "starting" ]]; then level="orange"; fi
|
|
161
|
+
|
|
162
|
+
local sf="$(sf_for_level "$level")"
|
|
163
|
+
local color="$(color_for_level "$level")"
|
|
164
|
+
print_item "$prefix" "Daemon | sfimage=$sf color=$color"
|
|
165
|
+
|
|
166
|
+
local p2="${prefix}--"
|
|
167
|
+
print_item "$p2" "Status: $daemon_status"
|
|
168
|
+
if [[ -n "$daemon_pid" ]]; then
|
|
169
|
+
if [[ -n "$daemon_metrics" ]]; then
|
|
170
|
+
local cpu mem etime
|
|
171
|
+
cpu="$(echo "$daemon_metrics" | cut -d'|' -f1)"
|
|
172
|
+
mem="$(echo "$daemon_metrics" | cut -d'|' -f2)"
|
|
173
|
+
etime="$(echo "$daemon_metrics" | cut -d'|' -f3)"
|
|
174
|
+
print_item "$p2" "PID: ${daemon_pid}, CPU: ${cpu}%, RAM: ${mem}MB, Uptime: ${etime}"
|
|
175
|
+
else
|
|
176
|
+
print_item "$p2" "PID: ${daemon_pid}"
|
|
177
|
+
fi
|
|
178
|
+
fi
|
|
179
|
+
[[ -n "$daemon_uptime" ]] && print_item "$p2" "Started: $(shorten_text "$daemon_uptime" 52)"
|
|
180
|
+
[[ -n "$last_heartbeat" ]] && print_item "$p2" "Last heartbeat: $(shorten_text "$last_heartbeat" 52)"
|
|
181
|
+
# State file may not exist yet (e.g. daemon is waiting for auth).
|
|
182
|
+
print_item "$p2" "State file: $(shorten_path "$state_file" 52)"
|
|
183
|
+
|
|
184
|
+
if [[ -n "$PNPM_BIN" ]]; then
|
|
185
|
+
print_sep "$p2"
|
|
186
|
+
if [[ "$daemon_status" == "auth_required" ]]; then
|
|
187
|
+
# Provide a direct "fix" action for the common first-run problem under launchd.
|
|
188
|
+
local auth_helper="$HAPPY_LOCAL_DIR/extras/swiftbar/auth-login.sh"
|
|
189
|
+
local server_url="http://127.0.0.1:$(resolve_main_port)"
|
|
190
|
+
local webapp_url
|
|
191
|
+
webapp_url="$(get_tailscale_url)"
|
|
192
|
+
[[ -z "$webapp_url" ]] && webapp_url="http://localhost:$(resolve_main_port)"
|
|
193
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
194
|
+
print_item "$p2" "Auth login (opens browser) | bash=$auth_helper param1=main param2=$server_url param3=$webapp_url dir=$HAPPY_LOCAL_DIR terminal=false refresh=false"
|
|
195
|
+
else
|
|
196
|
+
# For stacks, best-effort use the stack's configured port if available (fallback to main port).
|
|
197
|
+
local env_file="$HOME/.happy/stacks/$stack_name/env"
|
|
198
|
+
local port
|
|
199
|
+
port="$(dotenv_get "$env_file" "HAPPY_LOCAL_SERVER_PORT")"
|
|
200
|
+
[[ -z "$port" ]] && port="$(resolve_main_port)"
|
|
201
|
+
server_url="http://127.0.0.1:${port}"
|
|
202
|
+
webapp_url="$(get_tailscale_url)"
|
|
203
|
+
[[ -z "$webapp_url" ]] && webapp_url="http://localhost:${port}"
|
|
204
|
+
print_item "$p2" "Auth login (opens browser) | bash=$auth_helper param1=$stack_name param2=$server_url param3=$webapp_url param4=$HOME/.happy/stacks/$stack_name/cli dir=$HAPPY_LOCAL_DIR terminal=false refresh=false"
|
|
205
|
+
fi
|
|
206
|
+
print_sep "$p2"
|
|
207
|
+
fi
|
|
208
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
209
|
+
print_item "$p2" "Restart stack (service) | bash=$PNPM_BIN param1=service:restart dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
210
|
+
else
|
|
211
|
+
print_item "$p2" "Restart stack (service) | bash=$PNPM_BIN param1=stack param2=service:restart param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
212
|
+
fi
|
|
213
|
+
fi
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
render_component_autostart() {
|
|
217
|
+
local prefix="$1"
|
|
218
|
+
local stack_name="$2"
|
|
219
|
+
local label="$3"
|
|
220
|
+
local launchagent_status="$4" # loaded|unloaded|not_installed
|
|
221
|
+
local autostart_pid="$5"
|
|
222
|
+
local autostart_metrics="$6"
|
|
223
|
+
local logs_dir="$7"
|
|
224
|
+
|
|
225
|
+
local level="red"
|
|
226
|
+
if [[ "$launchagent_status" == "loaded" ]]; then level="green"; fi
|
|
227
|
+
if [[ "$launchagent_status" == "unloaded" ]]; then level="orange"; fi
|
|
228
|
+
|
|
229
|
+
local sf="$(sf_for_level "$level")"
|
|
230
|
+
local color="$(color_for_level "$level")"
|
|
231
|
+
print_item "$prefix" "Autostart | sfimage=$sf color=$color"
|
|
232
|
+
|
|
233
|
+
local p2="${prefix}--"
|
|
234
|
+
print_item "$p2" "Status: $launchagent_status"
|
|
235
|
+
print_item "$p2" "Plist: $(shorten_path "$HOME/Library/LaunchAgents/${label}.plist" 52)"
|
|
236
|
+
if [[ -n "$autostart_pid" ]]; then
|
|
237
|
+
if [[ -n "$autostart_metrics" ]]; then
|
|
238
|
+
local cpu mem etime
|
|
239
|
+
cpu="$(echo "$autostart_metrics" | cut -d'|' -f1)"
|
|
240
|
+
mem="$(echo "$autostart_metrics" | cut -d'|' -f2)"
|
|
241
|
+
etime="$(echo "$autostart_metrics" | cut -d'|' -f3)"
|
|
242
|
+
print_item "$p2" "PID: ${autostart_pid}, CPU: ${cpu}%, RAM: ${mem}MB, Uptime: ${etime}"
|
|
243
|
+
else
|
|
244
|
+
print_item "$p2" "PID: ${autostart_pid}"
|
|
245
|
+
fi
|
|
246
|
+
fi
|
|
247
|
+
local stdout_file="happy-stacks.out.log"
|
|
248
|
+
local stderr_file="happy-stacks.err.log"
|
|
249
|
+
if [[ -f "${logs_dir}/happy-local.out.log" && ! -f "${logs_dir}/happy-stacks.out.log" ]]; then stdout_file="happy-local.out.log"; fi
|
|
250
|
+
if [[ -f "${logs_dir}/happy-local.err.log" && ! -f "${logs_dir}/happy-stacks.err.log" ]]; then stderr_file="happy-local.err.log"; fi
|
|
251
|
+
print_item "$p2" "Open logs (stdout) | bash=/usr/bin/open param1=-a param2=Console param3='${logs_dir}/${stdout_file}' terminal=false"
|
|
252
|
+
print_item "$p2" "Open logs (stderr) | bash=/usr/bin/open param1=-a param2=Console param3='${logs_dir}/${stderr_file}' terminal=false"
|
|
253
|
+
|
|
254
|
+
if [[ -z "$PNPM_BIN" ]]; then
|
|
255
|
+
return
|
|
256
|
+
fi
|
|
257
|
+
print_sep "$p2"
|
|
258
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
259
|
+
if [[ "$launchagent_status" == "not_installed" ]]; then
|
|
260
|
+
print_item "$p2" "Install Autostart | bash=$PNPM_BIN param1=service:install dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
261
|
+
return
|
|
262
|
+
fi
|
|
263
|
+
# Status-aware: show only the relevant toggle (enable vs disable).
|
|
264
|
+
if [[ "$launchagent_status" == "loaded" ]]; then
|
|
265
|
+
print_item "$p2" "Disable Autostart | bash=$PNPM_BIN param1=service:disable dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
266
|
+
else
|
|
267
|
+
print_item "$p2" "Enable Autostart | bash=$PNPM_BIN param1=service:enable dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
268
|
+
fi
|
|
269
|
+
print_item "$p2" "Uninstall Autostart | bash=$PNPM_BIN param1=service:uninstall dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
270
|
+
return
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
if [[ "$launchagent_status" == "not_installed" ]]; then
|
|
274
|
+
print_item "$p2" "Install Autostart | bash=$PNPM_BIN param1=stack param2=service:install param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
275
|
+
return
|
|
276
|
+
fi
|
|
277
|
+
# Status-aware: show only the relevant toggle (enable vs disable).
|
|
278
|
+
if [[ "$launchagent_status" == "loaded" ]]; then
|
|
279
|
+
print_item "$p2" "Disable Autostart | bash=$PNPM_BIN param1=stack param2=service:disable param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
280
|
+
else
|
|
281
|
+
print_item "$p2" "Enable Autostart | bash=$PNPM_BIN param1=stack param2=service:enable param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
282
|
+
fi
|
|
283
|
+
print_item "$p2" "Uninstall Autostart | bash=$PNPM_BIN param1=stack param2=service:uninstall param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
render_component_tailscale() {
|
|
287
|
+
local prefix="$1"
|
|
288
|
+
local stack_name="$2"
|
|
289
|
+
local tailscale_url="$3"
|
|
290
|
+
|
|
291
|
+
local level="red"
|
|
292
|
+
[[ -n "$tailscale_url" ]] && level="green"
|
|
293
|
+
|
|
294
|
+
local sf="$(sf_for_level "$level")"
|
|
295
|
+
local color="$(color_for_level "$level")"
|
|
296
|
+
print_item "$prefix" "Tailscale | sfimage=$sf color=$color"
|
|
297
|
+
|
|
298
|
+
local p2="${prefix}--"
|
|
299
|
+
if [[ -n "$tailscale_url" ]]; then
|
|
300
|
+
local display="$tailscale_url"
|
|
301
|
+
[[ ${#display} -gt 48 ]] && display="${display:0:48}..."
|
|
302
|
+
print_item "$p2" "URL: $display"
|
|
303
|
+
print_item "$p2" "Copy URL | bash=/bin/bash param1=-c param2='echo -n \"$tailscale_url\" | pbcopy' terminal=false"
|
|
304
|
+
print_item "$p2" "Open URL | href=$tailscale_url"
|
|
305
|
+
else
|
|
306
|
+
print_item "$p2" "Status: not configured / unknown"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
if [[ -z "$PNPM_BIN" ]]; then
|
|
310
|
+
return
|
|
311
|
+
fi
|
|
312
|
+
print_sep "$p2"
|
|
313
|
+
|
|
314
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
315
|
+
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
|
|
316
|
+
print_item "$p2" "Tailscale status | bash=$PNPM_TERM param1=tailscale:status dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
317
|
+
if [[ -n "$tailscale_url" ]]; then
|
|
318
|
+
print_item "$p2" "Disable Tailscale Serve | bash=$PNPM_BIN param1=tailscale:disable dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
319
|
+
else
|
|
320
|
+
print_item "$p2" "Enable Tailscale Serve | bash=$PNPM_TERM param1=tailscale:enable dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
321
|
+
fi
|
|
322
|
+
print_item "$p2" "Print URL | bash=$PNPM_TERM param1=tailscale:url dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
323
|
+
return
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
|
|
327
|
+
print_item "$p2" "Tailscale status | bash=$PNPM_TERM param1=stack param2=tailscale:status param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
328
|
+
if [[ -n "$tailscale_url" ]]; then
|
|
329
|
+
print_item "$p2" "Disable Tailscale Serve | bash=$PNPM_BIN param1=stack param2=tailscale:disable param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
330
|
+
else
|
|
331
|
+
print_item "$p2" "Enable Tailscale Serve | bash=$PNPM_TERM param1=stack param2=tailscale:enable param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
332
|
+
fi
|
|
333
|
+
print_item "$p2" "Print URL | bash=$PNPM_TERM param1=stack param2=tailscale:url param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
render_component_repo() {
|
|
337
|
+
# Git/worktree component view (unified UI).
|
|
338
|
+
# Usage:
|
|
339
|
+
# render_component_repo <prefix> <component> <context> <stack_name> <env_file>
|
|
340
|
+
# context: main|stack
|
|
341
|
+
local prefix="$1"
|
|
342
|
+
local component="$2"
|
|
343
|
+
local context="$3"
|
|
344
|
+
local stack_name="$4"
|
|
345
|
+
local env_file="$5"
|
|
346
|
+
|
|
347
|
+
local active_dir=""
|
|
348
|
+
if [[ "$context" == "stack" && -n "$env_file" ]]; then
|
|
349
|
+
active_dir="$(resolve_component_dir_from_env_file "$env_file" "$component")"
|
|
350
|
+
else
|
|
351
|
+
active_dir="$(resolve_component_dir_from_env "$component")"
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
local level="red"
|
|
355
|
+
local detail="missing"
|
|
356
|
+
if is_git_repo "$active_dir"; then
|
|
357
|
+
local dirty
|
|
358
|
+
dirty="$(git_dirty_flag "$active_dir")"
|
|
359
|
+
local ab
|
|
360
|
+
ab="$(git_ahead_behind "$active_dir")"
|
|
361
|
+
local ahead="" behind=""
|
|
362
|
+
if [[ -n "$ab" ]]; then
|
|
363
|
+
ahead="$(echo "$ab" | cut -d'|' -f1)"
|
|
364
|
+
behind="$(echo "$ab" | cut -d'|' -f2)"
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
if [[ "$dirty" == "dirty" ]] || [[ -n "$behind" && "$behind" != "0" ]]; then
|
|
368
|
+
level="orange"
|
|
369
|
+
else
|
|
370
|
+
level="green"
|
|
371
|
+
fi
|
|
372
|
+
detail="ok"
|
|
373
|
+
fi
|
|
374
|
+
|
|
375
|
+
local sf color
|
|
376
|
+
sf="$(sf_for_level "$level")"
|
|
377
|
+
color="$(color_for_level "$level")"
|
|
378
|
+
print_item "$prefix" "${component} | sfimage=$sf color=$color"
|
|
379
|
+
|
|
380
|
+
local p2="${prefix}--"
|
|
381
|
+
print_item "$p2" "Dir: $(shorten_path "$active_dir" 52)"
|
|
382
|
+
if [[ "$detail" != "ok" ]]; then
|
|
383
|
+
print_item "$p2" "Status: not a git repo / missing"
|
|
384
|
+
if [[ -n "$PNPM_BIN" ]]; then
|
|
385
|
+
print_sep "$p2"
|
|
386
|
+
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
|
|
387
|
+
print_item "$p2" "Bootstrap (clone missing components) | bash=$PNPM_TERM param1=bootstrap dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
388
|
+
fi
|
|
389
|
+
return
|
|
390
|
+
fi
|
|
391
|
+
|
|
392
|
+
local branch head upstream
|
|
393
|
+
branch="$(git_head_branch "$active_dir")"
|
|
394
|
+
head="$(git_head_short "$active_dir")"
|
|
395
|
+
upstream="$(git_upstream_short "$active_dir")"
|
|
396
|
+
|
|
397
|
+
local dirty
|
|
398
|
+
dirty="$(git_dirty_flag "$active_dir")"
|
|
399
|
+
local ab ahead behind
|
|
400
|
+
ab="$(git_ahead_behind "$active_dir")"
|
|
401
|
+
ahead=""
|
|
402
|
+
behind=""
|
|
403
|
+
if [[ -n "$ab" ]]; then
|
|
404
|
+
ahead="$(echo "$ab" | cut -d'|' -f1)"
|
|
405
|
+
behind="$(echo "$ab" | cut -d'|' -f2)"
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
print_item "$p2" "HEAD: ${branch:-"(unknown)"} ${head:+($head)}"
|
|
409
|
+
print_item "$p2" "Upstream: ${upstream:-"(none)"}"
|
|
410
|
+
if [[ -n "$ahead" && -n "$behind" ]]; then
|
|
411
|
+
print_item "$p2" "Ahead/Behind: ${ahead}/${behind}"
|
|
412
|
+
fi
|
|
413
|
+
print_item "$p2" "Working tree: ${dirty}"
|
|
414
|
+
|
|
415
|
+
local main_branch main_upstream main_ab
|
|
416
|
+
main_branch="$(git_main_branch_name "$active_dir")"
|
|
417
|
+
if [[ -n "$main_branch" ]]; then
|
|
418
|
+
main_upstream="$(git_branch_upstream_short "$active_dir" "$main_branch")"
|
|
419
|
+
main_ab="$(git_branch_ahead_behind "$active_dir" "$main_branch")"
|
|
420
|
+
if [[ -n "$main_upstream" ]]; then
|
|
421
|
+
print_item "$p2" "Main: ${main_branch} → ${main_upstream}"
|
|
422
|
+
else
|
|
423
|
+
print_item "$p2" "Main: ${main_branch} → (no upstream)"
|
|
424
|
+
fi
|
|
425
|
+
if [[ -n "$main_ab" ]]; then
|
|
426
|
+
print_item "$p2" "Main ahead/behind: $(echo "$main_ab" | cut -d'|' -f1)/$(echo "$main_ab" | cut -d'|' -f2)"
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
# Always show comparisons against origin/* and upstream/* when those remote refs exist.
|
|
430
|
+
# (These reflect your last fetch; we do not auto-fetch in the menu.)
|
|
431
|
+
local oref uref
|
|
432
|
+
oref="$(git_remote_main_ref "$active_dir" "origin")"
|
|
433
|
+
uref="$(git_remote_main_ref "$active_dir" "upstream")"
|
|
434
|
+
if [[ -n "$oref" ]]; then
|
|
435
|
+
local oref_short="${oref#refs/remotes/}"
|
|
436
|
+
local oab
|
|
437
|
+
oab="$(git_ahead_behind_refs "$active_dir" "$oref" "$main_branch")"
|
|
438
|
+
if [[ -n "$oab" ]]; then
|
|
439
|
+
print_item "$p2" "Origin: ${oref_short} ahead/behind: $(echo "$oab" | cut -d'|' -f1)/$(echo "$oab" | cut -d'|' -f2)"
|
|
440
|
+
else
|
|
441
|
+
print_item "$p2" "Origin: ${oref_short}"
|
|
442
|
+
fi
|
|
443
|
+
else
|
|
444
|
+
print_item "$p2" "Origin: (no origin/main|master ref)"
|
|
445
|
+
fi
|
|
446
|
+
if [[ -n "$uref" ]]; then
|
|
447
|
+
local uref_short="${uref#refs/remotes/}"
|
|
448
|
+
local uab
|
|
449
|
+
uab="$(git_ahead_behind_refs "$active_dir" "$uref" "$main_branch")"
|
|
450
|
+
if [[ -n "$uab" ]]; then
|
|
451
|
+
print_item "$p2" "Upstream: ${uref_short} ahead/behind: $(echo "$uab" | cut -d'|' -f1)/$(echo "$uab" | cut -d'|' -f2)"
|
|
452
|
+
else
|
|
453
|
+
print_item "$p2" "Upstream: ${uref_short}"
|
|
454
|
+
fi
|
|
455
|
+
else
|
|
456
|
+
print_item "$p2" "Upstream: (no upstream/main|master ref)"
|
|
457
|
+
fi
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
local wt_count
|
|
461
|
+
wt_count="$(git_worktree_count "$active_dir")"
|
|
462
|
+
|
|
463
|
+
# Quick actions
|
|
464
|
+
print_sep "$p2"
|
|
465
|
+
print_item "$p2" "Open folder | bash=/usr/bin/open param1='$active_dir' terminal=false"
|
|
466
|
+
|
|
467
|
+
if [[ -n "$PNPM_BIN" ]]; then
|
|
468
|
+
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
|
|
469
|
+
# Run via stack wrappers when in a stack context so env-file stays authoritative.
|
|
470
|
+
if [[ "$context" == "stack" && -n "$stack_name" ]]; then
|
|
471
|
+
print_item "$p2" "Status (active) | bash=$PNPM_TERM param1=stack param2=wt param3=$stack_name param4=-- param5=status param6=$component dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
472
|
+
print_item "$p2" "Sync mirror (upstream/main) | bash=$PNPM_BIN param1=stack param2=wt param3=$stack_name param4=-- param5=sync param6=$component dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
473
|
+
print_item "$p2" "Update (dry-run) | bash=$PNPM_TERM param1=stack param2=wt param3=$stack_name param4=-- param5=update param6=$component param7=active param8=--dry-run dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
474
|
+
print_item "$p2" "Update (apply) | bash=$PNPM_BIN param1=stack param2=wt param3=$stack_name param4=-- param5=update param6=$component param7=active dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
475
|
+
print_item "$p2" "Update (apply + stash) | bash=$PNPM_BIN param1=stack param2=wt param3=$stack_name param4=-- param5=update param6=$component param7=active param8=--stash dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
476
|
+
else
|
|
477
|
+
print_item "$p2" "Status (active) | bash=$PNPM_TERM param1=wt param2=status param3=$component dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
478
|
+
print_item "$p2" "Sync mirror (upstream/main) | bash=$PNPM_BIN param1=wt param2=sync param3=$component dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
479
|
+
print_item "$p2" "Update (dry-run) | bash=$PNPM_TERM param1=wt param2=update param3=$component param4=active param5=--dry-run dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
480
|
+
print_item "$p2" "Update (apply) | bash=$PNPM_BIN param1=wt param2=update param3=$component param4=active dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
481
|
+
print_item "$p2" "Update (apply + stash) | bash=$PNPM_BIN param1=wt param2=update param3=$component param4=active param5=--stash dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
482
|
+
fi
|
|
483
|
+
|
|
484
|
+
print_sep "$p2"
|
|
485
|
+
if [[ "$context" == "stack" && -n "$stack_name" ]]; then
|
|
486
|
+
print_item "$p2" "Switch stack worktree (interactive) | bash=$PNPM_TERM param1=stack param2=wt param3=$stack_name param4=-- param5=use param6=$component param7=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
487
|
+
print_item "$p2" "New worktree (interactive) | bash=$PNPM_TERM param1=stack param2=wt param3=$stack_name param4=-- param5=new param6=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
488
|
+
print_item "$p2" "List worktrees (terminal) | bash=$PNPM_TERM param1=stack param2=wt param3=$stack_name param4=-- param5=list param6=$component dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
489
|
+
else
|
|
490
|
+
print_item "$p2" "Use worktree (interactive) | bash=$PNPM_TERM param1=wt param2=use param3=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
491
|
+
print_item "$p2" "New worktree (interactive) | bash=$PNPM_TERM param1=wt param2=new param3=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
492
|
+
print_item "$p2" "List worktrees (terminal) | bash=$PNPM_TERM param1=wt param2=list param3=$component dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
# PR worktree (prompt)
|
|
496
|
+
local pr_helper="$HAPPY_LOCAL_DIR/extras/swiftbar/wt-pr.sh"
|
|
497
|
+
if [[ -x "$pr_helper" ]]; then
|
|
498
|
+
if [[ "$context" == "stack" && -n "$stack_name" ]]; then
|
|
499
|
+
print_item "$p2" "PR worktree (prompt) | bash=$pr_helper param1=$component param2=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
500
|
+
else
|
|
501
|
+
print_item "$p2" "PR worktree (prompt) | bash=$pr_helper param1=$component dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
502
|
+
fi
|
|
503
|
+
fi
|
|
504
|
+
|
|
505
|
+
print_sep "$p2"
|
|
506
|
+
if [[ "$context" == "stack" && -n "$stack_name" ]]; then
|
|
507
|
+
print_item "$p2" "Shell (active, new window) | bash=$PNPM_TERM param1=stack param2=wt param3=$stack_name param4=-- param5=shell param6=$component param7=active param8=--new-window dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
508
|
+
print_item "$p2" "Open in VS Code (active) | bash=$PNPM_BIN param1=stack param2=wt param3=$stack_name param4=-- param5=code param6=$component param7=active dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
509
|
+
print_item "$p2" "Open in Cursor (active) | bash=$PNPM_BIN param1=stack param2=wt param3=$stack_name param4=-- param5=cursor param6=$component param7=active dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
510
|
+
else
|
|
511
|
+
print_item "$p2" "Shell (active, new window) | bash=$PNPM_TERM param1=wt param2=shell param3=$component param4=active param5=--new-window dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
512
|
+
print_item "$p2" "Open in VS Code (active) | bash=$PNPM_BIN param1=wt param2=code param3=$component param4=active dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
513
|
+
print_item "$p2" "Open in Cursor (active) | bash=$PNPM_BIN param1=wt param2=cursor param3=$component param4=active dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
514
|
+
fi
|
|
515
|
+
|
|
516
|
+
# Worktrees listing (inline in SwiftBar, plus stack-aware switch).
|
|
517
|
+
local wt_label="Worktrees: ${wt_count:-0} | sfimage=arrow.triangle.branch"
|
|
518
|
+
print_item "$p2" "$wt_label"
|
|
519
|
+
local p3="${p2}--"
|
|
520
|
+
local tsv
|
|
521
|
+
tsv="$(git_worktrees_tsv "$active_dir" 2>/dev/null || true)"
|
|
522
|
+
if [[ -z "$tsv" ]]; then
|
|
523
|
+
print_item "$p3" "No worktrees found | color=$GRAY"
|
|
524
|
+
else
|
|
525
|
+
local root="$(resolve_components_dir)/.worktrees/$component/"
|
|
526
|
+
local shown=0
|
|
527
|
+
while IFS=$'\t' read -r wt_path wt_branchref; do
|
|
528
|
+
[[ -n "$wt_path" ]] || continue
|
|
529
|
+
shown=$((shown + 1))
|
|
530
|
+
if [[ $shown -gt 30 ]]; then
|
|
531
|
+
print_item "$p3" "More… (open folder) | bash=/usr/bin/open param1='$root' terminal=false"
|
|
532
|
+
break
|
|
533
|
+
fi
|
|
534
|
+
|
|
535
|
+
local label=""
|
|
536
|
+
local spec=""
|
|
537
|
+
if [[ "$wt_path" == "$root"* ]]; then
|
|
538
|
+
spec="${wt_path#"$root"}"
|
|
539
|
+
label="$spec"
|
|
540
|
+
else
|
|
541
|
+
spec="$wt_path"
|
|
542
|
+
label="$(shorten_path "$wt_path" 52)"
|
|
543
|
+
fi
|
|
544
|
+
|
|
545
|
+
if [[ -n "$wt_branchref" && "$wt_branchref" == refs/heads/* ]]; then
|
|
546
|
+
label="$label ($(basename "$wt_branchref"))"
|
|
547
|
+
fi
|
|
548
|
+
if [[ "$wt_path" == "$active_dir" ]]; then
|
|
549
|
+
label="(active) $label"
|
|
550
|
+
fi
|
|
551
|
+
|
|
552
|
+
if [[ "$context" == "stack" && -n "$stack_name" ]]; then
|
|
553
|
+
print_item "$p3" "$label"
|
|
554
|
+
print_item "${p3}--" "Use in stack | bash=$PNPM_BIN param1=stack param2=wt param3=$stack_name param4=-- param5=use param6=$component param7=$spec dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
555
|
+
print_item "${p3}--" "Shell (new window) | bash=$PNPM_TERM param1=stack param2=wt param3=$stack_name param4=-- param5=shell param6=$component param7=$spec param8=--new-window dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
556
|
+
print_item "${p3}--" "Open in VS Code | bash=$PNPM_BIN param1=stack param2=wt param3=$stack_name param4=-- param5=code param6=$component param7=$spec dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
557
|
+
print_item "${p3}--" "Open in Cursor | bash=$PNPM_BIN param1=stack param2=wt param3=$stack_name param4=-- param5=cursor param6=$component param7=$spec dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
558
|
+
else
|
|
559
|
+
print_item "$p3" "$label"
|
|
560
|
+
print_item "${p3}--" "Use (main) | bash=$PNPM_BIN param1=wt param2=use param3=$component param4=$spec dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
561
|
+
print_item "${p3}--" "Shell (new window) | bash=$PNPM_TERM param1=wt param2=shell param3=$component param4=$spec param5=--new-window dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
562
|
+
print_item "${p3}--" "Open in VS Code | bash=$PNPM_BIN param1=wt param2=code param3=$component param4=$spec dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
563
|
+
print_item "${p3}--" "Open in Cursor | bash=$PNPM_BIN param1=wt param2=cursor param3=$component param4=$spec dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
564
|
+
fi
|
|
565
|
+
done <<<"$tsv"
|
|
566
|
+
fi
|
|
567
|
+
fi
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
render_components_menu() {
|
|
571
|
+
# Usage: render_components_menu <prefix> <context> <stack_name> <env_file>
|
|
572
|
+
local prefix="$1" # "" for main menu, "--" for inside a stack
|
|
573
|
+
local context="$2" # main|stack
|
|
574
|
+
local stack_name="$3"
|
|
575
|
+
local env_file="$4"
|
|
576
|
+
|
|
577
|
+
print_item "$prefix" "Components | sfimage=cube"
|
|
578
|
+
local p2="${prefix}--"
|
|
579
|
+
local components_dir
|
|
580
|
+
components_dir="$(resolve_components_dir)"
|
|
581
|
+
if [[ ! -d "$components_dir" ]]; then
|
|
582
|
+
print_item "$p2" "Missing components dir: $(shorten_path "$components_dir" 52) | color=$GRAY"
|
|
583
|
+
return
|
|
584
|
+
fi
|
|
585
|
+
|
|
586
|
+
local any="0"
|
|
587
|
+
for c in happy happy-cli happy-server-light happy-server; do
|
|
588
|
+
if [[ -d "$components_dir/$c" ]]; then
|
|
589
|
+
any="1"
|
|
590
|
+
render_component_repo "$p2" "$c" "$context" "$stack_name" "$env_file"
|
|
591
|
+
print_sep "$p2"
|
|
592
|
+
fi
|
|
593
|
+
done
|
|
594
|
+
if [[ "$any" == "0" ]]; then
|
|
595
|
+
print_item "$p2" "No components found under components/ | color=$GRAY"
|
|
596
|
+
fi
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
render_stack_overview_item() {
|
|
600
|
+
local title="$1"
|
|
601
|
+
local level="$2" # green|orange|red
|
|
602
|
+
local prefix="$3"
|
|
603
|
+
|
|
604
|
+
local icon_b64
|
|
605
|
+
icon_b64="$(status_icon_b64 "$level" 14)"
|
|
606
|
+
if [[ -n "$icon_b64" ]]; then
|
|
607
|
+
print_item "$prefix" "$title | image=$icon_b64"
|
|
608
|
+
else
|
|
609
|
+
print_item "$prefix" "$title"
|
|
610
|
+
fi
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
collect_stack_status() {
|
|
614
|
+
# Output (tab-separated):
|
|
615
|
+
# level server_status server_pid server_metrics daemon_status daemon_pid daemon_metrics daemon_uptime last_heartbeat launchagent_status autostart_pid autostart_metrics
|
|
616
|
+
local port="$1"
|
|
617
|
+
local cli_home_dir="$2"
|
|
618
|
+
local label="$3"
|
|
619
|
+
local base_dir="$4"
|
|
620
|
+
|
|
621
|
+
local server_status server_pid server_metrics
|
|
622
|
+
server_status="$(check_server_health "$port")"
|
|
623
|
+
server_pid=""
|
|
624
|
+
server_metrics=""
|
|
625
|
+
if [[ "$server_status" == "running" ]]; then
|
|
626
|
+
server_pid="$(get_port_listener_pid "$port")"
|
|
627
|
+
server_metrics="$(get_process_metrics "$server_pid")"
|
|
628
|
+
fi
|
|
629
|
+
|
|
630
|
+
local daemon_raw daemon_status daemon_pid daemon_metrics
|
|
631
|
+
daemon_raw="$(check_daemon_status "$cli_home_dir")"
|
|
632
|
+
daemon_status="$daemon_raw"
|
|
633
|
+
daemon_pid=""
|
|
634
|
+
if [[ "$daemon_raw" == running:* ]] || [[ "$daemon_raw" == running-no-http:* ]]; then
|
|
635
|
+
daemon_pid="${daemon_raw#*:}"
|
|
636
|
+
daemon_status="${daemon_raw%%:*}"
|
|
637
|
+
fi
|
|
638
|
+
daemon_metrics=""
|
|
639
|
+
if [[ -n "$daemon_pid" ]]; then
|
|
640
|
+
daemon_metrics="$(get_process_metrics "$daemon_pid")"
|
|
641
|
+
fi
|
|
642
|
+
|
|
643
|
+
local daemon_uptime last_heartbeat
|
|
644
|
+
daemon_uptime="$(get_daemon_uptime "$cli_home_dir")"
|
|
645
|
+
last_heartbeat="$(get_last_heartbeat "$cli_home_dir")"
|
|
646
|
+
|
|
647
|
+
local plist_path="$HOME/Library/LaunchAgents/${label}.plist"
|
|
648
|
+
local launchagent_status autostart_pid autostart_metrics
|
|
649
|
+
launchagent_status="$(check_launchagent_status "$label" "$plist_path")"
|
|
650
|
+
autostart_pid=""
|
|
651
|
+
autostart_metrics=""
|
|
652
|
+
if [[ "$launchagent_status" != "not_installed" ]]; then
|
|
653
|
+
autostart_pid="$(launchagent_pid_for_label "$label")"
|
|
654
|
+
autostart_metrics="$(get_process_metrics "$autostart_pid")"
|
|
655
|
+
fi
|
|
656
|
+
|
|
657
|
+
local level
|
|
658
|
+
level="$(level_from_server_daemon "$server_status" "$daemon_status")"
|
|
659
|
+
|
|
660
|
+
# Important: callers use `read` with IFS=$'\t' which collapses consecutive delimiters.
|
|
661
|
+
# Emit "-" placeholders for optional/empty fields so parsing stays stable.
|
|
662
|
+
local spid="${server_pid:-"-"}"
|
|
663
|
+
local smet="${server_metrics:-"-"}"
|
|
664
|
+
local dpid="${daemon_pid:-"-"}"
|
|
665
|
+
local dmet="${daemon_metrics:-"-"}"
|
|
666
|
+
local dupt="${daemon_uptime:-"-"}"
|
|
667
|
+
local dhb="${last_heartbeat:-"-"}"
|
|
668
|
+
local apid="${autostart_pid:-"-"}"
|
|
669
|
+
local amet="${autostart_metrics:-"-"}"
|
|
670
|
+
|
|
671
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
|
672
|
+
"$level" \
|
|
673
|
+
"$server_status" "$spid" "$smet" \
|
|
674
|
+
"$daemon_status" "$dpid" "$dmet" "$dupt" "$dhb" \
|
|
675
|
+
"$launchagent_status" "$apid" "$amet"
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
render_stack_info() {
|
|
679
|
+
# Renders a single "Info" item (with actions) at the given prefix.
|
|
680
|
+
local prefix="$1" # "" or "--"
|
|
681
|
+
local stack_name="$2"
|
|
682
|
+
local port="$3"
|
|
683
|
+
local server_component="$4"
|
|
684
|
+
local base_dir="$5"
|
|
685
|
+
local cli_home_dir="$6"
|
|
686
|
+
local label="$7"
|
|
687
|
+
local env_file="$8" # optional
|
|
688
|
+
local tailscale_url="$9" # optional
|
|
689
|
+
|
|
690
|
+
# Avoid low-contrast gray in the main list; keep it readable in both light/dark.
|
|
691
|
+
print_item "$prefix" "Stack details | sfimage=info.circle"
|
|
692
|
+
local p2="${prefix}--"
|
|
693
|
+
print_item "$p2" "Server component: ${server_component}"
|
|
694
|
+
print_item "$p2" "Port: ${port}"
|
|
695
|
+
print_item "$p2" "Label: ${label}"
|
|
696
|
+
[[ -n "$env_file" ]] && print_item "$p2" "Env: $(shorten_path "$env_file" 52)"
|
|
697
|
+
[[ -n "$tailscale_url" ]] && print_item "$p2" "Tailscale: $(shorten_text "$tailscale_url" 52)"
|
|
698
|
+
|
|
699
|
+
print_sep "$p2"
|
|
700
|
+
print_item "$p2" "Open repo | bash=/usr/bin/open param1='$HAPPY_LOCAL_DIR' terminal=false"
|
|
701
|
+
print_item "$p2" "Open data dir | bash=/usr/bin/open param1='$base_dir' terminal=false"
|
|
702
|
+
print_item "$p2" "Open logs dir | bash=/usr/bin/open param1='${base_dir}/logs' terminal=false"
|
|
703
|
+
print_item "$p2" "Open CLI home | bash=/usr/bin/open param1='$cli_home_dir' terminal=false"
|
|
704
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
705
|
+
local main_env
|
|
706
|
+
main_env="$(resolve_main_env_file)"
|
|
707
|
+
if [[ -n "$main_env" ]]; then
|
|
708
|
+
print_item "$p2" "Edit main env | bash=/usr/bin/open param1=-a param2=TextEdit param3='$main_env' terminal=false"
|
|
709
|
+
else
|
|
710
|
+
print_item "$p2" "Edit env.local | bash=/usr/bin/open param1=-a param2=TextEdit param3='$HAPPY_LOCAL_DIR/env.local' terminal=false"
|
|
711
|
+
fi
|
|
712
|
+
else
|
|
713
|
+
print_item "$p2" "Open stack env | bash=/usr/bin/open param1='$env_file' terminal=false"
|
|
714
|
+
fi
|
|
715
|
+
|
|
716
|
+
if [[ -z "$PNPM_BIN" ]]; then
|
|
717
|
+
return
|
|
718
|
+
fi
|
|
719
|
+
print_sep "$p2"
|
|
720
|
+
|
|
721
|
+
local plist="$HOME/Library/LaunchAgents/${label}.plist"
|
|
722
|
+
local svc_installed="0"
|
|
723
|
+
[[ -f "$plist" ]] && svc_installed="1"
|
|
724
|
+
|
|
725
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
726
|
+
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
|
|
727
|
+
if [[ "$svc_installed" == "1" ]]; then
|
|
728
|
+
# Status-aware: only show start/stop based on whether the stack is running.
|
|
729
|
+
if [[ "${MAIN_LEVEL:-}" == "red" ]]; then
|
|
730
|
+
print_item "$p2" "Start (service) | bash=$PNPM_BIN param1=service:start dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
731
|
+
else
|
|
732
|
+
print_item "$p2" "Stop (service) | bash=$PNPM_BIN param1=service:stop dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
733
|
+
fi
|
|
734
|
+
print_item "$p2" "Restart (service) | bash=$PNPM_BIN param1=service:restart dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
735
|
+
else
|
|
736
|
+
if [[ "${MAIN_LEVEL:-}" == "red" ]]; then
|
|
737
|
+
print_item "$p2" "Start (foreground) | bash=$PNPM_TERM param1=start dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
738
|
+
else
|
|
739
|
+
print_item "$p2" "Stop (kill port listeners) | bash=$PNPM_TERM param1=stack:fix dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
740
|
+
fi
|
|
741
|
+
fi
|
|
742
|
+
print_item "$p2" "Dev mode | bash=$PNPM_TERM param1=dev dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
743
|
+
print_item "$p2" "Build UI | bash=$PNPM_TERM param1=build dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
744
|
+
print_item "$p2" "Doctor | bash=$PNPM_TERM param1=stack:doctor dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
745
|
+
return
|
|
746
|
+
fi
|
|
747
|
+
|
|
748
|
+
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
|
|
749
|
+
if [[ "$svc_installed" == "1" ]]; then
|
|
750
|
+
# Status-aware: only show start/stop based on whether the stack is running.
|
|
751
|
+
if [[ "$STACK_LEVEL" == "red" ]]; then
|
|
752
|
+
print_item "$p2" "Start (service) | bash=$PNPM_BIN param1=stack param2=service:start param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
753
|
+
else
|
|
754
|
+
print_item "$p2" "Stop (service) | bash=$PNPM_BIN param1=stack param2=service:stop param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
755
|
+
fi
|
|
756
|
+
print_item "$p2" "Restart (service) | bash=$PNPM_BIN param1=stack param2=service:restart param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
757
|
+
else
|
|
758
|
+
if [[ "$STACK_LEVEL" == "red" ]]; then
|
|
759
|
+
print_item "$p2" "Start (foreground) | bash=$PNPM_TERM param1=stack param2=start param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
760
|
+
else
|
|
761
|
+
print_item "$p2" "Stop (kill port listeners) | bash=$PNPM_TERM param1=stack param2=fix param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
762
|
+
fi
|
|
763
|
+
fi
|
|
764
|
+
print_item "$p2" "Dev mode | bash=$PNPM_TERM param1=stack param2=dev param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
765
|
+
print_item "$p2" "Build UI | bash=$PNPM_TERM param1=stack param2=build param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
766
|
+
print_item "$p2" "Doctor | bash=$PNPM_TERM param1=stack param2=doctor param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
767
|
+
print_item "$p2" "Edit stack (interactive) | bash=$PNPM_TERM param1=stack param2=edit param3=$stack_name param4=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
768
|
+
print_item "$p2" "Select worktrees (interactive) | bash=$PNPM_TERM param1=stack param2=wt param3=$stack_name param4=-- param5=use param6=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
769
|
+
|
|
770
|
+
local pr_helper="$HAPPY_LOCAL_DIR/extras/swiftbar/wt-pr.sh"
|
|
771
|
+
if [[ -x "$pr_helper" ]]; then
|
|
772
|
+
print_item "$p2" "PR worktree into this stack (prompt) | bash=$pr_helper param1=_prompt_ param2=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
773
|
+
fi
|
|
774
|
+
}
|