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.
- package/README.md +130 -74
- package/bin/happys.mjs +140 -9
- 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/server-flavors.md +61 -2
- package/docs/stacks.md +55 -4
- package/extras/swiftbar/auth-login.sh +10 -7
- package/extras/swiftbar/git-cache-refresh.sh +130 -0
- package/extras/swiftbar/happy-stacks.5s.sh +175 -83
- package/extras/swiftbar/happys-term.sh +128 -0
- package/extras/swiftbar/happys.sh +35 -0
- 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 +279 -132
- package/extras/swiftbar/lib/system.sh +64 -10
- package/extras/swiftbar/lib/utils.sh +469 -10
- package/extras/swiftbar/pnpm-term.sh +2 -122
- package/extras/swiftbar/pnpm.sh +4 -14
- package/extras/swiftbar/set-interval.sh +10 -5
- package/extras/swiftbar/set-server-flavor.sh +19 -10
- package/extras/swiftbar/wt-pr.sh +10 -3
- package/package.json +2 -1
- package/scripts/auth.mjs +833 -14
- package/scripts/build.mjs +24 -4
- package/scripts/cli-link.mjs +3 -3
- package/scripts/completion.mjs +15 -8
- package/scripts/daemon.mjs +200 -23
- package/scripts/dev.mjs +230 -57
- package/scripts/doctor.mjs +26 -21
- package/scripts/edison.mjs +1828 -0
- package/scripts/happy.mjs +3 -7
- package/scripts/init.mjs +275 -46
- package/scripts/install.mjs +14 -8
- package/scripts/lint.mjs +145 -0
- package/scripts/menubar.mjs +81 -8
- package/scripts/migrate.mjs +302 -0
- package/scripts/mobile.mjs +59 -21
- package/scripts/run.mjs +222 -43
- package/scripts/self.mjs +3 -7
- package/scripts/server_flavor.mjs +3 -3
- package/scripts/service.mjs +190 -38
- package/scripts/setup.mjs +790 -0
- package/scripts/setup_pr.mjs +182 -0
- package/scripts/stack.mjs +2273 -92
- package/scripts/stop.mjs +160 -0
- package/scripts/tailscale.mjs +164 -23
- package/scripts/test.mjs +144 -0
- package/scripts/tui.mjs +556 -0
- package/scripts/typecheck.mjs +145 -0
- package/scripts/ui_gateway.mjs +248 -0
- package/scripts/uninstall.mjs +21 -13
- package/scripts/utils/auth_files.mjs +58 -0
- package/scripts/utils/auth_login_ux.mjs +76 -0
- package/scripts/utils/auth_sources.mjs +12 -0
- package/scripts/utils/browser.mjs +22 -0
- package/scripts/utils/canonical_home.mjs +20 -0
- package/scripts/utils/{cli_registry.mjs → cli/cli_registry.mjs} +71 -0
- package/scripts/utils/{wizard.mjs → cli/wizard.mjs} +1 -1
- package/scripts/utils/config.mjs +13 -1
- package/scripts/utils/dev_auth_key.mjs +169 -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/env.mjs +94 -23
- package/scripts/utils/env_file.mjs +36 -0
- package/scripts/utils/expo.mjs +96 -0
- package/scripts/utils/handy_master_secret.mjs +94 -0
- package/scripts/utils/happy_server_infra.mjs +484 -0
- package/scripts/utils/localhost_host.mjs +17 -0
- package/scripts/utils/ownership.mjs +135 -0
- package/scripts/utils/paths.mjs +5 -2
- package/scripts/utils/pm.mjs +132 -22
- package/scripts/utils/ports.mjs +51 -13
- package/scripts/utils/proc.mjs +75 -7
- package/scripts/utils/runtime.mjs +1 -3
- package/scripts/utils/sandbox.mjs +14 -0
- package/scripts/utils/server.mjs +61 -0
- package/scripts/utils/server_port.mjs +9 -0
- package/scripts/utils/server_urls.mjs +54 -0
- package/scripts/utils/stack_context.mjs +23 -0
- package/scripts/utils/stack_runtime_state.mjs +104 -0
- package/scripts/utils/stack_startup.mjs +208 -0
- package/scripts/utils/stack_stop.mjs +255 -0
- package/scripts/utils/stacks.mjs +38 -0
- package/scripts/utils/validate.mjs +42 -1
- package/scripts/utils/watch.mjs +63 -0
- package/scripts/utils/worktrees.mjs +57 -1
- package/scripts/where.mjs +14 -7
- package/scripts/worktrees.mjs +135 -15
- /package/scripts/utils/{args.mjs → cli/args.mjs} +0 -0
- /package/scripts/utils/{cli.mjs → cli/cli.mjs} +0 -0
- /package/scripts/utils/{smoke_help.mjs → cli/smoke_help.mjs} +0 -0
|
@@ -17,7 +17,46 @@
|
|
|
17
17
|
# Configuration
|
|
18
18
|
# ============================================================================
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# SwiftBar runs with a minimal environment, so users often won't have
|
|
21
|
+
# HAPPY_STACKS_HOME_DIR / HAPPY_STACKS_WORKSPACE_DIR exported.
|
|
22
|
+
# Treat <canonicalHomeDir>/.env as the canonical pointer file (written by `happys init`).
|
|
23
|
+
# Default: ~/.happy-stacks/.env
|
|
24
|
+
CANONICAL_HOME_DIR="${HAPPY_STACKS_CANONICAL_HOME_DIR:-${HAPPY_LOCAL_CANONICAL_HOME_DIR:-$HOME/.happy-stacks}}"
|
|
25
|
+
CANONICAL_ENV_FILE="$CANONICAL_HOME_DIR/.env"
|
|
26
|
+
|
|
27
|
+
_dotenv_get_quick() {
|
|
28
|
+
# Usage: _dotenv_get_quick /path/to/env KEY
|
|
29
|
+
local file="$1"
|
|
30
|
+
local key="$2"
|
|
31
|
+
[[ -n "$file" && -n "$key" && -f "$file" ]] || return 0
|
|
32
|
+
local line
|
|
33
|
+
line="$(grep -E "^${key}=" "$file" 2>/dev/null | head -n 1 || true)"
|
|
34
|
+
[[ -n "$line" ]] || return 0
|
|
35
|
+
local v="${line#*=}"
|
|
36
|
+
v="${v%$'\r'}"
|
|
37
|
+
# Strip simple surrounding quotes.
|
|
38
|
+
if [[ "$v" == \"*\" && "$v" == *\" ]]; then v="${v#\"}"; v="${v%\"}"; fi
|
|
39
|
+
if [[ "$v" == \'*\' && "$v" == *\' ]]; then v="${v#\'}"; v="${v%\'}"; fi
|
|
40
|
+
echo "$v"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_expand_home_quick() {
|
|
44
|
+
local p="$1"
|
|
45
|
+
if [[ "$p" == "~/"* ]]; then
|
|
46
|
+
echo "$HOME/${p#~/}"
|
|
47
|
+
else
|
|
48
|
+
echo "$p"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_home_from_canonical=""
|
|
53
|
+
if [[ -f "$CANONICAL_ENV_FILE" ]]; then
|
|
54
|
+
_home_from_canonical="$(_dotenv_get_quick "$CANONICAL_ENV_FILE" "HAPPY_STACKS_HOME_DIR")"
|
|
55
|
+
[[ -z "$_home_from_canonical" ]] && _home_from_canonical="$(_dotenv_get_quick "$CANONICAL_ENV_FILE" "HAPPY_LOCAL_HOME_DIR")"
|
|
56
|
+
fi
|
|
57
|
+
_home_from_canonical="$(_expand_home_quick "${_home_from_canonical:-}")"
|
|
58
|
+
|
|
59
|
+
HAPPY_STACKS_HOME_DIR="${HAPPY_STACKS_HOME_DIR:-${_home_from_canonical:-$CANONICAL_HOME_DIR}}"
|
|
21
60
|
HAPPY_LOCAL_DIR="${HAPPY_LOCAL_DIR:-$HAPPY_STACKS_HOME_DIR}"
|
|
22
61
|
HAPPY_LOCAL_PORT="${HAPPY_LOCAL_PORT:-3005}"
|
|
23
62
|
|
|
@@ -26,17 +65,6 @@ if [[ -n "${HAPPY_STACKS_WT_TERMINAL:-}" && -z "${HAPPY_LOCAL_WT_TERMINAL:-}" ]]
|
|
|
26
65
|
if [[ -n "${HAPPY_STACKS_WT_SHELL:-}" && -z "${HAPPY_LOCAL_WT_SHELL:-}" ]]; then HAPPY_LOCAL_WT_SHELL="$HAPPY_STACKS_WT_SHELL"; fi
|
|
27
66
|
if [[ -n "${HAPPY_STACKS_SWIFTBAR_ICON_PATH:-}" && -z "${HAPPY_LOCAL_SWIFTBAR_ICON_PATH:-}" ]]; then HAPPY_LOCAL_SWIFTBAR_ICON_PATH="$HAPPY_STACKS_SWIFTBAR_ICON_PATH"; fi
|
|
28
67
|
|
|
29
|
-
# Storage root migrated from ~/.happy/local -> ~/.happy/stacks/main.
|
|
30
|
-
if [[ -z "${HAPPY_HOME_DIR:-}" ]]; then
|
|
31
|
-
if [[ -d "$HOME/.happy/stacks/main" ]] || [[ ! -d "$HOME/.happy/local" ]]; then
|
|
32
|
-
HAPPY_HOME_DIR="$HOME/.happy/stacks/main"
|
|
33
|
-
else
|
|
34
|
-
HAPPY_HOME_DIR="$HOME/.happy/local"
|
|
35
|
-
fi
|
|
36
|
-
fi
|
|
37
|
-
CLI_HOME_DIR="$HAPPY_HOME_DIR/cli"
|
|
38
|
-
LOGS_DIR="$HAPPY_HOME_DIR/logs"
|
|
39
|
-
|
|
40
68
|
# Colors
|
|
41
69
|
GREEN="#34C759"
|
|
42
70
|
RED="#FF3B30"
|
|
@@ -78,11 +106,24 @@ PNPM_BIN="$(resolve_pnpm_bin)"
|
|
|
78
106
|
MAIN_PORT="$(resolve_main_port)"
|
|
79
107
|
MAIN_SERVER_COMPONENT="$(resolve_main_server_component)"
|
|
80
108
|
TAILSCALE_URL="$(get_tailscale_url)"
|
|
109
|
+
if swiftbar_is_sandboxed; then
|
|
110
|
+
# Never probe Tailscale (global machine state) when sandboxing.
|
|
111
|
+
TAILSCALE_URL=""
|
|
112
|
+
fi
|
|
81
113
|
MAIN_ENV_FILE="$(resolve_main_env_file)"
|
|
114
|
+
MENUBAR_MODE="$(resolve_menubar_mode)"
|
|
82
115
|
|
|
83
116
|
ensure_launchctl_cache
|
|
84
117
|
|
|
85
|
-
|
|
118
|
+
if [[ -z "$MAIN_ENV_FILE" ]]; then
|
|
119
|
+
MAIN_ENV_FILE="$(resolve_stack_env_file main)"
|
|
120
|
+
fi
|
|
121
|
+
HAPPY_HOME_DIR="$(resolve_stack_base_dir main "$MAIN_ENV_FILE")"
|
|
122
|
+
CLI_HOME_DIR="$(resolve_stack_cli_home_dir main "$MAIN_ENV_FILE")"
|
|
123
|
+
LOGS_DIR="$HAPPY_HOME_DIR/logs"
|
|
124
|
+
MAIN_LABEL="$(resolve_stack_label main)"
|
|
125
|
+
|
|
126
|
+
MAIN_COLLECT="$(collect_stack_status "$MAIN_PORT" "$CLI_HOME_DIR" "$MAIN_LABEL" "$HAPPY_HOME_DIR")"
|
|
86
127
|
IFS=$'\t' read -r MAIN_LEVEL MAIN_SERVER_STATUS MAIN_SERVER_PID MAIN_SERVER_METRICS MAIN_DAEMON_STATUS MAIN_DAEMON_PID MAIN_DAEMON_METRICS MAIN_DAEMON_UPTIME MAIN_LAST_HEARTBEAT MAIN_LAUNCHAGENT_STATUS MAIN_AUTOSTART_PID MAIN_AUTOSTART_METRICS <<<"$MAIN_COLLECT"
|
|
87
128
|
for v in MAIN_SERVER_PID MAIN_SERVER_METRICS MAIN_DAEMON_PID MAIN_DAEMON_METRICS MAIN_DAEMON_UPTIME MAIN_LAST_HEARTBEAT MAIN_AUTOSTART_PID MAIN_AUTOSTART_METRICS; do
|
|
88
129
|
if [[ "${!v}" == "-" ]]; then
|
|
@@ -108,95 +149,144 @@ echo "---"
|
|
|
108
149
|
echo "Happy Stacks | size=14 font=SF Pro Display"
|
|
109
150
|
echo "---"
|
|
110
151
|
|
|
152
|
+
# Mode (selfhost vs dev)
|
|
153
|
+
if [[ "$MENUBAR_MODE" == "selfhost" ]]; then
|
|
154
|
+
echo "Mode: Selfhost | sfimage=house"
|
|
155
|
+
else
|
|
156
|
+
echo "Mode: Dev | sfimage=hammer"
|
|
157
|
+
fi
|
|
158
|
+
if [[ -n "$PNPM_BIN" ]]; then
|
|
159
|
+
if [[ "$MENUBAR_MODE" == "selfhost" ]]; then
|
|
160
|
+
echo "--Switch to Dev mode | bash=$PNPM_BIN param1=menubar param2=mode param3=dev dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
161
|
+
else
|
|
162
|
+
echo "--Switch to Selfhost mode | bash=$PNPM_BIN param1=menubar param2=mode param3=selfhost dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
echo "---"
|
|
166
|
+
|
|
111
167
|
# Main stack (inline)
|
|
112
168
|
echo "Main stack"
|
|
113
169
|
echo "---"
|
|
114
170
|
export MAIN_LEVEL="$MAIN_LEVEL"
|
|
115
|
-
render_stack_info "" "main" "$MAIN_PORT" "$MAIN_SERVER_COMPONENT" "$HAPPY_HOME_DIR" "$CLI_HOME_DIR" "
|
|
116
|
-
render_component_server "" "main" "$MAIN_PORT" "$MAIN_SERVER_COMPONENT" "$MAIN_SERVER_STATUS" "$MAIN_SERVER_PID" "$MAIN_SERVER_METRICS" "$TAILSCALE_URL" "
|
|
171
|
+
render_stack_info "" "main" "$MAIN_PORT" "$MAIN_SERVER_COMPONENT" "$HAPPY_HOME_DIR" "$CLI_HOME_DIR" "$MAIN_LABEL" "$MAIN_ENV_FILE" "$TAILSCALE_URL"
|
|
172
|
+
render_component_server "" "main" "$MAIN_PORT" "$MAIN_SERVER_COMPONENT" "$MAIN_SERVER_STATUS" "$MAIN_SERVER_PID" "$MAIN_SERVER_METRICS" "$TAILSCALE_URL" "$MAIN_LABEL"
|
|
117
173
|
render_component_daemon "" "$MAIN_DAEMON_STATUS" "$MAIN_DAEMON_PID" "$MAIN_DAEMON_METRICS" "$MAIN_DAEMON_UPTIME" "$MAIN_LAST_HEARTBEAT" "$CLI_HOME_DIR/daemon.state.json" "main"
|
|
118
|
-
render_component_autostart "" "main" "
|
|
174
|
+
render_component_autostart "" "main" "$MAIN_LABEL" "$MAIN_LAUNCHAGENT_STATUS" "$MAIN_AUTOSTART_PID" "$MAIN_AUTOSTART_METRICS" "$LOGS_DIR"
|
|
119
175
|
render_component_tailscale "" "main" "$TAILSCALE_URL"
|
|
120
176
|
|
|
121
177
|
echo "---"
|
|
122
|
-
|
|
123
|
-
echo "
|
|
178
|
+
if [[ "$MENUBAR_MODE" == "selfhost" ]]; then
|
|
179
|
+
echo "Maintenance | sfimage=wrench.and.screwdriver"
|
|
180
|
+
if [[ -n "$PNPM_BIN" ]]; then
|
|
181
|
+
UPDATE_JSON="${HAPPY_LOCAL_DIR}/cache/update.json"
|
|
182
|
+
update_available=""
|
|
183
|
+
latest=""
|
|
184
|
+
current=""
|
|
185
|
+
if [[ -f "$UPDATE_JSON" ]]; then
|
|
186
|
+
update_available="$(grep -oE '\"updateAvailable\"[[:space:]]*:[[:space:]]*(true|false)' "$UPDATE_JSON" 2>/dev/null | head -1 | grep -oE '(true|false)' || true)"
|
|
187
|
+
latest="$(grep -oE '\"latest\"[[:space:]]*:[[:space:]]*\"[^\"]+\"' "$UPDATE_JSON" 2>/dev/null | head -1 | sed -E 's/.*\"latest\"[[:space:]]*:[[:space:]]*\"([^\"]+)\".*/\\1/' || true)"
|
|
188
|
+
current="$(grep -oE '\"current\"[[:space:]]*:[[:space:]]*\"[^\"]+\"' "$UPDATE_JSON" 2>/dev/null | head -1 | sed -E 's/.*\"current\"[[:space:]]*:[[:space:]]*\"([^\"]+)\".*/\\1/' || true)"
|
|
189
|
+
fi
|
|
190
|
+
if [[ "$update_available" == "true" && -n "$latest" ]]; then
|
|
191
|
+
echo "--Update available: ${current:-current} → ${latest} | color=$YELLOW"
|
|
192
|
+
else
|
|
193
|
+
echo "--Updates: up to date | color=$GRAY"
|
|
194
|
+
fi
|
|
195
|
+
echo "--Check for updates | bash=$PNPM_BIN param1=self param2=check dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
196
|
+
echo "--Update happy-stacks runtime | bash=$PNPM_BIN param1=self param2=update dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
197
|
+
echo "--Doctor | bash=$PNPM_BIN param1=doctor dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
198
|
+
else
|
|
199
|
+
echo "--⚠️ happys not found (run: npx happy-stacks setup)"
|
|
200
|
+
fi
|
|
201
|
+
else
|
|
202
|
+
echo "Stacks | sfimage=server.rack"
|
|
203
|
+
STACKS_PREFIX="--"
|
|
124
204
|
|
|
125
|
-
if [[ -n "$PNPM_BIN" ]]; then
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
fi
|
|
205
|
+
if [[ -n "$PNPM_BIN" ]]; then
|
|
206
|
+
HAPPYS_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/happys-term.sh"
|
|
207
|
+
echo "${STACKS_PREFIX}New stack (interactive) | bash=$HAPPYS_TERM param1=stack param2=new param3=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
208
|
+
echo "${STACKS_PREFIX}List stacks | bash=$HAPPYS_TERM param1=stack param2=list dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
209
|
+
print_sep "$STACKS_PREFIX"
|
|
210
|
+
fi
|
|
131
211
|
|
|
132
|
-
STACKS_DIR="$
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
echo "No stacks found | color=$GRAY"
|
|
212
|
+
STACKS_DIR="$(resolve_stacks_storage_root)"
|
|
213
|
+
LEGACY_STACKS_DIR="$HOME/.happy/local/stacks"
|
|
214
|
+
if swiftbar_is_sandboxed; then
|
|
215
|
+
LEGACY_STACKS_DIR=""
|
|
137
216
|
fi
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
217
|
+
if [[ -d "$STACKS_DIR" ]] || [[ -n "$LEGACY_STACKS_DIR" && -d "$LEGACY_STACKS_DIR" ]]; then
|
|
218
|
+
STACK_NAMES="$(
|
|
219
|
+
{
|
|
220
|
+
ls -1 "$STACKS_DIR" 2>/dev/null || true
|
|
221
|
+
[[ -n "$LEGACY_STACKS_DIR" ]] && ls -1 "$LEGACY_STACKS_DIR" 2>/dev/null || true
|
|
222
|
+
} | sort -u
|
|
223
|
+
)"
|
|
224
|
+
if [[ -z "$STACK_NAMES" ]]; then
|
|
225
|
+
echo "${STACKS_PREFIX}No stacks found | color=$GRAY"
|
|
226
|
+
fi
|
|
227
|
+
for s in $STACK_NAMES; do
|
|
228
|
+
env_file="$(resolve_stack_env_file "$s")"
|
|
229
|
+
[[ -f "$env_file" ]] || continue
|
|
141
230
|
|
|
142
|
-
|
|
143
|
-
|
|
231
|
+
# Ports may be ephemeral (runtime-only). Do not skip stacks if the env file does not pin a port.
|
|
232
|
+
port="$(resolve_stack_server_port "$s" "$env_file")"
|
|
144
233
|
|
|
145
|
-
|
|
146
|
-
|
|
234
|
+
server_component="$(dotenv_get "$env_file" "HAPPY_STACKS_SERVER_COMPONENT")"
|
|
235
|
+
[[ -z "$server_component" ]] && server_component="$(dotenv_get "$env_file" "HAPPY_LOCAL_SERVER_COMPONENT")"
|
|
236
|
+
[[ -n "$server_component" ]] || server_component="happy-server-light"
|
|
147
237
|
|
|
148
|
-
|
|
149
|
-
|
|
238
|
+
base_dir="$(resolve_stack_base_dir "$s" "$env_file")"
|
|
239
|
+
cli_home_dir="$(resolve_stack_cli_home_dir "$s" "$env_file")"
|
|
240
|
+
label="$(resolve_stack_label "$s")"
|
|
150
241
|
|
|
151
|
-
|
|
152
|
-
|
|
242
|
+
COLLECT="$(collect_stack_status "$port" "$cli_home_dir" "$label" "$base_dir")"
|
|
243
|
+
IFS=$'\t' read -r LEVEL SERVER_STATUS SERVER_PID SERVER_METRICS DAEMON_STATUS DAEMON_PID DAEMON_METRICS DAEMON_UPTIME LAST_HEARTBEAT LAUNCHAGENT_STATUS AUTOSTART_PID AUTOSTART_METRICS <<<"$COLLECT"
|
|
244
|
+
for v in SERVER_PID SERVER_METRICS DAEMON_PID DAEMON_METRICS DAEMON_UPTIME LAST_HEARTBEAT AUTOSTART_PID AUTOSTART_METRICS; do
|
|
245
|
+
if [[ "${!v}" == "-" ]]; then
|
|
246
|
+
printf -v "$v" '%s' ""
|
|
247
|
+
fi
|
|
248
|
+
done
|
|
153
249
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
250
|
+
render_stack_overview_item "Stack: $s" "$LEVEL" "$STACKS_PREFIX"
|
|
251
|
+
export STACK_LEVEL="$LEVEL"
|
|
252
|
+
render_stack_info "${STACKS_PREFIX}--" "$s" "$port" "$server_component" "$base_dir" "$cli_home_dir" "$label" "$env_file" ""
|
|
253
|
+
render_component_server "${STACKS_PREFIX}--" "$s" "$port" "$server_component" "$SERVER_STATUS" "$SERVER_PID" "$SERVER_METRICS" "" "$label"
|
|
254
|
+
render_component_daemon "${STACKS_PREFIX}--" "$DAEMON_STATUS" "$DAEMON_PID" "$DAEMON_METRICS" "$DAEMON_UPTIME" "$LAST_HEARTBEAT" "$cli_home_dir/daemon.state.json" "$s"
|
|
255
|
+
render_component_autostart "${STACKS_PREFIX}--" "$s" "$label" "$LAUNCHAGENT_STATUS" "$AUTOSTART_PID" "$AUTOSTART_METRICS" "$base_dir/logs"
|
|
256
|
+
render_component_tailscale "${STACKS_PREFIX}--" "$s" ""
|
|
257
|
+
render_components_menu "${STACKS_PREFIX}--" "stack" "$s" "$env_file"
|
|
160
258
|
done
|
|
259
|
+
else
|
|
260
|
+
echo "${STACKS_PREFIX}No stacks dir found at: $(shorten_path "$STACKS_DIR" 52) | color=$GRAY"
|
|
261
|
+
fi
|
|
161
262
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
render_stack_info "--" "$s" "$port" "$server_component" "$base_dir" "$cli_home_dir" "$label" "$env_file" ""
|
|
165
|
-
render_component_server "--" "$s" "$port" "$server_component" "$SERVER_STATUS" "$SERVER_PID" "$SERVER_METRICS" "" "$label"
|
|
166
|
-
render_component_daemon "--" "$DAEMON_STATUS" "$DAEMON_PID" "$DAEMON_METRICS" "$DAEMON_UPTIME" "$LAST_HEARTBEAT" "$cli_home_dir/daemon.state.json" "$s"
|
|
167
|
-
render_component_autostart "--" "$s" "$label" "$LAUNCHAGENT_STATUS" "$AUTOSTART_PID" "$AUTOSTART_METRICS" "$base_dir/logs"
|
|
168
|
-
render_component_tailscale "--" "$s" ""
|
|
169
|
-
render_components_menu "--" "stack" "$s" "$env_file"
|
|
170
|
-
done
|
|
171
|
-
else
|
|
172
|
-
echo "No stacks dir found at ~/.happy/stacks | color=$GRAY"
|
|
173
|
-
fi
|
|
174
|
-
|
|
175
|
-
echo "---"
|
|
176
|
-
render_components_menu "" "main" "main" ""
|
|
263
|
+
echo "---"
|
|
264
|
+
render_components_menu "" "main" "main" "$MAIN_ENV_FILE"
|
|
177
265
|
|
|
178
|
-
echo "Worktrees | sfimage=arrow.triangle.branch"
|
|
179
|
-
if [[ -z "$PNPM_BIN" ]]; then
|
|
180
|
-
|
|
181
|
-
else
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
fi
|
|
266
|
+
echo "Worktrees | sfimage=arrow.triangle.branch"
|
|
267
|
+
if [[ -z "$PNPM_BIN" ]]; then
|
|
268
|
+
echo "--⚠️ happys not found (run: npx happy-stacks setup)"
|
|
269
|
+
else
|
|
270
|
+
HAPPYS_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/happys-term.sh"
|
|
271
|
+
echo "--Use (interactive) | bash=$HAPPYS_TERM param1=wt param2=use param3=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
272
|
+
echo "--New (interactive) | bash=$HAPPYS_TERM param1=wt param2=new param3=--interactive dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
273
|
+
echo "--PR worktree (prompt) | bash=$HAPPY_LOCAL_DIR/extras/swiftbar/wt-pr.sh dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
274
|
+
echo "--Sync mirrors (all) | bash=$PNPM_BIN param1=wt param2=sync-all dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
275
|
+
echo "--Update all (dry-run) | bash=$HAPPYS_TERM param1=wt param2=update-all param3=--dry-run dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
276
|
+
echo "--Update all (apply) | bash=$PNPM_BIN param1=wt param2=update-all dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
277
|
+
fi
|
|
190
278
|
|
|
191
|
-
echo "---"
|
|
192
|
-
echo "Setup / Tools"
|
|
193
|
-
if [[ -z "$PNPM_BIN" ]]; then
|
|
194
|
-
|
|
195
|
-
else
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
279
|
+
echo "---"
|
|
280
|
+
echo "Setup / Tools"
|
|
281
|
+
if [[ -z "$PNPM_BIN" ]]; then
|
|
282
|
+
echo "--⚠️ happys not found (run: npx happy-stacks setup)"
|
|
283
|
+
else
|
|
284
|
+
HAPPYS_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/happys-term.sh"
|
|
285
|
+
echo "--Setup (guided) | bash=$HAPPYS_TERM param1=setup dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
286
|
+
echo "--Bootstrap (clone/install) | bash=$HAPPYS_TERM param1=bootstrap dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
287
|
+
echo "--CLI link (install happy wrapper) | bash=$HAPPYS_TERM param1=cli:link dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
288
|
+
echo "--Mobile dev helper | bash=$HAPPYS_TERM param1=mobile dir=$HAPPY_LOCAL_DIR terminal=false"
|
|
289
|
+
fi
|
|
200
290
|
fi
|
|
201
291
|
|
|
202
292
|
echo "---"
|
|
@@ -215,4 +305,6 @@ echo "--1h | bash=$SET_INTERVAL param1=1h dir=$HAPPY_LOCAL_DIR terminal=false re
|
|
|
215
305
|
echo "--2h | bash=$SET_INTERVAL param1=2h dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
216
306
|
echo "--6h | bash=$SET_INTERVAL param1=6h dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
217
307
|
echo "--12h | bash=$SET_INTERVAL param1=12h dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
218
|
-
echo "--1d | bash=$SET_INTERVAL param1=1d dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
308
|
+
echo "--1d | bash=$SET_INTERVAL param1=1d dir=$HAPPY_LOCAL_DIR terminal=false refresh=true"
|
|
309
|
+
|
|
310
|
+
exit 0
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Open preferred terminal and run a happys command.
|
|
5
|
+
#
|
|
6
|
+
# Preference order follows wt shell semantics:
|
|
7
|
+
# - HAPPY_LOCAL_WT_TERMINAL=ghostty|iterm|terminal|current
|
|
8
|
+
# (also accepts "auto" which tries ghostty->iterm->terminal->current)
|
|
9
|
+
#
|
|
10
|
+
# Notes:
|
|
11
|
+
# - iTerm / Terminal: we run the command automatically via AppleScript.
|
|
12
|
+
# - Ghostty: best-effort; if we can't run the command, we open Ghostty in the dir and copy the command to clipboard.
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
DEFAULT_HOME_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
16
|
+
|
|
17
|
+
# Prefer explicit env vars, but default to the install location inferred from this script path.
|
|
18
|
+
CANONICAL_HOME_DIR="${HAPPY_STACKS_CANONICAL_HOME_DIR:-${HAPPY_LOCAL_CANONICAL_HOME_DIR:-$DEFAULT_HOME_DIR}}"
|
|
19
|
+
HAPPY_LOCAL_DIR="${HAPPY_LOCAL_DIR:-${HAPPY_STACKS_HOME_DIR:-$CANONICAL_HOME_DIR}}"
|
|
20
|
+
HAPPY_STACKS_HOME_DIR="${HAPPY_STACKS_HOME_DIR:-$HAPPY_LOCAL_DIR}"
|
|
21
|
+
|
|
22
|
+
# Use shared resolver for workspace dir (respects HAPPY_STACKS_WORKSPACE_DIR and canonical pointer env).
|
|
23
|
+
LIB_DIR="$HAPPY_LOCAL_DIR/extras/swiftbar/lib"
|
|
24
|
+
if [[ -f "$LIB_DIR/utils.sh" ]]; then
|
|
25
|
+
# shellcheck source=/dev/null
|
|
26
|
+
source "$LIB_DIR/utils.sh"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
WORKDIR="${HAPPY_STACKS_WORKSPACE_DIR:-$(resolve_workspace_dir 2>/dev/null || true)}"
|
|
30
|
+
[[ -z "$WORKDIR" ]] && WORKDIR="$HAPPY_STACKS_HOME_DIR/workspace"
|
|
31
|
+
if [[ ! -d "$WORKDIR" ]]; then
|
|
32
|
+
WORKDIR="$HOME"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
HAPPYS_SH="$HAPPY_LOCAL_DIR/extras/swiftbar/happys.sh"
|
|
36
|
+
if [[ ! -x "$HAPPYS_SH" ]]; then
|
|
37
|
+
echo "missing happys wrapper: $HAPPYS_SH" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
pref_raw="$(echo "${HAPPY_STACKS_WT_TERMINAL:-${HAPPY_LOCAL_WT_TERMINAL:-auto}}" | tr '[:upper:]' '[:lower:]')"
|
|
42
|
+
pref="$pref_raw"
|
|
43
|
+
if [[ "$pref" == "" ]]; then pref="auto"; fi
|
|
44
|
+
|
|
45
|
+
cmd=( "$HAPPYS_SH" "$@" )
|
|
46
|
+
|
|
47
|
+
escape_for_osascript_string() {
|
|
48
|
+
local s="$1"
|
|
49
|
+
s="${s//\\/\\\\}"
|
|
50
|
+
s="${s//\"/\\\"}"
|
|
51
|
+
echo "$s"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
shell_cmd() {
|
|
55
|
+
local joined=""
|
|
56
|
+
local q
|
|
57
|
+
joined="cd \"${WORKDIR//\"/\\\"}\"; "
|
|
58
|
+
for q in "${cmd[@]}"; do
|
|
59
|
+
local escaped
|
|
60
|
+
escaped="$(printf "%s" "$q" | sed "s/'/'\\\\''/g")"
|
|
61
|
+
joined+="'${escaped}' "
|
|
62
|
+
done
|
|
63
|
+
joined+="; echo; echo \"[happy-stacks] done\"; exec /bin/zsh -i"
|
|
64
|
+
echo "$joined"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
run_iterm() {
|
|
68
|
+
if ! command -v osascript >/dev/null 2>&1; then
|
|
69
|
+
return 1
|
|
70
|
+
fi
|
|
71
|
+
local s
|
|
72
|
+
s="$(shell_cmd)"
|
|
73
|
+
s="$(escape_for_osascript_string "$s")"
|
|
74
|
+
osascript \
|
|
75
|
+
-e 'tell application "iTerm" to activate' \
|
|
76
|
+
-e 'tell application "iTerm" to create window with default profile' \
|
|
77
|
+
-e "tell application \"iTerm\" to tell current session of current window to write text \"${s}\"" >/dev/null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
run_terminal_app() {
|
|
81
|
+
if ! command -v osascript >/dev/null 2>&1; then
|
|
82
|
+
return 1
|
|
83
|
+
fi
|
|
84
|
+
local s
|
|
85
|
+
s="$(shell_cmd)"
|
|
86
|
+
s="$(escape_for_osascript_string "$s")"
|
|
87
|
+
osascript \
|
|
88
|
+
-e 'tell application "Terminal" to activate' \
|
|
89
|
+
-e "tell application \"Terminal\" to do script \"${s}\"" >/dev/null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
run_ghostty() {
|
|
93
|
+
if ! command -v ghostty >/dev/null 2>&1; then
|
|
94
|
+
return 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
local s
|
|
98
|
+
s="$(shell_cmd)"
|
|
99
|
+
if ghostty --working-directory "$WORKDIR" -e /bin/zsh -lc "$s" >/dev/null 2>&1; then
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
echo -n "$s" | pbcopy 2>/dev/null || true
|
|
104
|
+
ghostty --working-directory "$WORKDIR" >/dev/null 2>&1 || true
|
|
105
|
+
return 0
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try_one() {
|
|
109
|
+
local t="$1"
|
|
110
|
+
case "$t" in
|
|
111
|
+
ghostty) run_ghostty ;;
|
|
112
|
+
iterm) run_iterm ;;
|
|
113
|
+
terminal) run_terminal_app ;;
|
|
114
|
+
current) ( cd "$WORKDIR"; exec "${cmd[@]}" ) ;;
|
|
115
|
+
*) return 1 ;;
|
|
116
|
+
esac
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if [[ "$pref" == "auto" ]]; then
|
|
120
|
+
for t in ghostty iterm terminal current; do
|
|
121
|
+
if try_one "$t"; then
|
|
122
|
+
exit 0
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
try_one "$pref"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# SwiftBar menu action wrapper.
|
|
5
|
+
# Runs `happys` using the stable shim installed under <homeDir>/bin.
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
DEFAULT_HOME_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
9
|
+
|
|
10
|
+
# Treat presence of HAPPY_STACKS_SANDBOX_DIR as sandbox mode.
|
|
11
|
+
is_sandboxed() {
|
|
12
|
+
[[ -n "${HAPPY_STACKS_SANDBOX_DIR:-}" ]]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Prefer explicit env vars, but default to the install location inferred from this script path.
|
|
16
|
+
CANONICAL_HOME_DIR="${HAPPY_STACKS_CANONICAL_HOME_DIR:-${HAPPY_LOCAL_CANONICAL_HOME_DIR:-$DEFAULT_HOME_DIR}}"
|
|
17
|
+
HAPPY_LOCAL_DIR="${HAPPY_LOCAL_DIR:-${HAPPY_STACKS_HOME_DIR:-$CANONICAL_HOME_DIR}}"
|
|
18
|
+
HAPPY_STACKS_HOME_DIR="${HAPPY_STACKS_HOME_DIR:-$HAPPY_LOCAL_DIR}"
|
|
19
|
+
|
|
20
|
+
HAPPYS_BIN="$HAPPY_LOCAL_DIR/bin/happys"
|
|
21
|
+
if [[ ! -x "$HAPPYS_BIN" ]]; then
|
|
22
|
+
if is_sandboxed; then
|
|
23
|
+
echo "happys not found in sandbox home: $HAPPYS_BIN" >&2
|
|
24
|
+
echo "Tip: re-run: happys init (inside the sandbox) then re-install the menubar plugin." >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
HAPPYS_BIN="$(command -v happys 2>/dev/null || true)"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if [[ -z "${HAPPYS_BIN:-}" ]]; then
|
|
31
|
+
echo "happys not found (run: npx happy-stacks init, or npm i -g happy-stacks)" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
exec "$HAPPYS_BIN" "$@"
|
|
@@ -13,7 +13,19 @@ PLUGIN_SOURCE="$SCRIPT_DIR/happy-stacks.5s.sh"
|
|
|
13
13
|
# You can override:
|
|
14
14
|
# HAPPY_LOCAL_SWIFTBAR_INTERVAL=30s ./install.sh
|
|
15
15
|
PLUGIN_INTERVAL="${HAPPY_STACKS_SWIFTBAR_INTERVAL:-${HAPPY_LOCAL_SWIFTBAR_INTERVAL:-5m}}"
|
|
16
|
-
|
|
16
|
+
PLUGIN_BASENAME="${HAPPY_STACKS_SWIFTBAR_PLUGIN_BASENAME:-happy-stacks}"
|
|
17
|
+
PLUGIN_FILE="${PLUGIN_BASENAME}.${PLUGIN_INTERVAL}.sh"
|
|
18
|
+
|
|
19
|
+
# Optional: install a wrapper plugin instead of copying the source.
|
|
20
|
+
# This is useful for sandbox/test installs so the plugin can be pinned to a specific home/canonical dir
|
|
21
|
+
# even under SwiftBar's minimal environment.
|
|
22
|
+
WRAPPER="${HAPPY_STACKS_SWIFTBAR_PLUGIN_WRAPPER:-0}"
|
|
23
|
+
|
|
24
|
+
escape_single_quotes() {
|
|
25
|
+
# Escape a string so it can be safely embedded inside single quotes in a bash script.
|
|
26
|
+
# e.g. abc'def -> abc'"'"'def
|
|
27
|
+
printf "%s" "$1" | sed "s/'/'\"'\"'/g"
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
FORCE=0
|
|
19
31
|
for arg in "$@"; do
|
|
@@ -128,33 +140,107 @@ echo -e "${YELLOW}Step 3: Installing Happy Stacks plugin...${NC}"
|
|
|
128
140
|
|
|
129
141
|
PLUGIN_DEST="$PLUGINS_DIR/$PLUGIN_FILE"
|
|
130
142
|
|
|
131
|
-
# Remove any legacy happy-local plugins to avoid duplicates.
|
|
132
|
-
|
|
143
|
+
# Remove any legacy happy-local plugins to avoid duplicates *only* for the primary plugin.
|
|
144
|
+
# For sandbox installs (separate basenames), never delete other plugins.
|
|
145
|
+
if [[ "$PLUGIN_BASENAME" == "happy-stacks" ]]; then
|
|
146
|
+
rm -f "$PLUGINS_DIR"/happy-local.*.sh 2>/dev/null || true
|
|
147
|
+
fi
|
|
133
148
|
|
|
149
|
+
EXISTED=0
|
|
134
150
|
if [[ -f "$PLUGIN_DEST" ]]; then
|
|
151
|
+
EXISTED=1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
SHOULD_INSTALL=1
|
|
155
|
+
if [[ "$EXISTED" == "1" ]]; then
|
|
135
156
|
echo "Plugin already exists at $PLUGIN_DEST"
|
|
136
157
|
if [[ "$FORCE" == "1" ]] || [[ ! -t 0 ]]; then
|
|
137
|
-
|
|
138
|
-
chmod +x "$PLUGIN_DEST"
|
|
139
|
-
echo -e "${GREEN}✓ Plugin updated${NC}"
|
|
158
|
+
SHOULD_INSTALL=1
|
|
140
159
|
else
|
|
141
160
|
echo "Would you like to overwrite it? (y/n)"
|
|
142
161
|
read -r OVERWRITE_CHOICE
|
|
143
|
-
|
|
144
162
|
if [[ "$OVERWRITE_CHOICE" != "y" ]] && [[ "$OVERWRITE_CHOICE" != "Y" ]]; then
|
|
163
|
+
SHOULD_INSTALL=0
|
|
145
164
|
echo "Skipping plugin installation."
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
if [[ "$SHOULD_INSTALL" == "1" ]]; then
|
|
170
|
+
if [[ "$WRAPPER" == "1" ]]; then
|
|
171
|
+
# Generate a wrapper plugin that pins env vars and executes the real plugin source.
|
|
172
|
+
HOME_DIR_VAL="${HAPPY_STACKS_HOME_DIR:-${HAPPY_LOCAL_DIR:-$HOME/.happy-stacks}}"
|
|
173
|
+
CANONICAL_DIR_VAL="${HAPPY_STACKS_CANONICAL_HOME_DIR:-${HAPPY_LOCAL_CANONICAL_HOME_DIR:-$HOME/.happy-stacks}}"
|
|
174
|
+
SANDBOX_DIR_VAL="${HAPPY_STACKS_SANDBOX_DIR:-}"
|
|
175
|
+
WORKSPACE_DIR_VAL="${HAPPY_STACKS_WORKSPACE_DIR:-}"
|
|
176
|
+
RUNTIME_DIR_VAL="${HAPPY_STACKS_RUNTIME_DIR:-}"
|
|
177
|
+
STORAGE_DIR_VAL="${HAPPY_STACKS_STORAGE_DIR:-}"
|
|
178
|
+
|
|
179
|
+
if [[ -n "$SANDBOX_DIR_VAL" ]]; then
|
|
180
|
+
[[ -z "$WORKSPACE_DIR_VAL" ]] && WORKSPACE_DIR_VAL="${SANDBOX_DIR_VAL%/}/workspace"
|
|
181
|
+
[[ -z "$RUNTIME_DIR_VAL" ]] && RUNTIME_DIR_VAL="${SANDBOX_DIR_VAL%/}/runtime"
|
|
182
|
+
[[ -z "$STORAGE_DIR_VAL" ]] && STORAGE_DIR_VAL="${SANDBOX_DIR_VAL%/}/storage"
|
|
183
|
+
fi
|
|
184
|
+
HOME_DIR_ESC="$(escape_single_quotes "$HOME_DIR_VAL")"
|
|
185
|
+
CANONICAL_DIR_ESC="$(escape_single_quotes "$CANONICAL_DIR_VAL")"
|
|
186
|
+
SANDBOX_DIR_ESC="$(escape_single_quotes "$SANDBOX_DIR_VAL")"
|
|
187
|
+
WORKSPACE_DIR_ESC="$(escape_single_quotes "$WORKSPACE_DIR_VAL")"
|
|
188
|
+
RUNTIME_DIR_ESC="$(escape_single_quotes "$RUNTIME_DIR_VAL")"
|
|
189
|
+
STORAGE_DIR_ESC="$(escape_single_quotes "$STORAGE_DIR_VAL")"
|
|
190
|
+
SRC_ESC="$(escape_single_quotes "$PLUGIN_SOURCE")"
|
|
191
|
+
BASENAME_ESC="$(escape_single_quotes "$PLUGIN_BASENAME")"
|
|
192
|
+
|
|
193
|
+
cat >"$PLUGIN_DEST" <<EOF
|
|
194
|
+
#!/bin/bash
|
|
195
|
+
set -euo pipefail
|
|
196
|
+
export HAPPY_STACKS_HOME_DIR='$HOME_DIR_ESC'
|
|
197
|
+
export HAPPY_LOCAL_DIR='$HOME_DIR_ESC'
|
|
198
|
+
export HAPPY_STACKS_CANONICAL_HOME_DIR='$CANONICAL_DIR_ESC'
|
|
199
|
+
export HAPPY_LOCAL_CANONICAL_HOME_DIR='$CANONICAL_DIR_ESC'
|
|
200
|
+
export HAPPY_STACKS_SWIFTBAR_PLUGIN_BASENAME='$BASENAME_ESC'
|
|
201
|
+
export HAPPY_LOCAL_SWIFTBAR_PLUGIN_BASENAME='$BASENAME_ESC'
|
|
202
|
+
if [[ -n '$SANDBOX_DIR_ESC' ]]; then
|
|
203
|
+
export HAPPY_STACKS_SANDBOX_DIR='$SANDBOX_DIR_ESC'
|
|
204
|
+
fi
|
|
205
|
+
if [[ -n '$WORKSPACE_DIR_ESC' ]]; then
|
|
206
|
+
export HAPPY_STACKS_WORKSPACE_DIR='$WORKSPACE_DIR_ESC'
|
|
207
|
+
export HAPPY_LOCAL_WORKSPACE_DIR='$WORKSPACE_DIR_ESC'
|
|
208
|
+
fi
|
|
209
|
+
if [[ -n '$RUNTIME_DIR_ESC' ]]; then
|
|
210
|
+
export HAPPY_STACKS_RUNTIME_DIR='$RUNTIME_DIR_ESC'
|
|
211
|
+
export HAPPY_LOCAL_RUNTIME_DIR='$RUNTIME_DIR_ESC'
|
|
212
|
+
fi
|
|
213
|
+
if [[ -n '$STORAGE_DIR_ESC' ]]; then
|
|
214
|
+
export HAPPY_STACKS_STORAGE_DIR='$STORAGE_DIR_ESC'
|
|
215
|
+
export HAPPY_LOCAL_STORAGE_DIR='$STORAGE_DIR_ESC'
|
|
216
|
+
fi
|
|
217
|
+
# Prevent any re-exec into a "real" install when testing.
|
|
218
|
+
export HAPPY_STACKS_CLI_ROOT_DISABLE="1"
|
|
219
|
+
exec '$SRC_ESC'
|
|
220
|
+
EOF
|
|
221
|
+
chmod +x "$PLUGIN_DEST"
|
|
222
|
+
if [[ "$EXISTED" == "1" ]]; then
|
|
223
|
+
echo -e "${GREEN}✓ Plugin updated (wrapper)${NC}"
|
|
146
224
|
else
|
|
147
|
-
|
|
148
|
-
|
|
225
|
+
echo -e "${GREEN}✓ Plugin installed (wrapper)${NC}"
|
|
226
|
+
fi
|
|
227
|
+
else
|
|
228
|
+
cp "$PLUGIN_SOURCE" "$PLUGIN_DEST"
|
|
229
|
+
chmod +x "$PLUGIN_DEST"
|
|
230
|
+
if [[ "$EXISTED" == "1" ]]; then
|
|
149
231
|
echo -e "${GREEN}✓ Plugin updated${NC}"
|
|
232
|
+
else
|
|
233
|
+
echo -e "${GREEN}✓ Plugin installed${NC}"
|
|
150
234
|
fi
|
|
151
235
|
fi
|
|
152
|
-
else
|
|
153
|
-
cp "$PLUGIN_SOURCE" "$PLUGIN_DEST"
|
|
154
|
-
chmod +x "$PLUGIN_DEST"
|
|
155
|
-
echo -e "${GREEN}✓ Plugin installed${NC}"
|
|
156
236
|
fi
|
|
157
237
|
|
|
238
|
+
#
|
|
239
|
+
# Ensure helper scripts are executable (SwiftBar menu actions rely on this).
|
|
240
|
+
# The repo usually tracks +x, but home installs can lose mode bits depending on how assets are copied.
|
|
241
|
+
#
|
|
242
|
+
chmod +x "$SCRIPT_DIR"/*.sh 2>/dev/null || true
|
|
243
|
+
|
|
158
244
|
echo ""
|
|
159
245
|
|
|
160
246
|
# Step 4: Launch SwiftBar if not running
|