happy-stacks 0.1.2 → 0.3.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 +164 -89
- package/bin/happys.mjs +70 -10
- package/docs/edison.md +381 -0
- package/docs/happy-development.md +733 -0
- package/docs/menubar.md +54 -0
- package/docs/paths-and-env.md +141 -0
- package/docs/stacks.md +39 -0
- package/extras/swiftbar/auth-login.sh +5 -2
- package/extras/swiftbar/git-cache-refresh.sh +130 -0
- package/extras/swiftbar/happy-stacks.5s.sh +131 -81
- package/extras/swiftbar/happys-term.sh +15 -38
- package/extras/swiftbar/happys.sh +15 -32
- package/extras/swiftbar/install.sh +99 -13
- package/extras/swiftbar/lib/git.sh +309 -1
- package/extras/swiftbar/lib/icons.sh +2 -2
- package/extras/swiftbar/lib/render.sh +209 -80
- package/extras/swiftbar/lib/system.sh +27 -4
- package/extras/swiftbar/lib/utils.sh +311 -28
- package/extras/swiftbar/pnpm.sh +2 -1
- package/extras/swiftbar/set-interval.sh +10 -5
- package/extras/swiftbar/set-server-flavor.sh +11 -2
- package/extras/swiftbar/wt-pr.sh +9 -2
- package/package.json +2 -1
- package/scripts/auth.mjs +521 -226
- package/scripts/build.mjs +29 -10
- package/scripts/cli-link.mjs +6 -6
- package/scripts/completion.mjs +18 -11
- package/scripts/daemon.mjs +133 -31
- package/scripts/dev.mjs +196 -137
- package/scripts/doctor.mjs +44 -55
- package/scripts/edison.mjs +1853 -0
- package/scripts/happy.mjs +10 -25
- package/scripts/init.mjs +46 -31
- package/scripts/install.mjs +21 -15
- package/scripts/lint.mjs +124 -0
- package/scripts/menubar.mjs +76 -10
- package/scripts/migrate.mjs +35 -35
- package/scripts/mobile.mjs +24 -17
- package/scripts/run.mjs +122 -35
- package/scripts/self.mjs +13 -35
- package/scripts/server_flavor.mjs +7 -7
- package/scripts/service.mjs +31 -28
- package/scripts/setup.mjs +694 -0
- package/scripts/setup_pr.mjs +165 -0
- package/scripts/stack.mjs +1851 -363
- package/scripts/stop.mjs +9 -6
- package/scripts/tailscale.mjs +23 -11
- package/scripts/test.mjs +123 -0
- package/scripts/tui.mjs +526 -0
- package/scripts/typecheck.mjs +10 -31
- package/scripts/ui_gateway.mjs +3 -3
- package/scripts/uninstall.mjs +21 -13
- package/scripts/utils/auth/dev_key.mjs +163 -0
- package/scripts/utils/auth/files.mjs +56 -0
- package/scripts/utils/auth/handy_master_secret.mjs +68 -0
- package/scripts/utils/auth/login_ux.mjs +76 -0
- package/scripts/utils/auth/sources.mjs +12 -0
- package/scripts/utils/{cli_registry.mjs → cli/cli_registry.mjs} +48 -0
- package/scripts/utils/cli/flags.mjs +17 -0
- package/scripts/utils/cli/normalize.mjs +16 -0
- package/scripts/utils/{smoke_help.mjs → cli/smoke_help.mjs} +2 -2
- package/scripts/utils/{wizard.mjs → cli/wizard.mjs} +1 -1
- package/scripts/utils/crypto/tokens.mjs +14 -0
- package/scripts/utils/dev/daemon.mjs +104 -0
- package/scripts/utils/dev/expo_web.mjs +112 -0
- package/scripts/utils/dev/server.mjs +183 -0
- package/scripts/utils/{config.mjs → env/config.mjs} +8 -3
- package/scripts/utils/{dotenv.mjs → env/dotenv.mjs} +3 -0
- package/scripts/utils/{env.mjs → env/env.mjs} +64 -13
- package/scripts/utils/{env_file.mjs → env/env_file.mjs} +38 -1
- package/scripts/utils/{env_local.mjs → env/env_local.mjs} +1 -0
- package/scripts/utils/env/read.mjs +30 -0
- package/scripts/utils/env/sandbox.mjs +14 -0
- package/scripts/utils/env/values.mjs +13 -0
- package/scripts/utils/{expo.mjs → expo/expo.mjs} +7 -11
- package/scripts/utils/fs/json.mjs +25 -0
- package/scripts/utils/fs/ops.mjs +29 -0
- package/scripts/utils/fs/package_json.mjs +8 -0
- package/scripts/utils/fs/tail.mjs +12 -0
- package/scripts/utils/git/refs.mjs +26 -0
- package/scripts/utils/{worktrees.mjs → git/worktrees.mjs} +60 -4
- package/scripts/utils/net/dns.mjs +10 -0
- package/scripts/utils/{ports.mjs → net/ports.mjs} +3 -5
- package/scripts/utils/paths/canonical_home.mjs +20 -0
- package/scripts/utils/paths/localhost_host.mjs +9 -0
- package/scripts/utils/{paths.mjs → paths/paths.mjs} +14 -8
- package/scripts/utils/{runtime.mjs → paths/runtime.mjs} +4 -4
- package/scripts/utils/proc/commands.mjs +34 -0
- package/scripts/utils/proc/ownership.mjs +135 -0
- package/scripts/utils/proc/package_scripts.mjs +31 -0
- package/scripts/utils/proc/pids.mjs +11 -0
- package/scripts/utils/proc/pm.mjs +317 -0
- package/scripts/utils/{proc.mjs → proc/proc.mjs} +30 -2
- package/scripts/utils/proc/watch.mjs +63 -0
- package/scripts/utils/{happy_server_infra.mjs → server/infra/happy_server_infra.mjs} +109 -94
- package/scripts/utils/server/port.mjs +68 -0
- package/scripts/utils/{server.mjs → server/server.mjs} +36 -0
- package/scripts/utils/server/urls.mjs +91 -0
- package/scripts/utils/{validate.mjs → server/validate.mjs} +1 -1
- package/scripts/utils/service/autostart_darwin.mjs +142 -0
- package/scripts/utils/stack/context.mjs +23 -0
- package/scripts/utils/stack/dirs.mjs +27 -0
- package/scripts/utils/stack/editor_workspace.mjs +152 -0
- package/scripts/utils/stack/names.mjs +12 -0
- package/scripts/utils/stack/runtime_state.mjs +87 -0
- package/scripts/utils/stack/stacks.mjs +45 -0
- package/scripts/utils/stack/startup.mjs +208 -0
- package/scripts/utils/{stack_stop.mjs → stack/stop.mjs} +85 -42
- package/scripts/utils/ui/browser.mjs +22 -0
- package/scripts/utils/ui/text.mjs +16 -0
- package/scripts/where.mjs +17 -10
- package/scripts/worktrees.mjs +110 -64
- package/scripts/utils/pm.mjs +0 -303
- /package/scripts/utils/{args.mjs → cli/args.mjs} +0 -0
- /package/scripts/utils/{cli.mjs → cli/cli.mjs} +0 -0
- /package/scripts/utils/{fs.mjs → fs/fs.mjs} +0 -0
|
@@ -79,7 +79,11 @@ render_component_server() {
|
|
|
79
79
|
|
|
80
80
|
local p2="${prefix}--"
|
|
81
81
|
print_item "$p2" "Status: $server_status"
|
|
82
|
-
|
|
82
|
+
if [[ -n "$port" ]]; then
|
|
83
|
+
print_item "$p2" "Internal: http://127.0.0.1:${port}"
|
|
84
|
+
else
|
|
85
|
+
print_item "$p2" "Port: ephemeral (allocated at start time)"
|
|
86
|
+
fi
|
|
83
87
|
if [[ -n "$server_pid" ]]; then
|
|
84
88
|
if [[ -n "$server_metrics" ]]; then
|
|
85
89
|
local cpu mem etime
|
|
@@ -91,8 +95,10 @@ render_component_server() {
|
|
|
91
95
|
print_item "$p2" "PID: ${server_pid}"
|
|
92
96
|
fi
|
|
93
97
|
fi
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
if [[ -n "$port" ]]; then
|
|
99
|
+
print_item "$p2" "Open UI (local) | href=http://localhost:${port}/"
|
|
100
|
+
print_item "$p2" "Open Health | href=http://127.0.0.1:${port}/health"
|
|
101
|
+
fi
|
|
96
102
|
if [[ -n "$tailscale_url" ]]; then
|
|
97
103
|
print_item "$p2" "Open UI (Tailscale) | href=$tailscale_url"
|
|
98
104
|
fi
|
|
@@ -102,9 +108,11 @@ render_component_server() {
|
|
|
102
108
|
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/happys-term.sh"
|
|
103
109
|
local plist=""
|
|
104
110
|
local svc_installed="0"
|
|
105
|
-
if
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
if ! swiftbar_is_sandboxed; then
|
|
112
|
+
if [[ -n "$launch_label" ]]; then
|
|
113
|
+
plist="$HOME/Library/LaunchAgents/${launch_label}.plist"
|
|
114
|
+
[[ -f "$plist" ]] && svc_installed="1"
|
|
115
|
+
fi
|
|
108
116
|
fi
|
|
109
117
|
|
|
110
118
|
print_sep "$p2"
|
|
@@ -118,7 +126,7 @@ render_component_server() {
|
|
|
118
126
|
print_item "$p2" "Restart stack (service) | bash=$PNPM_BIN param1=service:restart dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
119
127
|
else
|
|
120
128
|
if [[ "$server_status" == "running" ]]; then
|
|
121
|
-
print_item "$p2" "Stop
|
|
129
|
+
print_item "$p2" "Stop stack | bash=$PNPM_BIN param1=stack param2=stop param3=main dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
122
130
|
else
|
|
123
131
|
print_item "$p2" "Start stack (foreground) | bash=$PNPM_TERM param1=start dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
124
132
|
fi
|
|
@@ -133,7 +141,7 @@ render_component_server() {
|
|
|
133
141
|
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"
|
|
134
142
|
else
|
|
135
143
|
if [[ "$server_status" == "running" ]]; then
|
|
136
|
-
print_item "$p2" "Stop
|
|
144
|
+
print_item "$p2" "Stop stack | bash=$PNPM_BIN param1=stack param2=stop param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
137
145
|
else
|
|
138
146
|
print_item "$p2" "Start stack (foreground) | bash=$PNPM_TERM param1=stack param2=start param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
139
147
|
fi
|
|
@@ -202,11 +210,9 @@ render_component_daemon() {
|
|
|
202
210
|
# Provide a direct "fix" action for the common first-run problem under launchd.
|
|
203
211
|
local auth_helper="$HAPPY_LOCAL_DIR/extras/swiftbar/auth-login.sh"
|
|
204
212
|
local server_url="http://127.0.0.1:$(resolve_main_port)"
|
|
205
|
-
local webapp_url
|
|
206
|
-
webapp_url="$(get_tailscale_url)"
|
|
207
|
-
[[ -z "$webapp_url" ]] && webapp_url="http://localhost:$(resolve_main_port)"
|
|
213
|
+
local webapp_url="http://localhost:$(resolve_main_port)"
|
|
208
214
|
if [[ "$stack_name" == "main" ]]; then
|
|
209
|
-
print_item "$p2" "Auth login (opens browser) | bash=$auth_helper param1=main
|
|
215
|
+
print_item "$p2" "Auth login (opens browser) | bash=$auth_helper param1=main dir=$HAPPY_LOCAL_DIR terminal=false refresh=false"
|
|
210
216
|
else
|
|
211
217
|
# For stacks, best-effort use the stack's configured port if available (fallback to main port).
|
|
212
218
|
local env_file
|
|
@@ -216,16 +222,17 @@ render_component_daemon() {
|
|
|
216
222
|
[[ -z "$port" ]] && port="$(dotenv_get "$env_file" "HAPPY_LOCAL_SERVER_PORT")"
|
|
217
223
|
[[ -z "$port" ]] && port="$(resolve_main_port)"
|
|
218
224
|
server_url="http://127.0.0.1:${port}"
|
|
219
|
-
webapp_url="
|
|
220
|
-
|
|
221
|
-
print_item "$p2" "Auth login (opens browser) | bash=$auth_helper param1=$stack_name param2=$server_url param3=$webapp_url dir=$HAPPY_LOCAL_DIR terminal=false refresh=false"
|
|
225
|
+
webapp_url="http://localhost:${port}"
|
|
226
|
+
print_item "$p2" "Auth login (opens browser) | bash=$auth_helper param1=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=false"
|
|
222
227
|
fi
|
|
223
228
|
print_sep "$p2"
|
|
224
229
|
fi
|
|
225
|
-
if
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
230
|
+
if ! swiftbar_is_sandboxed; then
|
|
231
|
+
if [[ "$stack_name" == "main" ]]; then
|
|
232
|
+
print_item "$p2" "Restart stack (service) | bash=$PNPM_BIN param1=service:restart dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
233
|
+
else
|
|
234
|
+
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"
|
|
235
|
+
fi
|
|
229
236
|
fi
|
|
230
237
|
fi
|
|
231
238
|
}
|
|
@@ -239,6 +246,13 @@ render_component_autostart() {
|
|
|
239
246
|
local autostart_metrics="$6"
|
|
240
247
|
local logs_dir="$7"
|
|
241
248
|
|
|
249
|
+
if swiftbar_is_sandboxed; then
|
|
250
|
+
print_item "$prefix" "Autostart | sfimage=exclamationmark.triangle sfconfig=light"
|
|
251
|
+
local p2="${prefix}--"
|
|
252
|
+
print_item "$p2" "Status: disabled in sandbox"
|
|
253
|
+
return
|
|
254
|
+
fi
|
|
255
|
+
|
|
242
256
|
local level="red"
|
|
243
257
|
if [[ "$launchagent_status" == "loaded" ]]; then level="green"; fi
|
|
244
258
|
if [[ "$launchagent_status" == "unloaded" ]]; then level="orange"; fi
|
|
@@ -323,6 +337,11 @@ render_component_tailscale() {
|
|
|
323
337
|
print_item "$p2" "Status: not configured / unknown"
|
|
324
338
|
fi
|
|
325
339
|
|
|
340
|
+
# Tailscale Serve is global machine state; never offer enable/disable actions in sandbox mode.
|
|
341
|
+
if swiftbar_is_sandboxed; then
|
|
342
|
+
return
|
|
343
|
+
fi
|
|
344
|
+
|
|
326
345
|
if [[ -z "$PNPM_BIN" ]]; then
|
|
327
346
|
return
|
|
328
347
|
fi
|
|
@@ -361,6 +380,9 @@ render_component_repo() {
|
|
|
361
380
|
local stack_name="$4"
|
|
362
381
|
local env_file="$5"
|
|
363
382
|
|
|
383
|
+
local t0 t1
|
|
384
|
+
t0="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
385
|
+
|
|
364
386
|
local active_dir=""
|
|
365
387
|
# If we have an env file for the current context, prefer it (stack env is authoritative).
|
|
366
388
|
if [[ -n "$env_file" && -f "$env_file" ]]; then
|
|
@@ -371,25 +393,51 @@ render_component_repo() {
|
|
|
371
393
|
|
|
372
394
|
local level="red"
|
|
373
395
|
local detail="missing"
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
396
|
+
|
|
397
|
+
local git_mode
|
|
398
|
+
git_mode="$(git_cache_mode)"
|
|
399
|
+
|
|
400
|
+
local stale="0"
|
|
401
|
+
local meta="" info="" wts=""
|
|
402
|
+
if [[ "$git_mode" == "cached" ]]; then
|
|
403
|
+
# Never refresh synchronously during menu render.
|
|
404
|
+
IFS=$'\t' read -r meta info wts stale <<<"$(git_cache_load_or_refresh "$context" "$stack_name" "$component" "$active_dir" "0")"
|
|
405
|
+
fi
|
|
406
|
+
|
|
407
|
+
local status="missing"
|
|
408
|
+
local dirty="" ahead="" behind="" wt_count=""
|
|
409
|
+
local branch="" head="" upstream=""
|
|
410
|
+
local main_branch="" main_upstream="" main_ahead="" main_behind=""
|
|
411
|
+
local oref="" o_ahead="" o_behind="" uref="" u_ahead="" u_behind=""
|
|
412
|
+
|
|
413
|
+
if [[ "$git_mode" == "cached" && -f "$info" ]]; then
|
|
414
|
+
IFS=$'\t' read -r status _ad branch head upstream dirty ahead behind main_branch main_upstream main_ahead main_behind oref o_ahead o_behind uref u_ahead u_behind wt_count <"$info" || true
|
|
415
|
+
elif [[ "$git_mode" == "live" ]]; then
|
|
416
|
+
# live mode only: do git work on every refresh
|
|
417
|
+
if is_git_repo "$active_dir"; then
|
|
418
|
+
status="ok"
|
|
419
|
+
dirty="$(git_dirty_flag "$active_dir")"
|
|
420
|
+
local ab
|
|
421
|
+
ab="$(git_ahead_behind "$active_dir")"
|
|
422
|
+
if [[ -n "$ab" ]]; then
|
|
423
|
+
ahead="$(echo "$ab" | cut -d'|' -f1)"
|
|
424
|
+
behind="$(echo "$ab" | cut -d'|' -f2)"
|
|
425
|
+
fi
|
|
383
426
|
fi
|
|
427
|
+
fi
|
|
384
428
|
|
|
429
|
+
if [[ "$status" == "ok" ]]; then
|
|
430
|
+
detail="ok"
|
|
385
431
|
if [[ "$dirty" == "dirty" ]] || [[ -n "$behind" && "$behind" != "0" ]]; then
|
|
386
432
|
level="orange"
|
|
387
433
|
else
|
|
388
434
|
level="green"
|
|
389
435
|
fi
|
|
390
|
-
detail="ok"
|
|
391
436
|
fi
|
|
392
437
|
|
|
438
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
439
|
+
swiftbar_profile_log "time" "label=render_component_repo" "component=${component}" "context=${context}" "ms=$((t1 - t0))" "detail=${detail}"
|
|
440
|
+
|
|
393
441
|
local sf color
|
|
394
442
|
sf="$(sf_for_level "$level")"
|
|
395
443
|
color="$(color_for_level "$level")"
|
|
@@ -398,7 +446,20 @@ render_component_repo() {
|
|
|
398
446
|
local p2="${prefix}--"
|
|
399
447
|
print_item "$p2" "Dir: $(shorten_path "$active_dir" 52)"
|
|
400
448
|
if [[ "$detail" != "ok" ]]; then
|
|
401
|
-
|
|
449
|
+
if [[ "$git_mode" == "cached" ]]; then
|
|
450
|
+
print_item "$p2" "Status: git cache missing (or not a git repo)"
|
|
451
|
+
local refresh="$HAPPY_LOCAL_DIR/extras/swiftbar/git-cache-refresh.sh"
|
|
452
|
+
if [[ -x "$refresh" ]]; then
|
|
453
|
+
print_sep "$p2"
|
|
454
|
+
if [[ "$context" == "stack" && -n "$stack_name" ]]; then
|
|
455
|
+
print_item "$p2" "Refresh Git cache (this stack) | bash=$refresh param1=stack param2=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
456
|
+
else
|
|
457
|
+
print_item "$p2" "Refresh Git cache (main) | bash=$refresh param1=main dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
458
|
+
fi
|
|
459
|
+
fi
|
|
460
|
+
else
|
|
461
|
+
print_item "$p2" "Status: not a git repo / missing"
|
|
462
|
+
fi
|
|
402
463
|
if [[ -n "$PNPM_BIN" ]]; then
|
|
403
464
|
print_sep "$p2"
|
|
404
465
|
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/happys-term.sh"
|
|
@@ -407,22 +468,28 @@ render_component_repo() {
|
|
|
407
468
|
return
|
|
408
469
|
fi
|
|
409
470
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
471
|
+
# Cache status + refresh actions
|
|
472
|
+
if [[ "$git_mode" == "cached" ]]; then
|
|
473
|
+
local age=""
|
|
474
|
+
age="$(git_cache_age_sec "$meta")"
|
|
475
|
+
if [[ -n "$age" ]]; then
|
|
476
|
+
if [[ "$stale" == "1" ]]; then
|
|
477
|
+
print_item "$p2" "Git cache: stale (${age}s old) | color=$YELLOW"
|
|
478
|
+
else
|
|
479
|
+
print_item "$p2" "Git cache: fresh (${age}s old) | color=$GRAY"
|
|
480
|
+
fi
|
|
481
|
+
fi
|
|
482
|
+
local refresh="$HAPPY_LOCAL_DIR/extras/swiftbar/git-cache-refresh.sh"
|
|
483
|
+
if [[ -x "$refresh" ]]; then
|
|
484
|
+
print_sep "$p2"
|
|
485
|
+
print_item "$p2" "Refresh Git cache (this component) | bash=$refresh param1=component param2=$context param3=$stack_name param4=$component dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
486
|
+
if [[ "$context" == "stack" && -n "$stack_name" ]]; then
|
|
487
|
+
print_item "$p2" "Refresh Git cache (this stack) | bash=$refresh param1=stack param2=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
488
|
+
fi
|
|
489
|
+
fi
|
|
424
490
|
fi
|
|
425
491
|
|
|
492
|
+
print_sep "$p2"
|
|
426
493
|
print_item "$p2" "HEAD: ${branch:-"(unknown)"} ${head:+($head)}"
|
|
427
494
|
print_item "$p2" "Upstream: ${upstream:-"(none)"}"
|
|
428
495
|
if [[ -n "$ahead" && -n "$behind" ]]; then
|
|
@@ -430,31 +497,22 @@ render_component_repo() {
|
|
|
430
497
|
fi
|
|
431
498
|
print_item "$p2" "Working tree: ${dirty}"
|
|
432
499
|
|
|
433
|
-
local main_branch main_upstream main_ab
|
|
434
|
-
main_branch="$(git_main_branch_name "$active_dir")"
|
|
435
500
|
if [[ -n "$main_branch" ]]; then
|
|
436
|
-
main_upstream="$(git_branch_upstream_short "$active_dir" "$main_branch")"
|
|
437
|
-
main_ab="$(git_branch_ahead_behind "$active_dir" "$main_branch")"
|
|
438
501
|
if [[ -n "$main_upstream" ]]; then
|
|
439
502
|
print_item "$p2" "Main: ${main_branch} → ${main_upstream}"
|
|
440
503
|
else
|
|
441
504
|
print_item "$p2" "Main: ${main_branch} → (no upstream)"
|
|
442
505
|
fi
|
|
443
|
-
if [[ -n "$
|
|
444
|
-
print_item "$p2" "Main ahead/behind: $
|
|
506
|
+
if [[ -n "$main_ahead" && -n "$main_behind" ]]; then
|
|
507
|
+
print_item "$p2" "Main ahead/behind: ${main_ahead}/${main_behind}"
|
|
445
508
|
fi
|
|
446
509
|
|
|
447
510
|
# Always show comparisons against origin/* and upstream/* when those remote refs exist.
|
|
448
511
|
# (These reflect your last fetch; we do not auto-fetch in the menu.)
|
|
449
|
-
local oref uref
|
|
450
|
-
oref="$(git_remote_main_ref "$active_dir" "origin")"
|
|
451
|
-
uref="$(git_remote_main_ref "$active_dir" "upstream")"
|
|
452
512
|
if [[ -n "$oref" ]]; then
|
|
453
513
|
local oref_short="${oref#refs/remotes/}"
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if [[ -n "$oab" ]]; then
|
|
457
|
-
print_item "$p2" "Origin: ${oref_short} ahead/behind: $(echo "$oab" | cut -d'|' -f1)/$(echo "$oab" | cut -d'|' -f2)"
|
|
514
|
+
if [[ -n "$o_ahead" && -n "$o_behind" ]]; then
|
|
515
|
+
print_item "$p2" "Origin: ${oref_short} ahead/behind: ${o_ahead}/${o_behind}"
|
|
458
516
|
else
|
|
459
517
|
print_item "$p2" "Origin: ${oref_short}"
|
|
460
518
|
fi
|
|
@@ -463,10 +521,8 @@ render_component_repo() {
|
|
|
463
521
|
fi
|
|
464
522
|
if [[ -n "$uref" ]]; then
|
|
465
523
|
local uref_short="${uref#refs/remotes/}"
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if [[ -n "$uab" ]]; then
|
|
469
|
-
print_item "$p2" "Upstream: ${uref_short} ahead/behind: $(echo "$uab" | cut -d'|' -f1)/$(echo "$uab" | cut -d'|' -f2)"
|
|
524
|
+
if [[ -n "$u_ahead" && -n "$u_behind" ]]; then
|
|
525
|
+
print_item "$p2" "Upstream: ${uref_short} ahead/behind: ${u_ahead}/${u_behind}"
|
|
470
526
|
else
|
|
471
527
|
print_item "$p2" "Upstream: ${uref_short}"
|
|
472
528
|
fi
|
|
@@ -476,7 +532,8 @@ render_component_repo() {
|
|
|
476
532
|
fi
|
|
477
533
|
|
|
478
534
|
local wt_count
|
|
479
|
-
wt_count
|
|
535
|
+
# If cache didn't populate wt_count, fall back to empty string.
|
|
536
|
+
wt_count="${wt_count:-}"
|
|
480
537
|
|
|
481
538
|
# Quick actions
|
|
482
539
|
print_sep "$p2"
|
|
@@ -536,7 +593,11 @@ render_component_repo() {
|
|
|
536
593
|
print_item "$p2" "$wt_label"
|
|
537
594
|
local p3="${p2}--"
|
|
538
595
|
local tsv
|
|
539
|
-
|
|
596
|
+
if [[ "$git_mode" == "cached" && -f "$wts" ]]; then
|
|
597
|
+
tsv="$(cat "$wts" 2>/dev/null || true)"
|
|
598
|
+
else
|
|
599
|
+
tsv="$(git_worktrees_tsv "$active_dir" 2>/dev/null || true)"
|
|
600
|
+
fi
|
|
540
601
|
if [[ -z "$tsv" ]]; then
|
|
541
602
|
print_item "$p3" "No worktrees found | color=$GRAY"
|
|
542
603
|
else
|
|
@@ -604,14 +665,55 @@ render_components_menu() {
|
|
|
604
665
|
local stack_name="$3"
|
|
605
666
|
local env_file="$4"
|
|
606
667
|
|
|
668
|
+
local t0 t1
|
|
669
|
+
t0="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
670
|
+
|
|
607
671
|
print_item "$prefix" "Components | sfimage=cube"
|
|
608
672
|
local p2="${prefix}--"
|
|
673
|
+
|
|
674
|
+
# Background auto-refresh: keep menu refresh snappy but update git cache when TTL expires.
|
|
675
|
+
if [[ "$(git_cache_mode)" == "cached" ]]; then
|
|
676
|
+
local scope
|
|
677
|
+
scope="$(git_cache_auto_refresh_scope)"
|
|
678
|
+
local refresh="$HAPPY_LOCAL_DIR/extras/swiftbar/git-cache-refresh.sh"
|
|
679
|
+
if [[ -x "$refresh" ]]; then
|
|
680
|
+
if [[ "$scope" == "all" ]]; then
|
|
681
|
+
git_cache_maybe_refresh_async "all" "$refresh" all
|
|
682
|
+
elif [[ "$scope" == "main" && "$context" == "main" ]]; then
|
|
683
|
+
git_cache_maybe_refresh_async "main" "$refresh" main
|
|
684
|
+
fi
|
|
685
|
+
fi
|
|
686
|
+
fi
|
|
687
|
+
|
|
688
|
+
# Git cache controls (to keep the menu refresh fast while retaining rich inline worktrees UI).
|
|
689
|
+
local refresh="$HAPPY_LOCAL_DIR/extras/swiftbar/git-cache-refresh.sh"
|
|
690
|
+
if [[ -f "$refresh" ]]; then
|
|
691
|
+
local mode ttl
|
|
692
|
+
mode="$(git_cache_mode)"
|
|
693
|
+
ttl="$(git_cache_ttl_sec)"
|
|
694
|
+
print_item "$p2" "Git cache | sfimage=arrow.triangle.2.circlepath"
|
|
695
|
+
local p3="${p2}--"
|
|
696
|
+
print_item "$p3" "Mode: ${mode} (default: cached)"
|
|
697
|
+
print_item "$p3" "TTL: ${ttl}s (set HAPPY_STACKS_SWIFTBAR_GIT_TTL_SEC)"
|
|
698
|
+
print_sep "$p3"
|
|
699
|
+
if [[ "$context" == "main" ]]; then
|
|
700
|
+
print_item "$p3" "Refresh now (main components) | bash=$refresh param1=main dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
701
|
+
print_item "$p3" "Refresh now (all stacks/components) | bash=$refresh param1=all dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
702
|
+
else
|
|
703
|
+
print_item "$p3" "Refresh now (this stack) | bash=$refresh param1=stack param2=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
704
|
+
fi
|
|
705
|
+
print_sep "$p2"
|
|
706
|
+
fi
|
|
707
|
+
|
|
609
708
|
# Always render the known components using the resolved component dirs (env file → env.local/.env → fallback),
|
|
610
709
|
# instead of assuming they live under `~/.happy-stacks/workspace/components`.
|
|
611
710
|
for c in happy happy-cli happy-server-light happy-server; do
|
|
612
711
|
render_component_repo "$p2" "$c" "$context" "$stack_name" "$env_file"
|
|
613
712
|
print_sep "$p2"
|
|
614
713
|
done
|
|
714
|
+
|
|
715
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
716
|
+
swiftbar_profile_log "time" "label=render_components_menu" "context=${context}" "stack=${stack_name}" "ms=$((t1 - t0))"
|
|
615
717
|
}
|
|
616
718
|
|
|
617
719
|
render_stack_overview_item() {
|
|
@@ -662,14 +764,20 @@ collect_stack_status() {
|
|
|
662
764
|
daemon_uptime="$(get_daemon_uptime "$cli_home_dir")"
|
|
663
765
|
last_heartbeat="$(get_last_heartbeat "$cli_home_dir")"
|
|
664
766
|
|
|
665
|
-
local plist_path="$HOME/Library/LaunchAgents/${label}.plist"
|
|
666
767
|
local launchagent_status autostart_pid autostart_metrics
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
768
|
+
if swiftbar_is_sandboxed; then
|
|
769
|
+
launchagent_status="sandbox_disabled"
|
|
770
|
+
autostart_pid=""
|
|
771
|
+
autostart_metrics=""
|
|
772
|
+
else
|
|
773
|
+
local plist_path="$HOME/Library/LaunchAgents/${label}.plist"
|
|
774
|
+
launchagent_status="$(check_launchagent_status "$label" "$plist_path")"
|
|
775
|
+
autostart_pid=""
|
|
776
|
+
autostart_metrics=""
|
|
777
|
+
if [[ "$launchagent_status" != "not_installed" ]]; then
|
|
778
|
+
autostart_pid="$(launchagent_pid_for_label "$label")"
|
|
779
|
+
autostart_metrics="$(get_process_metrics "$autostart_pid")"
|
|
780
|
+
fi
|
|
673
781
|
fi
|
|
674
782
|
|
|
675
783
|
local level
|
|
@@ -709,7 +817,18 @@ render_stack_info() {
|
|
|
709
817
|
print_item "$prefix" "Stack details | sfimage=server.rack"
|
|
710
818
|
local p2="${prefix}--"
|
|
711
819
|
print_item "$p2" "Server component: ${server_component}"
|
|
712
|
-
|
|
820
|
+
local pinned_port=""
|
|
821
|
+
if [[ -n "$env_file" && -f "$env_file" ]]; then
|
|
822
|
+
pinned_port="$(dotenv_get "$env_file" "HAPPY_STACKS_SERVER_PORT")"
|
|
823
|
+
[[ -z "$pinned_port" ]] && pinned_port="$(dotenv_get "$env_file" "HAPPY_LOCAL_SERVER_PORT")"
|
|
824
|
+
fi
|
|
825
|
+
local port_display="$port"
|
|
826
|
+
if [[ -z "$port_display" ]]; then
|
|
827
|
+
port_display="ephemeral (not running)"
|
|
828
|
+
elif [[ -z "$pinned_port" ]]; then
|
|
829
|
+
port_display="${port_display} (ephemeral)"
|
|
830
|
+
fi
|
|
831
|
+
print_item "$p2" "Port: ${port_display}"
|
|
713
832
|
print_item "$p2" "Label: ${label}"
|
|
714
833
|
[[ -n "$env_file" ]] && print_item "$p2" "Env: $(shorten_path "$env_file" 52)"
|
|
715
834
|
[[ -n "$tailscale_url" ]] && print_item "$p2" "Tailscale: $(shorten_text "$tailscale_url" 52)"
|
|
@@ -736,9 +855,13 @@ render_stack_info() {
|
|
|
736
855
|
fi
|
|
737
856
|
print_sep "$p2"
|
|
738
857
|
|
|
739
|
-
local plist="$HOME/Library/LaunchAgents/${label}.plist"
|
|
740
858
|
local svc_installed="0"
|
|
741
|
-
|
|
859
|
+
if ! swiftbar_is_sandboxed; then
|
|
860
|
+
local plist="$HOME/Library/LaunchAgents/${label}.plist"
|
|
861
|
+
[[ -f "$plist" ]] && svc_installed="1"
|
|
862
|
+
fi
|
|
863
|
+
local menu_mode
|
|
864
|
+
menu_mode="$(resolve_menubar_mode)"
|
|
742
865
|
|
|
743
866
|
if [[ "$stack_name" == "main" ]]; then
|
|
744
867
|
local PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/happys-term.sh"
|
|
@@ -754,10 +877,12 @@ render_stack_info() {
|
|
|
754
877
|
if [[ "${MAIN_LEVEL:-}" == "red" ]]; then
|
|
755
878
|
print_item "$p2" "Start (foreground) | bash=$PNPM_TERM param1=start dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
756
879
|
else
|
|
757
|
-
print_item "$p2" "Stop
|
|
880
|
+
print_item "$p2" "Stop stack | bash=$PNPM_BIN param1=stack param2=stop param3=main dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
758
881
|
fi
|
|
759
882
|
fi
|
|
760
|
-
|
|
883
|
+
if [[ "$menu_mode" != "selfhost" ]]; then
|
|
884
|
+
print_item "$p2" "Dev mode | bash=$PNPM_TERM param1=dev dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
885
|
+
fi
|
|
761
886
|
print_item "$p2" "Build UI | bash=$PNPM_TERM param1=build dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
762
887
|
print_item "$p2" "Doctor | bash=$PNPM_TERM param1=stack:doctor dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
763
888
|
return
|
|
@@ -776,17 +901,21 @@ render_stack_info() {
|
|
|
776
901
|
if [[ "$STACK_LEVEL" == "red" ]]; then
|
|
777
902
|
print_item "$p2" "Start (foreground) | bash=$PNPM_TERM param1=stack param2=start param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
778
903
|
else
|
|
779
|
-
print_item "$p2" "Stop
|
|
904
|
+
print_item "$p2" "Stop stack | bash=$PNPM_BIN param1=stack param2=stop param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
780
905
|
fi
|
|
781
906
|
fi
|
|
782
|
-
|
|
907
|
+
if [[ "$menu_mode" != "selfhost" ]]; then
|
|
908
|
+
print_item "$p2" "Dev mode | bash=$PNPM_TERM param1=stack param2=dev param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
909
|
+
fi
|
|
783
910
|
print_item "$p2" "Build UI | bash=$PNPM_TERM param1=stack param2=build param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
784
911
|
print_item "$p2" "Doctor | bash=$PNPM_TERM param1=stack param2=doctor param3=$stack_name dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
785
|
-
|
|
786
|
-
|
|
912
|
+
if [[ "$menu_mode" != "selfhost" ]]; then
|
|
913
|
+
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"
|
|
914
|
+
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"
|
|
915
|
+
fi
|
|
787
916
|
|
|
788
917
|
local pr_helper="$HAPPY_LOCAL_DIR/extras/swiftbar/wt-pr.sh"
|
|
789
|
-
if [[ -x "$pr_helper" ]]; then
|
|
918
|
+
if [[ "$menu_mode" != "selfhost" && -x "$pr_helper" ]]; then
|
|
790
919
|
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"
|
|
791
920
|
fi
|
|
792
921
|
}
|
|
@@ -14,9 +14,7 @@ get_process_metrics() {
|
|
|
14
14
|
return
|
|
15
15
|
fi
|
|
16
16
|
local cpu rss etime
|
|
17
|
-
|
|
18
|
-
rss="$(echo "$line" | awk '{print $2}')" # KB
|
|
19
|
-
etime="$(echo "$line" | awk '{print $3}')"
|
|
17
|
+
IFS=' ' read -r cpu rss etime <<<"$line"
|
|
20
18
|
local mem_mb
|
|
21
19
|
mem_mb="$(awk -v rss="$rss" 'BEGIN { printf "%.0f", (rss/1024.0) }')"
|
|
22
20
|
echo "$cpu|$mem_mb|$etime"
|
|
@@ -43,7 +41,11 @@ ensure_launchctl_cache() {
|
|
|
43
41
|
return
|
|
44
42
|
fi
|
|
45
43
|
if command -v launchctl >/dev/null 2>&1; then
|
|
44
|
+
local t0 t1
|
|
45
|
+
t0="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
46
46
|
LAUNCHCTL_LIST_CACHE="$(launchctl list 2>/dev/null || true)"
|
|
47
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
48
|
+
swiftbar_profile_log "time" "label=launchctl_list" "ms=$((t1 - t0))"
|
|
47
49
|
fi
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -56,7 +58,8 @@ check_launchagent_status() {
|
|
|
56
58
|
fi
|
|
57
59
|
|
|
58
60
|
ensure_launchctl_cache
|
|
59
|
-
|
|
61
|
+
# Match the label column exactly (avoid substring false positives).
|
|
62
|
+
if echo "$LAUNCHCTL_LIST_CACHE" | awk -v lbl="$label" '$3==lbl{found=1} END{exit found?0:1}'; then
|
|
60
63
|
echo "loaded"
|
|
61
64
|
return
|
|
62
65
|
fi
|
|
@@ -88,7 +91,11 @@ check_server_health() {
|
|
|
88
91
|
fi
|
|
89
92
|
local response
|
|
90
93
|
# Tight timeouts to keep menus snappy even with many stacks.
|
|
94
|
+
local t0 t1
|
|
95
|
+
t0="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
91
96
|
response="$(curl -s --connect-timeout 0.2 --max-time 0.6 "http://127.0.0.1:${port}/health" 2>/dev/null || true)"
|
|
97
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
98
|
+
swiftbar_profile_log "time" "label=curl_health" "port=${port}" "ms=$((t1 - t0))" "bytes=${#response}"
|
|
92
99
|
if [[ "$response" == *"ok"* ]] || [[ "$response" == *"Welcome"* ]]; then
|
|
93
100
|
echo "running"
|
|
94
101
|
return
|
|
@@ -99,6 +106,8 @@ check_server_health() {
|
|
|
99
106
|
check_daemon_status() {
|
|
100
107
|
local cli_home_dir="$1"
|
|
101
108
|
local state_file="$cli_home_dir/daemon.state.json"
|
|
109
|
+
local t0 t1
|
|
110
|
+
t0="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
102
111
|
if [[ -z "$cli_home_dir" ]] || [[ ! -f "$state_file" ]]; then
|
|
103
112
|
# If the daemon is starting but hasn't written daemon.state.json yet, we can still detect it
|
|
104
113
|
# via the lock file PID.
|
|
@@ -153,13 +162,19 @@ check_daemon_status() {
|
|
|
153
162
|
# Best-effort: confirm the control server is responding (tight timeouts).
|
|
154
163
|
if [[ -n "$httpPort" ]] && [[ "$httpPort" =~ ^[0-9]+$ ]]; then
|
|
155
164
|
if curl -s --connect-timeout 0.2 --max-time 0.5 -X POST -H 'content-type: application/json' -d '{}' "http://127.0.0.1:${httpPort}/list" >/dev/null 2>&1; then
|
|
165
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
166
|
+
swiftbar_profile_log "time" "label=daemon_status" "ms=$((t1 - t0))" "httpProbe=ok"
|
|
156
167
|
echo "running:$pid"
|
|
157
168
|
return
|
|
158
169
|
fi
|
|
170
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
171
|
+
swiftbar_profile_log "time" "label=daemon_status" "ms=$((t1 - t0))" "httpProbe=fail"
|
|
159
172
|
echo "running-no-http:$pid"
|
|
160
173
|
return
|
|
161
174
|
fi
|
|
162
175
|
|
|
176
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
177
|
+
swiftbar_profile_log "time" "label=daemon_status" "ms=$((t1 - t0))" "httpProbe=skip"
|
|
163
178
|
echo "running:$pid"
|
|
164
179
|
}
|
|
165
180
|
|
|
@@ -199,7 +214,11 @@ get_tailscale_url() {
|
|
|
199
214
|
local happys_sh="$HAPPY_LOCAL_DIR/extras/swiftbar/happys.sh"
|
|
200
215
|
if [[ -x "$happys_sh" ]]; then
|
|
201
216
|
# Keep SwiftBar responsive: use a tight timeout for this periodic probe.
|
|
217
|
+
local t0 t1
|
|
218
|
+
t0="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
202
219
|
url="$("$happys_sh" tailscale:url --timeout-ms=2500 2>/dev/null | head -1 | tr -d '[:space:]' || true)"
|
|
220
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
221
|
+
swiftbar_profile_log "time" "label=tailscale_url_happys" "ms=$((t1 - t0))" "ok=$([[ "$url" == https://* ]] && echo 1 || echo 0)"
|
|
203
222
|
if [[ "$url" == https://* ]]; then
|
|
204
223
|
echo "$url"
|
|
205
224
|
return
|
|
@@ -208,7 +227,11 @@ get_tailscale_url() {
|
|
|
208
227
|
fi
|
|
209
228
|
|
|
210
229
|
if command -v tailscale &>/dev/null; then
|
|
230
|
+
local t0 t1
|
|
231
|
+
t0="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
211
232
|
url="$(tailscale serve status 2>/dev/null | grep -oE 'https://[^ ]+' | head -1 || true)"
|
|
233
|
+
t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
|
|
234
|
+
swiftbar_profile_log "time" "label=tailscale_url_cli" "ms=$((t1 - t0))" "ok=$([[ -n "$url" ]] && echo 1 || echo 0)"
|
|
212
235
|
fi
|
|
213
236
|
if [[ -z "$url" ]] && [[ -x "/Applications/Tailscale.app/Contents/MacOS/tailscale" ]]; then
|
|
214
237
|
url="$(/Applications/Tailscale.app/Contents/MacOS/tailscale serve status 2>/dev/null | grep -oE 'https://[^ ]+' | head -1 || true)"
|