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
package/docs/menubar.md CHANGED
@@ -32,6 +32,26 @@ SwiftBar runs a script on an interval and renders its output as native macOS men
32
32
  - Each component includes a **Worktrees** submenu listing all worktrees, with actions to switch/open
33
33
  - Quick actions: `wt status/sync/update`, PR worktree prompt, open shells/editors (`wt shell/code/cursor`)
34
34
  - Shows **origin** and **upstream** comparisons for the component repo’s main branch (based on your last `git fetch`)
35
+ - Uses a **Git cache** by default so SwiftBar refresh stays fast even with many stacks/worktrees
36
+
37
+ ## Modes: selfhost vs dev
38
+
39
+ The menu supports two modes:
40
+
41
+ - **Selfhost mode** (`selfhost`): lightweight “control panel” for running Happy.
42
+ - Shows only the main stack essentials (Server/Daemon/Autostart/Tailscale) plus a small **Maintenance** section.
43
+ - Hides developer-oriented sections like stacks enumeration, components git/worktrees, and worktree tooling.
44
+ - **Dev mode** (`dev`): full happy-stacks control plane (stacks + components + worktrees).
45
+
46
+ ### How to switch modes
47
+
48
+ - In the menu, use the **Mode** section at the top, or
49
+ - From a terminal:
50
+
51
+ ```bash
52
+ happys menubar mode selfhost
53
+ happys menubar mode dev
54
+ ```
35
55
 
36
56
  ## Stacks (multiple instances)
37
57
 
@@ -125,6 +145,40 @@ The plugin defaults to a slower interval (recommended), and also sets:
125
145
 
126
146
  You can also change the interval directly from the menu via **Refresh interval** (it renames the plugin file and restarts SwiftBar).
127
147
 
148
+ ## Git cache (important for performance)
149
+
150
+ Git/worktree inspection is the most expensive part of the menu when you have many stacks.
151
+ By default, the plugin runs in **cached mode**:
152
+
153
+ - It renders git/worktree info from an on-disk cache under `~/.happy-stacks/cache/swiftbar/git`.
154
+ - Normal menu refreshes do **not** run git commands (so refresh stays snappy).
155
+ - The cache is refreshed explicitly (via menu actions), and can optionally refresh on TTL expiry.
156
+
157
+ Controls and settings:
158
+
159
+ - **Refresh now**: open **Components → Git cache** and run:
160
+ - “Refresh now (main components)”
161
+ - “Refresh now (all stacks/components)”
162
+ - or “Refresh now (this stack)” from a stack’s Components menu
163
+ - **TTL**: `HAPPY_STACKS_SWIFTBAR_GIT_TTL_SEC` (default `3600` seconds)
164
+ - **TTL**: `HAPPY_STACKS_SWIFTBAR_GIT_TTL_SEC` (default `21600` seconds = 6 hours)
165
+ - **Mode**: `HAPPY_STACKS_SWIFTBAR_GIT_MODE=cached|live` (default `cached`)
166
+ - (Optional) **Background auto-refresh**: `HAPPY_STACKS_SWIFTBAR_GIT_AUTO_REFRESH_SCOPE=main|all|off` (default `main`)
167
+
168
+ Notes:
169
+
170
+ - Cached git info can be stale; it’s meant for at-a-glance signal.
171
+ - Actions like worktree switching/build/dev are always live (they use `happys`); only *displayed git status* is cached.
172
+
173
+ ## Maintenance (selfhost mode)
174
+
175
+ In **selfhost** mode, the menu includes a **Maintenance** section that can:
176
+
177
+ - show whether a `happy-stacks` update is available (from cached `~/.happy-stacks/cache/update.json`)
178
+ - run:
179
+ - `happys self check`
180
+ - `happys self update`
181
+
128
182
  ## Terminal preference for interactive actions
129
183
 
130
184
  Many menu actions open a terminal (interactive wizards, long-running dev servers, etc).
@@ -0,0 +1,141 @@
1
+ # Paths, folders, and env precedence
2
+
3
+ This doc explains the **directories** that Happy Stacks uses (home/workspace/runtime/stacks), and the **environment file precedence** used by `happys`.
4
+
5
+ If you’re ever unsure what your machine is actually using, run:
6
+
7
+ ```bash
8
+ happys where
9
+ ```
10
+
11
+ ---
12
+
13
+ ## Quick glossary
14
+
15
+ - **CLI root dir**
16
+ - The directory containing the `happy-stacks` scripts (`scripts/*.mjs`) that your `happys` command is currently executing.
17
+ - This is *not* necessarily your current shell `cwd`.
18
+ - It can be:
19
+ - a cloned repo checkout (e.g. `/Users/<you>/.../happy-local`), or
20
+ - the installed runtime package under `~/.happy-stacks/runtime/node_modules/happy-stacks` (see “Runtime dir”).
21
+
22
+ - **Home dir** (`HAPPY_STACKS_HOME_DIR`)
23
+ - Default: `~/.happy-stacks`
24
+ - Stores **global user config** + caches, and may include a runtime install.
25
+
26
+ - **Runtime dir** (`HAPPY_STACKS_RUNTIME_DIR`)
27
+ - Default: `~/.happy-stacks/runtime`
28
+ - Used by `happys self update` to install/upgrade a pinned `happy-stacks` runtime package.
29
+
30
+ - **Workspace dir** (`HAPPY_STACKS_WORKSPACE_DIR`)
31
+ - Default: `~/.happy-stacks/workspace` (when it exists).
32
+ - This is the **storage workspace for component repos and worktrees** used by Happy Stacks.
33
+ - Important: this is **not your IDE workspace**; it’s where Happy Stacks keeps `components/` by default.
34
+ - Back-compat: before you run `happys init` (cloned repo usage), we fall back to using the CLI root dir as the workspace, so `components/` lives inside the repo checkout.
35
+
36
+ - **Components dir**
37
+ - Computed as: `<workspaceDir>/components`
38
+ - Contains `happy`, `happy-cli`, `happy-server-light`, `happy-server`, plus `.worktrees/`.
39
+
40
+ - **Stacks storage dir**
41
+ - Default: `~/.happy/stacks`
42
+ - Each stack lives under `~/.happy/stacks/<name>/...` and has its own env file:
43
+ - `~/.happy/stacks/<name>/env`
44
+ - Legacy stacks path is also supported:
45
+ - `~/.happy/local/stacks/<name>/env`
46
+
47
+ ---
48
+
49
+ ## “Where am I actually running from?”
50
+
51
+ `happys` may **re-exec** to a different CLI root dir (for example, when you use an installed shim but want it to run a local checkout).
52
+
53
+ - Run `happys where` to see:
54
+ - **rootDir** (CLI root dir)
55
+ - **homeDir** (stacks home dir)
56
+ - **runtimeDir**
57
+ - **workspaceDir**
58
+ - resolved env file paths
59
+
60
+ Tip: `happys where --json` is easier to parse.
61
+
62
+ ---
63
+
64
+ ## Env files + precedence (lowest → highest)
65
+
66
+ Happy Stacks loads env in `scripts/utils/env.mjs`.
67
+
68
+ ### 0) “Canonical pointer” env (discovery)
69
+
70
+ If `HAPPY_STACKS_HOME_DIR` is *not* set, we first try to read the **canonical pointer** env file to discover the intended home dir (useful for LaunchAgents / SwiftBar / minimal shells).
71
+
72
+ - Default canonical pointer path: `~/.happy-stacks/.env`
73
+ - Override canonical pointer location:
74
+ - `HAPPY_STACKS_CANONICAL_HOME_DIR=/some/dir` (pointer becomes `<dir>/.env`)
75
+
76
+ ### 1) Global defaults (home config) OR cloned-repo defaults
77
+
78
+ If home config exists, we load:
79
+
80
+ - `~/.happy-stacks/.env` (**defaults**)
81
+ - `~/.happy-stacks/env.local` (**overrides**, prefix-aware for `HAPPY_STACKS_*` / `HAPPY_LOCAL_*`)
82
+
83
+ If home config does *not* exist (cloned repo usage before `happys init`), we load:
84
+
85
+ - `<cliRootDir>/.env`
86
+ - `<cliRootDir>/env.local` (prefix-aware for `HAPPY_STACKS_*` / `HAPPY_LOCAL_*`)
87
+
88
+ ### 2) Repo `.env` fallback (dev convenience)
89
+
90
+ Even when home config exists, we also load:
91
+
92
+ - `<cliRootDir>/.env` (non-overriding fallback)
93
+
94
+ This exists so repo-local dev settings (example: `HAPPY_CODEX_BIN`) can work without forcing everyone to duplicate them into `~/.happy-stacks/env.local`.
95
+
96
+ Notes:
97
+ - This is a **fallback only** (`override: false`): it won’t stomp on values already provided by the environment or home config.
98
+ - We intentionally do **not** auto-load `<cliRootDir>/env.local` in this “home config exists” path, because it’s higher-precedence and can unexpectedly fight stack config.
99
+
100
+ ### 3) Stack env overlay (highest precedence)
101
+
102
+ Finally, we load the active stack env file (override = true):
103
+
104
+ - `HAPPY_STACKS_ENV_FILE` (or legacy `HAPPY_LOCAL_ENV_FILE`)
105
+ - if neither is set, we auto-select the env file for the current stack (defaults to `main`) if it exists
106
+
107
+ Stack env files are allowed to contain **non-prefixed keys** (like `DATABASE_URL`) because that’s required for per-stack isolation.
108
+
109
+ ---
110
+
111
+ ## What should go where? (rules of thumb)
112
+
113
+ - Put **global, machine-wide defaults** in `~/.happy-stacks/.env`.
114
+ - Put **your personal overrides** in `~/.happy-stacks/env.local`.
115
+ - Put **per-stack isolation config** in the stack env file `~/.happy/stacks/<name>/env` (this is what `happys stack edit` and `happys stack wt` mutate).
116
+ - Put **repo-local dev-only defaults** in `<cliRootDir>/.env` (works best when you’re actually running from that checkout as the CLI root dir).
117
+
118
+ ---
119
+
120
+ ## Sandbox / test installs (fully isolated)
121
+
122
+ If you want to test the full install + setup flows without touching your real installation, run with:
123
+
124
+ ```bash
125
+ npx happy-stacks --sandbox-dir /tmp/happy-stacks-sandbox where
126
+ ```
127
+
128
+ In sandbox mode, Happy Stacks redirects **home/workspace/runtime/storage** under the sandbox folder (so you can `rm -rf` it to reset).
129
+
130
+ Global OS side effects (PATH edits, SwiftBar plugin install, LaunchAgents/systemd services) are **disabled by default** in sandbox mode.
131
+ To explicitly allow them for testing, set:
132
+
133
+ - `HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1`
134
+
135
+ ---
136
+
137
+ ## Related docs
138
+
139
+ - `docs/stacks.md` (stacks lifecycle + commands)
140
+ - `docs/worktrees-and-forks.md` (worktrees layout + upstream/fork workflows)
141
+
@@ -13,8 +13,67 @@ Both are forks/flavors of the same upstream server repo (`slopus/happy-server`),
13
13
 
14
14
  - **`happy-server`** (full server)
15
15
  - closer to upstream “full” behavior (useful when developing server changes meant to go upstream)
16
- - typically does **not** serve the built UI (you’ll use the UI dev server or connect the UI separately)
17
- - useful when you need to test upstream/server-only behavior or reproduce upstream issues
16
+ - the upstream server typically does **not** serve the built UI itself, but happy-stacks provides a **UI gateway** so you still get a single URL that serves the UI and proxies API/websockets/files
17
+ - useful when you need to test upstream/server-only behavior or reproduce upstream issues, with per-stack infra isolation
18
+
19
+ ## Full server infra (no AWS required)
20
+
21
+ `happy-server` requires:
22
+
23
+ - Postgres (`DATABASE_URL`)
24
+ - Redis (`REDIS_URL`)
25
+ - S3-compatible object storage (`S3_*`)
26
+
27
+ Happy Stacks can **manage this for you automatically per stack** using Docker Compose (Postgres + Redis + Minio),
28
+ so you **do not need AWS S3**.
29
+
30
+ This happens automatically when you run `happys start/dev --server=happy-server` (or when a stack uses `happy-server`),
31
+ unless you disable it with:
32
+
33
+ ```bash
34
+ export HAPPY_STACKS_MANAGED_INFRA=0
35
+ ```
36
+
37
+ If disabled, you must provide `DATABASE_URL`, `REDIS_URL`, and `S3_*` yourself.
38
+
39
+ ## UI serving with `happy-server`
40
+
41
+ The upstream `happy-server` does not serve the built UI itself.
42
+
43
+ For a “one URL” UX (especially with Tailscale), happy-stacks starts a lightweight **UI gateway** that:
44
+
45
+ - serves the built UI at `/` (if a build exists)
46
+ - reverse-proxies API calls to the backend server
47
+ - reverse-proxies realtime websocket upgrades (`/v1/updates`)
48
+ - reverse-proxies public files (to local Minio)
49
+
50
+ This means `happys start --server=happy-server` can still work end-to-end without requiring AWS S3 or a separate nginx setup.
51
+
52
+ ## Migrating between flavors (SQLite ⇢ Postgres)
53
+
54
+ Happy Stacks includes an **experimental** migration helper that can copy core chat data from a
55
+ `happy-server-light` stack (SQLite) into a `happy-server` stack (Postgres):
56
+
57
+ ```bash
58
+ happys migrate light-to-server --from-stack=main --to-stack=full1
59
+ ```
60
+
61
+ Optional: include local public files (server-light `files/`) by mirroring them into Minio:
62
+
63
+ ```bash
64
+ happys migrate light-to-server --from-stack=main --to-stack=full1 --include-files
65
+ ```
66
+
67
+ Notes:
68
+ - This preserves IDs (session URLs remain valid on the target server).
69
+ - It also copies the `HANDY_MASTER_SECRET` from the source stack into the target stack’s secret file so auth tokens remain valid.
70
+
71
+ ## Prisma behavior (why start is safer under LaunchAgents)
72
+
73
+ - **`happys start`** is “production-like”. It avoids running heavyweight schema sync loops under launchd KeepAlive.
74
+ - **`happys dev`** is for rapid iteration:
75
+ - for `happy-server`: Happy Stacks runs `prisma migrate deploy` by default (configurable via `HAPPY_STACKS_PRISMA_MIGRATE`).
76
+ - for `happy-server-light`: the upstream dev script runs `prisma db push` by default (configurable via `HAPPY_STACKS_PRISMA_PUSH`).
18
77
 
19
78
  Important: for a given run (`happys start` / `happys dev`) you choose **one** flavor.
20
79
 
package/docs/stacks.md CHANGED
@@ -7,6 +7,7 @@ A “stack” is just:
7
7
  - a dedicated **server port**
8
8
  - isolated directories for **UI build output**, **CLI home**, and **logs**
9
9
  - optional per-component overrides (point at specific worktrees)
10
+ - (when using `happy-server`) isolated **infra** (Postgres/Redis/Minio) managed per-stack
10
11
 
11
12
  Stacks are configured via a plain env file stored under:
12
13
 
@@ -40,6 +41,43 @@ Auto-pick a port:
40
41
  happys stack new exp2
41
42
  ```
42
43
 
44
+ ## Create a PR test stack (copy/paste friendly)
45
+
46
+ If you want maintainers to be able to try your PR quickly, you can give them a single command that:
47
+
48
+ - creates an isolated stack
49
+ - checks out PR(s) into worktrees
50
+ - pins those worktrees to the stack
51
+ - optionally seeds auth
52
+ - optionally starts the stack in dev mode
53
+
54
+ Example (most common):
55
+
56
+ ```bash
57
+ happys stack pr pr123 \
58
+ --happy=https://github.com/slopus/happy/pull/123 \
59
+ --happy-cli=https://github.com/slopus/happy-cli/pull/456 \
60
+ --seed-auth --copy-auth-from=dev-auth --link-auth \
61
+ --dev
62
+ ```
63
+
64
+ Notes:
65
+
66
+ - `--remote` (default `upstream`) controls which Git remote is used to fetch `refs/pull/<n>/head`.
67
+ - `--seed-auth` uses `happys stack auth <stack> copy-from <source>` under the hood, which also best-effort seeds DB Account rows (avoids FK errors like Prisma `P2003`).
68
+ - You can use your existing non-stacks Happy install as an auth seed source with:
69
+ - `--copy-auth-from=legacy` (reads from `~/.happy/{cli,server-light}` best-effort)
70
+ - `--link-auth` symlinks auth files instead of copying them (keeps credentials in sync, but reduces isolation).
71
+ - For full-server stacks (`happy-server`), seeding may need Docker infra:
72
+
73
+ ```bash
74
+ happys stack pr pr789 \
75
+ --server=happy-server \
76
+ --happy-server=https://github.com/slopus/happy-server/pull/789 \
77
+ --seed-auth --copy-auth-from=dev-auth --with-infra \
78
+ --dev
79
+ ```
80
+
43
81
  Interactive wizard (TTY only):
44
82
 
45
83
  ```bash
@@ -52,6 +90,15 @@ The wizard lets you:
52
90
  - pick or create worktrees for `happy`, `happy-cli`, and the chosen server component
53
91
  - choose which Git remote to base newly-created worktrees on (defaults to `upstream`)
54
92
 
93
+ When creating `--server=happy-server` stacks, happy-stacks will also reserve additional ports and persist
94
+ the stack-scoped infra config in the stack env file (so restarts are stable):
95
+
96
+ - `HAPPY_STACKS_PG_PORT`
97
+ - `HAPPY_STACKS_REDIS_PORT`
98
+ - `HAPPY_STACKS_MINIO_PORT`
99
+ - `HAPPY_STACKS_MINIO_CONSOLE_PORT`
100
+ - `DATABASE_URL`, `REDIS_URL`, `S3_*`
101
+
55
102
  ## Run a stack
56
103
 
57
104
  Dev mode:
@@ -128,9 +175,9 @@ Global/non-stack commands:
128
175
  - `happys bootstrap` (sets up shared component repos)
129
176
  - `happys cli:link` (installs `happy` shim under `~/.happy-stacks/bin/`)
130
177
 
131
- ## Services (macOS LaunchAgents)
178
+ ## Services (autostart)
132
179
 
133
- Each stack can have its own LaunchAgent (so multiple stacks can start at login).
180
+ Each stack can have its own autostart service (so multiple stacks can start at login).
134
181
 
135
182
  ```bash
136
183
  happys stack service exp1 install
@@ -141,10 +188,12 @@ happys stack service exp1 logs
141
188
 
142
189
  Implementation notes:
143
190
 
144
- - Service label is stack-scoped:
191
+ - Service name/label is stack-scoped:
145
192
  - `main` → `com.happy.stacks` (legacy: `com.happy.local`)
146
193
  - `exp1` → `com.happy.stacks.exp1` (legacy: `com.happy.local.exp1`)
147
- - The LaunchAgent persists `HAPPY_STACKS_ENV_FILE` (and legacy `HAPPY_LOCAL_ENV_FILE`), so you can edit the stack env file without reinstalling.
194
+ - macOS: implemented via **launchd LaunchAgents**
195
+ - Linux: implemented via **systemd user services** (if available)
196
+ - The service persists `HAPPY_STACKS_ENV_FILE` (and legacy `HAPPY_LOCAL_ENV_FILE`), so you can edit the stack env file without reinstalling.
148
197
 
149
198
  ## Component/worktree selection per stack
150
199
 
@@ -181,6 +230,8 @@ On startup, `happy-stacks` loads env in this order:
181
230
 
182
231
  `happys stack ...` sets `HAPPY_STACKS_ENV_FILE=~/.happy/stacks/<name>/env` (and also sets legacy `HAPPY_LOCAL_ENV_FILE`) and clears any already-exported `HAPPY_STACKS_*` / `HAPPY_LOCAL_*` variables so the stack env stays authoritative.
183
232
 
233
+ For a full explanation of the different folders/paths (`home` vs `workspace` vs `runtime` vs stack storage) and the exact env precedence rules, see: `[docs/paths-and-env.md](docs/paths-and-env.md)`.
234
+
184
235
  Cloned-repo fallback (before you run `happys init`):
185
236
 
186
237
  1. `<repo>/.env` (defaults)
@@ -15,17 +15,20 @@ _server_url="${2:-}" # ignored (kept for backwards compatibility)
15
15
  _webapp_url="${3:-}" # ignored (kept for backwards compatibility)
16
16
  _cli_home_dir="${4:-}" # ignored (kept for backwards compatibility)
17
17
 
18
- HAPPY_STACKS_HOME_DIR="${HAPPY_STACKS_HOME_DIR:-$HOME/.happy-stacks}"
19
- HAPPY_LOCAL_DIR="${HAPPY_LOCAL_DIR:-$HAPPY_STACKS_HOME_DIR}"
18
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+ DEFAULT_HOME_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
20
20
 
21
- PNPM_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/pnpm-term.sh"
22
- if [[ ! -x "$PNPM_TERM" ]]; then
23
- echo "missing terminal happys wrapper: $PNPM_TERM" >&2
21
+ HAPPY_LOCAL_DIR="${HAPPY_LOCAL_DIR:-${HAPPY_STACKS_HOME_DIR:-$DEFAULT_HOME_DIR}}"
22
+ HAPPY_STACKS_HOME_DIR="${HAPPY_STACKS_HOME_DIR:-$HAPPY_LOCAL_DIR}"
23
+
24
+ HAPPYS_TERM="$HAPPY_LOCAL_DIR/extras/swiftbar/happys-term.sh"
25
+ if [[ ! -x "$HAPPYS_TERM" ]]; then
26
+ echo "missing terminal happys wrapper: $HAPPYS_TERM" >&2
24
27
  exit 1
25
28
  fi
26
29
 
27
30
  if [[ "$stack" == "main" ]]; then
28
- exec "$PNPM_TERM" auth login
31
+ exec "$HAPPYS_TERM" auth login
29
32
  fi
30
33
 
31
- exec "$PNPM_TERM" stack auth "$stack" login
34
+ exec "$HAPPYS_TERM" stack auth "$stack" login
@@ -0,0 +1,130 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Refresh SwiftBar Git/worktree cache.
5
+ #
6
+ # Usage:
7
+ # ./git-cache-refresh.sh all
8
+ # ./git-cache-refresh.sh main
9
+ # ./git-cache-refresh.sh stack <name>
10
+ # ./git-cache-refresh.sh component <context:main|stack> <stackName> <component>
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ DEFAULT_HOME_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
14
+
15
+ HAPPY_LOCAL_DIR="${HAPPY_LOCAL_DIR:-${HAPPY_STACKS_HOME_DIR:-$DEFAULT_HOME_DIR}}"
16
+ HAPPY_STACKS_HOME_DIR="${HAPPY_STACKS_HOME_DIR:-$HAPPY_LOCAL_DIR}"
17
+
18
+ LIB_DIR="$HAPPY_LOCAL_DIR/extras/swiftbar/lib"
19
+ if [[ ! -f "$LIB_DIR/utils.sh" ]]; then
20
+ echo "missing SwiftBar libs at: $LIB_DIR" >&2
21
+ exit 1
22
+ fi
23
+
24
+ # shellcheck source=/dev/null
25
+ source "$LIB_DIR/utils.sh"
26
+ HAPPY_LOCAL_DIR="$(resolve_happy_local_dir)"
27
+ LIB_DIR="$HAPPY_LOCAL_DIR/extras/swiftbar/lib"
28
+ # shellcheck source=/dev/null
29
+ source "$LIB_DIR/git.sh"
30
+
31
+ components=(happy happy-cli happy-server-light happy-server)
32
+
33
+ refresh_one() {
34
+ local context="$1"
35
+ local stack="$2"
36
+ local component="$3"
37
+ local env_file="$4"
38
+
39
+ local active_dir=""
40
+ if [[ -n "$env_file" && -f "$env_file" ]]; then
41
+ active_dir="$(resolve_component_dir_from_env_file "$env_file" "$component")"
42
+ else
43
+ active_dir="$(resolve_component_dir_from_env "$component")"
44
+ fi
45
+
46
+ git_cache_refresh_one "$context" "$stack" "$component" "$active_dir" >/dev/null
47
+ }
48
+
49
+ refresh_stack() {
50
+ local stack="$1"
51
+ local env_file=""
52
+ if [[ "$stack" == "main" ]]; then
53
+ env_file="$(resolve_main_env_file)"
54
+ [[ -z "$env_file" ]] && env_file="$(resolve_stack_env_file main)"
55
+ else
56
+ env_file="$(resolve_stack_env_file "$stack")"
57
+ fi
58
+
59
+ for c in "${components[@]}"; do
60
+ refresh_one "stack" "$stack" "$c" "$env_file"
61
+ done
62
+ }
63
+
64
+ cmd="${1:-}"
65
+ case "$cmd" in
66
+ all)
67
+ refresh_stack "main"
68
+ STACKS_DIR="$(resolve_stacks_storage_root)"
69
+ LEGACY_STACKS_DIR="$HOME/.happy/local/stacks"
70
+ if swiftbar_is_sandboxed; then
71
+ LEGACY_STACKS_DIR=""
72
+ fi
73
+ STACK_NAMES="$(
74
+ {
75
+ ls -1 "$STACKS_DIR" 2>/dev/null || true
76
+ [[ -n "$LEGACY_STACKS_DIR" ]] && ls -1 "$LEGACY_STACKS_DIR" 2>/dev/null || true
77
+ } | sort -u
78
+ )"
79
+ while IFS= read -r s; do
80
+ [[ -n "$s" ]] || continue
81
+ [[ "$s" == "main" ]] && continue
82
+ refresh_stack "$s"
83
+ done <<<"$STACK_NAMES"
84
+ git_cache_touch_last_refresh "all"
85
+ echo "ok: git cache refreshed (all)"
86
+ ;;
87
+ main)
88
+ # Main stack only (fast).
89
+ local_env="$(resolve_main_env_file)"
90
+ for c in "${components[@]}"; do
91
+ refresh_one "main" "main" "$c" "$local_env"
92
+ done
93
+ git_cache_touch_last_refresh "main"
94
+ echo "ok: git cache refreshed (main)"
95
+ ;;
96
+ stack)
97
+ stack="${2:-}"
98
+ if [[ -z "$stack" ]]; then
99
+ echo "usage: $0 stack <name>" >&2
100
+ exit 2
101
+ fi
102
+ refresh_stack "$stack"
103
+ git_cache_touch_last_refresh "stack:${stack}"
104
+ echo "ok: git cache refreshed (stack $stack)"
105
+ ;;
106
+ component)
107
+ context="${2:-}"
108
+ stack="${3:-}"
109
+ component="${4:-}"
110
+ if [[ -z "$context" || -z "$stack" || -z "$component" ]]; then
111
+ echo "usage: $0 component <main|stack> <stackName> <component>" >&2
112
+ exit 2
113
+ fi
114
+ env_file=""
115
+ if [[ "$stack" == "main" ]]; then
116
+ env_file="$(resolve_main_env_file)"
117
+ [[ -z "$env_file" ]] && env_file="$(resolve_stack_env_file main)"
118
+ else
119
+ env_file="$(resolve_stack_env_file "$stack")"
120
+ fi
121
+ refresh_one "$context" "$stack" "$component" "$env_file"
122
+ git_cache_touch_last_refresh "component:${context}:${stack}:${component}"
123
+ echo "ok: git cache refreshed ($context/$stack/$component)"
124
+ ;;
125
+ *)
126
+ echo "usage: $0 all|main|stack <name>|component <context> <stackName> <component>" >&2
127
+ exit 2
128
+ ;;
129
+ esac
130
+