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
package/README.md
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
# Happy Stacks
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Run the **Happy** stack locally (or many stacks in parallel) and access it remotely and securely (using Tailscale).
|
|
6
|
-
|
|
7
|
-
`happy-stacks` is a CLI (`happys`) that orchestrate the real upstream repos
|
|
8
|
-
cloned under your configured workspace (default: `~/.happy-stacks/workspace/components/*`).
|
|
3
|
+
Run [**Happy**](https://happy.engineering/) locally and access it remotely and securely (using Tailscale).
|
|
9
4
|
|
|
10
5
|
## What is Happy?
|
|
11
6
|
|
|
@@ -13,62 +8,24 @@ Happy is an UI/CLI stack (server + web UI + CLI + daemon) who let you monitor an
|
|
|
13
8
|
|
|
14
9
|
## What is Happy Stacks?
|
|
15
10
|
|
|
16
|
-
happy-stacks is a
|
|
11
|
+
happy-stacks is a guided installer + local orchestration CLI for Happy.
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- manage **worktrees** for clean upstream PRs while keeping a patched fork
|
|
21
|
-
- run **multiple isolated stacks** (ports + dirs + component overrides)
|
|
22
|
-
- optionally manage autostart (macOS LaunchAgent) and a SwiftBar menu bar control panel
|
|
13
|
+
If you only want to **self-host Happy**, start with the **Self-host** section below.
|
|
14
|
+
If you want to **develop Happy** (worktrees, multiple stacks, upstream PR workflows), see the **Development** section further down.
|
|
23
15
|
|
|
24
|
-
##
|
|
16
|
+
## Self-host Happy (install + run)
|
|
25
17
|
|
|
26
|
-
### Step 1:
|
|
18
|
+
### Step 1: Setup
|
|
27
19
|
|
|
28
20
|
Recommended:
|
|
29
21
|
|
|
30
22
|
```bash
|
|
31
|
-
npx happy-stacks
|
|
32
|
-
export PATH="$HOME/.happy-stacks/bin:$PATH"
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Alternative (global install):
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
npm install -g happy-stacks
|
|
39
|
-
happys init
|
|
40
|
-
export PATH="$HOME/.happy-stacks/bin:$PATH"
|
|
23
|
+
npx happy-stacks setup --profile=selfhost
|
|
41
24
|
```
|
|
42
25
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Developer mode (clone this repo):
|
|
26
|
+
`setup` can optionally start Happy and guide you through authentication.
|
|
46
27
|
|
|
47
|
-
|
|
48
|
-
git clone https://github.com/leeroybrun/happy-stacks.git
|
|
49
|
-
cd happy-stacks
|
|
50
|
-
|
|
51
|
-
node ./bin/happys.mjs bootstrap --interactive
|
|
52
|
-
# legacy:
|
|
53
|
-
# pnpm bootstrap -- --interactive
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Notes:
|
|
57
|
-
|
|
58
|
-
- In a cloned repo, `pnpm <script>` still works, but `happys <command>` is now the recommended UX (same underlying scripts).
|
|
59
|
-
- To make the installed `~/.happy-stacks/bin/happys` shim (LaunchAgents / SwiftBar) run your local checkout without publishing to npm, set:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
echo 'HAPPY_STACKS_CLI_ROOT_DIR=/path/to/your/happy-stacks-checkout' >> ~/.happy-stacks/.env
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Or (recommended) persist it via init:
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
happys init --cli-root-dir=/path/to/your/happy-stacks-checkout
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Step 2: Run the main stack
|
|
28
|
+
### Step 2: Start Happy
|
|
72
29
|
|
|
73
30
|
Starts the local server, CLI daemon, and serves the pre-built UI.
|
|
74
31
|
|
|
@@ -76,7 +33,7 @@ Starts the local server, CLI daemon, and serves the pre-built UI.
|
|
|
76
33
|
happys start
|
|
77
34
|
```
|
|
78
35
|
|
|
79
|
-
### Step
|
|
36
|
+
### Step 3 (first run only): authenticate
|
|
80
37
|
|
|
81
38
|
On a **fresh machine** (or any new stack), the daemon needs to authenticate once before it can register a “machine”.
|
|
82
39
|
|
|
@@ -84,34 +41,20 @@ On a **fresh machine** (or any new stack), the daemon needs to authenticate once
|
|
|
84
41
|
happys auth login
|
|
85
42
|
```
|
|
86
43
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
If `.../new` shows “no machine” check whether this is **auth** vs a **daemon/runtime** issue:
|
|
44
|
+
If you want a quick diagnosis:
|
|
90
45
|
|
|
91
46
|
```bash
|
|
92
47
|
happys auth status
|
|
93
48
|
```
|
|
94
49
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
happys auth login
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
If auth is OK but the daemon isn’t running, run:
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
happys doctor
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Step 3: Enable Tailscale Serve (recommended for remote devices)
|
|
50
|
+
### Step 4: Enable Tailscale Serve (recommended for mobile/remote)
|
|
108
51
|
|
|
109
52
|
```bash
|
|
110
53
|
happys tailscale enable
|
|
111
54
|
happys tailscale url
|
|
112
55
|
```
|
|
113
56
|
|
|
114
|
-
### Step
|
|
57
|
+
### Step 5: Mobile access
|
|
115
58
|
|
|
116
59
|
Make sure Tailscale is [installed and running]
|
|
117
60
|
([https://tailscale.com/kb/1347/installation](https://tailscale.com/kb/1347/installation)) on your
|
|
@@ -124,9 +67,41 @@ your local server](docs/remote-access.md).
|
|
|
124
67
|
|
|
125
68
|
Details (secure context, phone instructions, automation knobs): `[docs/remote-access.md](docs/remote-access.md)`.
|
|
126
69
|
|
|
127
|
-
##
|
|
70
|
+
## Development (worktrees, stacks, contributor workflows)
|
|
71
|
+
|
|
72
|
+
### Setup (guided)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx happy-stacks setup --profile=dev
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Developing from a cloned repo
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git clone https://github.com/leeroybrun/happy-stacks.git
|
|
82
|
+
cd happy-stacks
|
|
83
|
+
|
|
84
|
+
node ./bin/happys.mjs setup --profile=dev
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Notes:
|
|
88
|
+
|
|
89
|
+
- In a cloned repo, `pnpm <script>` still works, but `happys <command>` is the recommended UX (same underlying scripts).
|
|
90
|
+
- To make the installed `~/.happy-stacks/bin/happys` shim (LaunchAgents / SwiftBar) run your local checkout without publishing to npm, set:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
echo 'HAPPY_STACKS_CLI_ROOT_DIR=/path/to/your/happy-stacks-checkout' >> ~/.happy-stacks/.env
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or (recommended) persist it via init:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
happys init --cli-root-dir=/path/to/your/happy-stacks-checkout
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Why this exists
|
|
128
103
|
|
|
129
|
-
- **Automated setup**: `happys
|
|
104
|
+
- **Automated setup**: `happys setup` + `happys start` gets the whole stack up and running.
|
|
130
105
|
- **No hosted dependency**: run the full stack on your own computer.
|
|
131
106
|
- **Lower latency**: localhost/LAN is typically much faster than remote hosted servers.
|
|
132
107
|
- **Custom forks**: easily use forks of the Happy UI + CLI (e.g. `leeroybrun/*`) while still contributing upstream to `slopus/*`.
|
|
@@ -134,7 +109,7 @@ Details (secure context, phone instructions, automation knobs): `[docs/remote-ac
|
|
|
134
109
|
- **Stacks**: run multiple isolated instances in parallel (ports + dirs + component overrides).
|
|
135
110
|
- **Remote access**: `happys tailscale ...` helps you get an HTTPS URL for mobile/remote devices.
|
|
136
111
|
|
|
137
|
-
|
|
112
|
+
### How Happy Stacks wires “local” URLs
|
|
138
113
|
|
|
139
114
|
There are two “URLs” to understand:
|
|
140
115
|
|
|
@@ -170,7 +145,7 @@ Diagram:
|
|
|
170
145
|
|
|
171
146
|
More details + automation: `[docs/remote-access.md](docs/remote-access.md)`.
|
|
172
147
|
|
|
173
|
-
|
|
148
|
+
### How it’s organized
|
|
174
149
|
|
|
175
150
|
- **Scripts**: `scripts/*.mjs` (bootstrap/dev/start/build/stacks/worktrees/service/tailscale/mobile)
|
|
176
151
|
- **Components**: `components/*` (each is its own Git repo)
|
|
@@ -183,9 +158,9 @@ Components:
|
|
|
183
158
|
- `happy-server-light` (light server, can serve built UI)
|
|
184
159
|
- `happy-server` (full server)
|
|
185
160
|
|
|
186
|
-
|
|
161
|
+
### Quickstarts (feature-focused)
|
|
187
162
|
|
|
188
|
-
|
|
163
|
+
#### Remote access (Tailscale Serve)
|
|
189
164
|
|
|
190
165
|
```bash
|
|
191
166
|
happys tailscale enable
|
|
@@ -194,7 +169,7 @@ happys tailscale url
|
|
|
194
169
|
|
|
195
170
|
Details: `[docs/remote-access.md](docs/remote-access.md)`.
|
|
196
171
|
|
|
197
|
-
|
|
172
|
+
#### Worktrees + forks (clean upstream PRs)
|
|
198
173
|
|
|
199
174
|
Create a clean upstream PR worktree:
|
|
200
175
|
|
|
@@ -210,9 +185,80 @@ happys wt pr happy https://github.com/slopus/happy/pull/123 --use
|
|
|
210
185
|
happys wt pr happy 123 --update --stash
|
|
211
186
|
```
|
|
212
187
|
|
|
188
|
+
##### Developer quickstart: create a PR stack (isolated ports/dirs; idempotent updates)
|
|
189
|
+
|
|
190
|
+
This creates (or reuses) a named stack, checks out PR worktrees for the selected components, optionally seeds auth, and starts the stack.
|
|
191
|
+
Re-run with `--reuse` to update the existing worktrees when the PR changes.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
happys stack pr pr123 \
|
|
195
|
+
--happy=https://github.com/slopus/happy/pull/123 \
|
|
196
|
+
--happy-cli=https://github.com/slopus/happy-cli/pull/456 \
|
|
197
|
+
--seed-auth --copy-auth-from=dev-auth --link-auth \
|
|
198
|
+
--dev
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Optional: run it in a self-contained sandbox folder (delete it to uninstall completely):
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
SANDBOX="$(mktemp -d /tmp/happy-stacks-sandbox.XXXXXX)"
|
|
205
|
+
happys --sandbox-dir "$SANDBOX" stack pr pr123 --happy=123 --happy-cli=456 --dev
|
|
206
|
+
rm -rf "$SANDBOX"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Update when the PR changes:
|
|
210
|
+
|
|
211
|
+
- Re-run with `--reuse` to fast-forward worktrees when possible.
|
|
212
|
+
- If the PR was force-pushed, add `--force`.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
happys stack pr pr123 --happy=123 --happy-cli=456 --reuse
|
|
216
|
+
happys stack pr pr123 --happy=123 --happy-cli=456 --reuse --force
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
##### Maintainer quickstart: one-shot “install + run PR stack” (idempotent)
|
|
220
|
+
|
|
221
|
+
This is the maintainer-friendly entrypoint. It is safe to re-run and will keep the PR stack wiring intact.
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
npx happy-stacks setup-pr \
|
|
225
|
+
--happy=https://github.com/slopus/happy/pull/123 \
|
|
226
|
+
--happy-cli=https://github.com/slopus/happy-cli/pull/456
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Optional: run it in a self-contained sandbox folder (delete it to uninstall completely):
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
SANDBOX="$(mktemp -d /tmp/happy-stacks-sandbox.XXXXXX)"
|
|
233
|
+
npx happy-stacks --sandbox-dir "$SANDBOX" setup-pr --happy=123 --happy-cli=456
|
|
234
|
+
rm -rf "$SANDBOX"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Short form (PR numbers):
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
npx happy-stacks setup-pr --happy=123 --happy-cli=456
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Override stack name (optional):
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npx happy-stacks setup-pr --name=pr123 --happy=123 --happy-cli=456
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Update when the PR changes:
|
|
250
|
+
|
|
251
|
+
- Re-run the same command to fast-forward the PR worktrees.
|
|
252
|
+
- If the PR was force-pushed, add `--force`.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npx happy-stacks setup-pr --happy=123 --happy-cli=456
|
|
256
|
+
npx happy-stacks setup-pr --happy=123 --happy-cli=456 --force
|
|
257
|
+
```
|
|
258
|
+
|
|
213
259
|
Details: `[docs/worktrees-and-forks.md](docs/worktrees-and-forks.md)`.
|
|
214
260
|
|
|
215
|
-
|
|
261
|
+
#### Server flavor (server-light vs full server)
|
|
216
262
|
|
|
217
263
|
- Use `happy-server-light` for a light local stack (no Redis, no Postgres, no Docker), and UI serving via server-light.
|
|
218
264
|
- Use `happy-server` when you need a more production-like server (Postgres + Redis + S3-compatible storage) or want to develop server changes for upstream.
|
|
@@ -233,7 +279,7 @@ happys stack srv exp1 -- use --interactive
|
|
|
233
279
|
|
|
234
280
|
Details: `[docs/server-flavors.md](docs/server-flavors.md)`.
|
|
235
281
|
|
|
236
|
-
|
|
282
|
+
#### Stacks (multiple isolated instances)
|
|
237
283
|
|
|
238
284
|
```bash
|
|
239
285
|
happys stack new exp1 --interactive
|
|
@@ -250,7 +296,15 @@ happys stack dev exp1
|
|
|
250
296
|
|
|
251
297
|
Details: `[docs/stacks.md](docs/stacks.md)`.
|
|
252
298
|
|
|
253
|
-
|
|
299
|
+
#### Dev stacks: browser origin isolation (IMPORTANT)
|
|
300
|
+
|
|
301
|
+
Non-main stacks use a stack-specific localhost hostname (no `/etc/hosts` changes required):
|
|
302
|
+
|
|
303
|
+
- `http://happy-<stack>.localhost:<uiPort>`
|
|
304
|
+
|
|
305
|
+
This avoids browser auth/session collisions between stacks (separate origin per stack).
|
|
306
|
+
|
|
307
|
+
#### Menu bar (SwiftBar)
|
|
254
308
|
|
|
255
309
|
```bash
|
|
256
310
|
happys menubar install
|
|
@@ -259,7 +313,7 @@ happys menubar open
|
|
|
259
313
|
|
|
260
314
|
Details: `[docs/menubar.md](docs/menubar.md)`.
|
|
261
315
|
|
|
262
|
-
|
|
316
|
+
#### Mobile iOS dev (optional)
|
|
263
317
|
|
|
264
318
|
```bash
|
|
265
319
|
happys mobile --help
|
|
@@ -268,7 +322,7 @@ happys mobile --json
|
|
|
268
322
|
|
|
269
323
|
Details: `[docs/mobile-ios.md](docs/mobile-ios.md)`.
|
|
270
324
|
|
|
271
|
-
|
|
325
|
+
#### Tauri desktop app (optional)
|
|
272
326
|
|
|
273
327
|
```bash
|
|
274
328
|
happys build --tauri
|
|
@@ -276,13 +330,12 @@ happys build --tauri
|
|
|
276
330
|
|
|
277
331
|
Details: `[docs/tauri.md](docs/tauri.md)`.
|
|
278
332
|
|
|
279
|
-
|
|
333
|
+
### Commands (high-signal)
|
|
280
334
|
|
|
281
335
|
- **Setup**:
|
|
282
|
-
- `happys
|
|
283
|
-
- `happys
|
|
284
|
-
- `happys bootstrap --
|
|
285
|
-
- `happys bootstrap --server=happy-server|happy-server-light|both`
|
|
336
|
+
- `happys setup` (guided; selfhost or dev)
|
|
337
|
+
- (advanced) `happys init` (plumbing: shims/runtime/pointer env)
|
|
338
|
+
- (advanced) `happys bootstrap --interactive` (component installer wizard)
|
|
286
339
|
- **Run**:
|
|
287
340
|
- `happys start` (production-like; serves built UI via server-light)
|
|
288
341
|
- `happys dev` (dev; Expo web dev server for UI)
|
|
@@ -303,17 +356,18 @@ Details: `[docs/tauri.md](docs/tauri.md)`.
|
|
|
303
356
|
- **Menu bar (SwiftBar)**:
|
|
304
357
|
- `happys menubar install`
|
|
305
358
|
|
|
306
|
-
|
|
359
|
+
### Docs (deep dives)
|
|
307
360
|
|
|
308
361
|
- **Remote access (Tailscale + phone)**: `[docs/remote-access.md](docs/remote-access.md)`
|
|
309
362
|
- **Server flavors (server-light vs server)**: `[docs/server-flavors.md](docs/server-flavors.md)`
|
|
310
363
|
- **Worktrees + forks workflow**: `[docs/worktrees-and-forks.md](docs/worktrees-and-forks.md)`
|
|
311
364
|
- **Stacks (multiple instances)**: `[docs/stacks.md](docs/stacks.md)`
|
|
365
|
+
- **Paths + env precedence (home/workspace/runtime/stacks)**: `[docs/paths-and-env.md](docs/paths-and-env.md)`
|
|
312
366
|
- **Menu bar (SwiftBar)**: `[docs/menubar.md](docs/menubar.md)`
|
|
313
367
|
- **Mobile iOS dev**: `[docs/mobile-ios.md](docs/mobile-ios.md)`
|
|
314
368
|
- **Tauri desktop app**: `[docs/tauri.md](docs/tauri.md)`
|
|
315
369
|
|
|
316
|
-
|
|
370
|
+
### Configuration
|
|
317
371
|
|
|
318
372
|
Where config lives by default:
|
|
319
373
|
|
|
@@ -329,4 +383,25 @@ Notes:
|
|
|
329
383
|
- **Use `.env.example` as the canonical template** (copy it to `.env` if you’re running this repo directly).
|
|
330
384
|
- If an LLM tool refuses to read/edit `.env.example` due to safety restrictions, **do not create an `env.example` workaround**—instead, ask the user to apply the change manually.
|
|
331
385
|
|
|
386
|
+
### Sandbox / test installs (fully isolated)
|
|
387
|
+
|
|
388
|
+
If you want to test the full setup flow (including PR stacks) without impacting your “real” install, run everything with `--sandbox-dir`.
|
|
389
|
+
To fully uninstall the test run, stop the sandbox stacks and delete the sandbox folder.
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
SANDBOX="$(mktemp -d /tmp/happy-stacks-sandbox.XXXXXX)"
|
|
393
|
+
|
|
394
|
+
# Run a PR stack (fully isolated install)
|
|
395
|
+
npx happy-stacks --sandbox-dir "$SANDBOX" setup-pr --happy=123 --happy-cli=456
|
|
396
|
+
|
|
397
|
+
# Tear down + uninstall
|
|
398
|
+
npx happy-stacks --sandbox-dir "$SANDBOX" stop --yes --no-service
|
|
399
|
+
rm -rf "$SANDBOX"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Notes:
|
|
403
|
+
|
|
404
|
+
- Sandbox mode disables global OS side effects (**PATH edits**, **SwiftBar plugin install**, **LaunchAgents/systemd services**, **Tailscale Serve enable/disable**) by default.
|
|
405
|
+
- To explicitly allow those for testing, set `HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1` (still recommended to clean up after).
|
|
406
|
+
|
|
332
407
|
For contributor/LLM workflow expectations: `[AGENTS.md](AGENTS.md)`.
|
package/bin/happys.mjs
CHANGED
|
@@ -7,15 +7,14 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
8
|
import { dirname, join } from 'node:path';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { commandHelpArgs, renderHappysRootHelp, resolveHappysCommand } from '../scripts/utils/cli_registry.mjs';
|
|
10
|
+
import { commandHelpArgs, renderHappysRootHelp, resolveHappysCommand } from '../scripts/utils/cli/cli_registry.mjs';
|
|
11
|
+
import { expandHome, getCanonicalHomeEnvPathFromEnv } from '../scripts/utils/paths/canonical_home.mjs';
|
|
11
12
|
|
|
12
13
|
function getCliRootDir() {
|
|
13
14
|
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
return String(p ?? '').replace(/^~(?=\/)/, homedir());
|
|
18
|
-
}
|
|
17
|
+
// expandHome is imported from scripts/utils/paths/canonical_home.mjs
|
|
19
18
|
|
|
20
19
|
function dotenvGetQuick(envPath, key) {
|
|
21
20
|
try {
|
|
@@ -47,7 +46,7 @@ function resolveCliRootDir() {
|
|
|
47
46
|
if (fromEnv) return expandHome(fromEnv);
|
|
48
47
|
|
|
49
48
|
// Stable pointer file: even if the real home dir is elsewhere, `happys init` writes the pointer here.
|
|
50
|
-
const canonicalEnv =
|
|
49
|
+
const canonicalEnv = getCanonicalHomeEnvPathFromEnv(process.env);
|
|
51
50
|
const v =
|
|
52
51
|
dotenvGetQuick(canonicalEnv, 'HAPPY_STACKS_CLI_ROOT_DIR') ||
|
|
53
52
|
dotenvGetQuick(canonicalEnv, 'HAPPY_LOCAL_CLI_ROOT_DIR') ||
|
|
@@ -86,11 +85,66 @@ function resolveHomeDir() {
|
|
|
86
85
|
if (fromEnv) return expandHome(fromEnv);
|
|
87
86
|
|
|
88
87
|
// Stable pointer file: even if the real home dir is elsewhere, `happys init` writes the pointer here.
|
|
89
|
-
const canonicalEnv =
|
|
88
|
+
const canonicalEnv = getCanonicalHomeEnvPathFromEnv(process.env);
|
|
90
89
|
const v = dotenvGetQuick(canonicalEnv, 'HAPPY_STACKS_HOME_DIR') || dotenvGetQuick(canonicalEnv, 'HAPPY_LOCAL_HOME_DIR') || '';
|
|
91
90
|
return v ? expandHome(v) : join(homedir(), '.happy-stacks');
|
|
92
91
|
}
|
|
93
92
|
|
|
93
|
+
function stripGlobalOpt(argv, { name, aliases = [] }) {
|
|
94
|
+
const names = [name, ...aliases];
|
|
95
|
+
for (const n of names) {
|
|
96
|
+
const eq = `${n}=`;
|
|
97
|
+
const iEq = argv.findIndex((a) => a.startsWith(eq));
|
|
98
|
+
if (iEq >= 0) {
|
|
99
|
+
const value = argv[iEq].slice(eq.length);
|
|
100
|
+
const next = [...argv.slice(0, iEq), ...argv.slice(iEq + 1)];
|
|
101
|
+
return { value, argv: next };
|
|
102
|
+
}
|
|
103
|
+
const i = argv.indexOf(n);
|
|
104
|
+
if (i >= 0 && argv[i + 1] && !argv[i + 1].startsWith('-')) {
|
|
105
|
+
const value = argv[i + 1];
|
|
106
|
+
const next = [...argv.slice(0, i), ...argv.slice(i + 2)];
|
|
107
|
+
return { value, argv: next };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { value: '', argv };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function applySandboxDirIfRequested(argv) {
|
|
114
|
+
const explicit = (process.env.HAPPY_STACKS_SANDBOX_DIR ?? '').trim();
|
|
115
|
+
const { value, argv: nextArgv } = stripGlobalOpt(argv, { name: '--sandbox-dir', aliases: ['--sandbox'] });
|
|
116
|
+
const raw = value || explicit;
|
|
117
|
+
if (!raw) return { argv: nextArgv, enabled: false };
|
|
118
|
+
|
|
119
|
+
const sandboxDir = expandHome(raw);
|
|
120
|
+
// Keep all state under one folder that can be deleted to reset completely.
|
|
121
|
+
const canonicalHomeDir = join(sandboxDir, 'canonical');
|
|
122
|
+
const homeDir = join(sandboxDir, 'home');
|
|
123
|
+
const workspaceDir = join(sandboxDir, 'workspace');
|
|
124
|
+
const runtimeDir = join(sandboxDir, 'runtime');
|
|
125
|
+
const storageDir = join(sandboxDir, 'storage');
|
|
126
|
+
|
|
127
|
+
process.env.HAPPY_STACKS_SANDBOX_DIR = sandboxDir;
|
|
128
|
+
process.env.HAPPY_STACKS_CLI_ROOT_DISABLE = '1'; // never re-exec into a user's "real" install when sandboxing
|
|
129
|
+
|
|
130
|
+
process.env.HAPPY_STACKS_CANONICAL_HOME_DIR = process.env.HAPPY_STACKS_CANONICAL_HOME_DIR ?? canonicalHomeDir;
|
|
131
|
+
process.env.HAPPY_LOCAL_CANONICAL_HOME_DIR = process.env.HAPPY_LOCAL_CANONICAL_HOME_DIR ?? process.env.HAPPY_STACKS_CANONICAL_HOME_DIR;
|
|
132
|
+
|
|
133
|
+
process.env.HAPPY_STACKS_HOME_DIR = process.env.HAPPY_STACKS_HOME_DIR ?? homeDir;
|
|
134
|
+
process.env.HAPPY_LOCAL_HOME_DIR = process.env.HAPPY_LOCAL_HOME_DIR ?? process.env.HAPPY_STACKS_HOME_DIR;
|
|
135
|
+
|
|
136
|
+
process.env.HAPPY_STACKS_WORKSPACE_DIR = process.env.HAPPY_STACKS_WORKSPACE_DIR ?? workspaceDir;
|
|
137
|
+
process.env.HAPPY_LOCAL_WORKSPACE_DIR = process.env.HAPPY_LOCAL_WORKSPACE_DIR ?? process.env.HAPPY_STACKS_WORKSPACE_DIR;
|
|
138
|
+
|
|
139
|
+
process.env.HAPPY_STACKS_RUNTIME_DIR = process.env.HAPPY_STACKS_RUNTIME_DIR ?? runtimeDir;
|
|
140
|
+
process.env.HAPPY_LOCAL_RUNTIME_DIR = process.env.HAPPY_LOCAL_RUNTIME_DIR ?? process.env.HAPPY_STACKS_RUNTIME_DIR;
|
|
141
|
+
|
|
142
|
+
process.env.HAPPY_STACKS_STORAGE_DIR = process.env.HAPPY_STACKS_STORAGE_DIR ?? storageDir;
|
|
143
|
+
process.env.HAPPY_LOCAL_STORAGE_DIR = process.env.HAPPY_LOCAL_STORAGE_DIR ?? process.env.HAPPY_STACKS_STORAGE_DIR;
|
|
144
|
+
|
|
145
|
+
return { argv: nextArgv, enabled: true };
|
|
146
|
+
}
|
|
147
|
+
|
|
94
148
|
function maybeAutoUpdateNotice(cliRootDir, cmd) {
|
|
95
149
|
// Non-blocking, cached update checks:
|
|
96
150
|
// - never run network calls in-process
|
|
@@ -193,17 +247,23 @@ function runNodeScript(cliRootDir, scriptRelPath, args) {
|
|
|
193
247
|
|
|
194
248
|
function main() {
|
|
195
249
|
const cliRootDir = getCliRootDir();
|
|
250
|
+
const initialArgv = process.argv.slice(2);
|
|
251
|
+
const { argv, enabled: sandboxed } = applySandboxDirIfRequested(initialArgv);
|
|
252
|
+
void sandboxed;
|
|
196
253
|
maybeReexecToCliRoot(cliRootDir);
|
|
197
|
-
const argv = process.argv.slice(2);
|
|
198
254
|
|
|
255
|
+
// If the user passed only flags (common via `npx happy-stacks --help`),
|
|
256
|
+
// treat it as root help rather than `help --help` (which would look like
|
|
257
|
+
// "unknown command: --help").
|
|
199
258
|
const cmd = argv.find((a) => !a.startsWith('--')) ?? 'help';
|
|
200
|
-
const
|
|
259
|
+
const cmdIndex = argv.indexOf(cmd);
|
|
260
|
+
const rest = cmdIndex >= 0 ? argv.slice(cmdIndex + 1) : [];
|
|
201
261
|
|
|
202
262
|
maybeAutoUpdateNotice(cliRootDir, cmd);
|
|
203
263
|
|
|
204
264
|
if (cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
205
|
-
const target =
|
|
206
|
-
if (!target) {
|
|
265
|
+
const target = rest[0];
|
|
266
|
+
if (!target || target.startsWith('-')) {
|
|
207
267
|
console.log(usage());
|
|
208
268
|
return;
|
|
209
269
|
}
|