happy-stacks 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +130 -74
  2. package/bin/happys.mjs +140 -9
  3. package/docs/edison.md +381 -0
  4. package/docs/happy-development.md +733 -0
  5. package/docs/menubar.md +54 -0
  6. package/docs/paths-and-env.md +141 -0
  7. package/docs/server-flavors.md +61 -2
  8. package/docs/stacks.md +55 -4
  9. package/extras/swiftbar/auth-login.sh +10 -7
  10. package/extras/swiftbar/git-cache-refresh.sh +130 -0
  11. package/extras/swiftbar/happy-stacks.5s.sh +175 -83
  12. package/extras/swiftbar/happys-term.sh +128 -0
  13. package/extras/swiftbar/happys.sh +35 -0
  14. package/extras/swiftbar/install.sh +99 -13
  15. package/extras/swiftbar/lib/git.sh +309 -1
  16. package/extras/swiftbar/lib/icons.sh +2 -2
  17. package/extras/swiftbar/lib/render.sh +279 -132
  18. package/extras/swiftbar/lib/system.sh +64 -10
  19. package/extras/swiftbar/lib/utils.sh +469 -10
  20. package/extras/swiftbar/pnpm-term.sh +2 -122
  21. package/extras/swiftbar/pnpm.sh +4 -14
  22. package/extras/swiftbar/set-interval.sh +10 -5
  23. package/extras/swiftbar/set-server-flavor.sh +19 -10
  24. package/extras/swiftbar/wt-pr.sh +10 -3
  25. package/package.json +2 -1
  26. package/scripts/auth.mjs +833 -14
  27. package/scripts/build.mjs +24 -4
  28. package/scripts/cli-link.mjs +3 -3
  29. package/scripts/completion.mjs +15 -8
  30. package/scripts/daemon.mjs +200 -23
  31. package/scripts/dev.mjs +230 -57
  32. package/scripts/doctor.mjs +26 -21
  33. package/scripts/edison.mjs +1828 -0
  34. package/scripts/happy.mjs +3 -7
  35. package/scripts/init.mjs +275 -46
  36. package/scripts/install.mjs +14 -8
  37. package/scripts/lint.mjs +145 -0
  38. package/scripts/menubar.mjs +81 -8
  39. package/scripts/migrate.mjs +302 -0
  40. package/scripts/mobile.mjs +59 -21
  41. package/scripts/run.mjs +222 -43
  42. package/scripts/self.mjs +3 -7
  43. package/scripts/server_flavor.mjs +3 -3
  44. package/scripts/service.mjs +190 -38
  45. package/scripts/setup.mjs +790 -0
  46. package/scripts/setup_pr.mjs +182 -0
  47. package/scripts/stack.mjs +2273 -92
  48. package/scripts/stop.mjs +160 -0
  49. package/scripts/tailscale.mjs +164 -23
  50. package/scripts/test.mjs +144 -0
  51. package/scripts/tui.mjs +556 -0
  52. package/scripts/typecheck.mjs +145 -0
  53. package/scripts/ui_gateway.mjs +248 -0
  54. package/scripts/uninstall.mjs +21 -13
  55. package/scripts/utils/auth_files.mjs +58 -0
  56. package/scripts/utils/auth_login_ux.mjs +76 -0
  57. package/scripts/utils/auth_sources.mjs +12 -0
  58. package/scripts/utils/browser.mjs +22 -0
  59. package/scripts/utils/canonical_home.mjs +20 -0
  60. package/scripts/utils/{cli_registry.mjs → cli/cli_registry.mjs} +71 -0
  61. package/scripts/utils/{wizard.mjs → cli/wizard.mjs} +1 -1
  62. package/scripts/utils/config.mjs +13 -1
  63. package/scripts/utils/dev_auth_key.mjs +169 -0
  64. package/scripts/utils/dev_daemon.mjs +104 -0
  65. package/scripts/utils/dev_expo_web.mjs +112 -0
  66. package/scripts/utils/dev_server.mjs +183 -0
  67. package/scripts/utils/env.mjs +94 -23
  68. package/scripts/utils/env_file.mjs +36 -0
  69. package/scripts/utils/expo.mjs +96 -0
  70. package/scripts/utils/handy_master_secret.mjs +94 -0
  71. package/scripts/utils/happy_server_infra.mjs +484 -0
  72. package/scripts/utils/localhost_host.mjs +17 -0
  73. package/scripts/utils/ownership.mjs +135 -0
  74. package/scripts/utils/paths.mjs +5 -2
  75. package/scripts/utils/pm.mjs +132 -22
  76. package/scripts/utils/ports.mjs +51 -13
  77. package/scripts/utils/proc.mjs +75 -7
  78. package/scripts/utils/runtime.mjs +1 -3
  79. package/scripts/utils/sandbox.mjs +14 -0
  80. package/scripts/utils/server.mjs +61 -0
  81. package/scripts/utils/server_port.mjs +9 -0
  82. package/scripts/utils/server_urls.mjs +54 -0
  83. package/scripts/utils/stack_context.mjs +23 -0
  84. package/scripts/utils/stack_runtime_state.mjs +104 -0
  85. package/scripts/utils/stack_startup.mjs +208 -0
  86. package/scripts/utils/stack_stop.mjs +255 -0
  87. package/scripts/utils/stacks.mjs +38 -0
  88. package/scripts/utils/validate.mjs +42 -1
  89. package/scripts/utils/watch.mjs +63 -0
  90. package/scripts/utils/worktrees.mjs +57 -1
  91. package/scripts/where.mjs +14 -7
  92. package/scripts/worktrees.mjs +135 -15
  93. /package/scripts/utils/{args.mjs → cli/args.mjs} +0 -0
  94. /package/scripts/utils/{cli.mjs → cli/cli.mjs} +0 -0
  95. /package/scripts/utils/{smoke_help.mjs → cli/smoke_help.mjs} +0 -0
@@ -14,9 +14,7 @@ get_process_metrics() {
14
14
  return
15
15
  fi
16
16
  local cpu rss etime
17
- cpu="$(echo "$line" | awk '{print $1}')"
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
- if echo "$LAUNCHCTL_LIST_CACHE" | grep -q "$label"; then
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.
@@ -112,7 +121,7 @@ check_daemon_status() {
112
121
  local latest_log
113
122
  latest_log="$(ls -1t "$cli_home_dir"/logs/*-daemon.log 2>/dev/null | head -1 || true)"
114
123
  if [[ -n "$latest_log" ]]; then
115
- if tail -n 120 "$latest_log" 2>/dev/null | rg -q "No credentials found|starting authentication flow|Waiting for credentials"; then
124
+ if tail -n 120 "$latest_log" 2>/dev/null | grep -Eq "No credentials found|starting authentication flow|Waiting for credentials"; then
116
125
  echo "auth_required:$lock_pid"
117
126
  return
118
127
  fi
@@ -129,9 +138,16 @@ check_daemon_status() {
129
138
  return
130
139
  fi
131
140
 
141
+ local node_bin
142
+ node_bin="$(resolve_node_bin)"
143
+ if [[ -z "$node_bin" ]] || [[ ! -x "$node_bin" ]]; then
144
+ echo "unknown"
145
+ return
146
+ fi
147
+
132
148
  local pid httpPort
133
- pid="$(node -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.stdout.write(String(s.pid ?? ""));' "$state_file" 2>/dev/null || true)"
134
- httpPort="$(node -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.stdout.write(String(s.httpPort ?? ""));' "$state_file" 2>/dev/null || true)"
149
+ pid="$("$node_bin" -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.stdout.write(String(s.pid ?? ""));' "$state_file" 2>/dev/null || true)"
150
+ httpPort="$("$node_bin" -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); process.stdout.write(String(s.httpPort ?? ""));' "$state_file" 2>/dev/null || true)"
135
151
 
136
152
  if [[ -z "$pid" ]] || ! [[ "$pid" =~ ^[0-9]+$ ]]; then
137
153
  echo "unknown"
@@ -146,13 +162,19 @@ check_daemon_status() {
146
162
  # Best-effort: confirm the control server is responding (tight timeouts).
147
163
  if [[ -n "$httpPort" ]] && [[ "$httpPort" =~ ^[0-9]+$ ]]; then
148
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"
149
167
  echo "running:$pid"
150
168
  return
151
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"
152
172
  echo "running-no-http:$pid"
153
173
  return
154
174
  fi
155
175
 
176
+ t1="$(swiftbar_now_ms 2>/dev/null || echo 0)"
177
+ swiftbar_profile_log "time" "label=daemon_status" "ms=$((t1 - t0))" "httpProbe=skip"
156
178
  echo "running:$pid"
157
179
  }
158
180
 
@@ -162,7 +184,12 @@ get_daemon_uptime() {
162
184
  if [[ -z "$cli_home_dir" ]] || [[ ! -f "$state_file" ]]; then
163
185
  return
164
186
  fi
165
- node -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if (s.startTime) process.stdout.write(String(s.startTime));' "$state_file" 2>/dev/null || true
187
+ local node_bin
188
+ node_bin="$(resolve_node_bin)"
189
+ if [[ -z "$node_bin" ]] || [[ ! -x "$node_bin" ]]; then
190
+ return
191
+ fi
192
+ "$node_bin" -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if (s.startTime) process.stdout.write(String(s.startTime));' "$state_file" 2>/dev/null || true
166
193
  }
167
194
 
168
195
  get_last_heartbeat() {
@@ -171,15 +198,43 @@ get_last_heartbeat() {
171
198
  if [[ -z "$cli_home_dir" ]] || [[ ! -f "$state_file" ]]; then
172
199
  return
173
200
  fi
174
- node -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if (s.lastHeartbeat) process.stdout.write(String(s.lastHeartbeat));' "$state_file" 2>/dev/null || true
201
+ local node_bin
202
+ node_bin="$(resolve_node_bin)"
203
+ if [[ -z "$node_bin" ]] || [[ ! -x "$node_bin" ]]; then
204
+ return
205
+ fi
206
+ "$node_bin" -e 'const fs=require("fs"); const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); if (s.lastHeartbeat) process.stdout.write(String(s.lastHeartbeat));' "$state_file" 2>/dev/null || true
175
207
  }
176
208
 
177
209
  get_tailscale_url() {
178
210
  # Try multiple methods to get the Tailscale URL (best-effort).
179
211
  local url=""
180
212
 
213
+ # Preferred: use happys (respects our own timeouts/env handling).
214
+ local happys_sh="$HAPPY_LOCAL_DIR/extras/swiftbar/happys.sh"
215
+ if [[ -x "$happys_sh" ]]; then
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)"
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)"
222
+ if [[ "$url" == https://* ]]; then
223
+ echo "$url"
224
+ return
225
+ fi
226
+ url=""
227
+ fi
228
+
181
229
  if command -v tailscale &>/dev/null; then
230
+ local t0 t1
231
+ t0="$(swiftbar_now_ms 2>/dev/null || echo 0)"
182
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)"
235
+ fi
236
+ if [[ -z "$url" ]] && [[ -x "/Applications/Tailscale.app/Contents/MacOS/tailscale" ]]; then
237
+ url="$(/Applications/Tailscale.app/Contents/MacOS/tailscale serve status 2>/dev/null | grep -oE 'https://[^ ]+' | head -1 || true)"
183
238
  fi
184
239
  if [[ -z "$url" ]] && [[ -x "/Applications/Tailscale.app/Contents/MacOS/Tailscale" ]]; then
185
240
  url="$(/Applications/Tailscale.app/Contents/MacOS/Tailscale serve status 2>/dev/null | grep -oE 'https://[^ ]+' | head -1 || true)"
@@ -187,4 +242,3 @@ get_tailscale_url() {
187
242
 
188
243
  echo "$url"
189
244
  }
190
-