happy-stacks 0.1.2 → 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 +121 -83
- 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 +560 -112
- package/scripts/build.mjs +24 -4
- package/scripts/cli-link.mjs +3 -3
- package/scripts/completion.mjs +15 -8
- package/scripts/daemon.mjs +130 -20
- package/scripts/dev.mjs +201 -133
- package/scripts/doctor.mjs +26 -21
- package/scripts/edison.mjs +1828 -0
- package/scripts/happy.mjs +3 -7
- package/scripts/init.mjs +43 -20
- package/scripts/install.mjs +14 -8
- package/scripts/lint.mjs +145 -0
- package/scripts/menubar.mjs +81 -8
- package/scripts/migrate.mjs +25 -15
- package/scripts/mobile.mjs +13 -7
- package/scripts/run.mjs +114 -27
- package/scripts/self.mjs +3 -7
- package/scripts/server_flavor.mjs +3 -3
- package/scripts/service.mjs +15 -2
- package/scripts/setup.mjs +790 -0
- package/scripts/setup_pr.mjs +182 -0
- package/scripts/stack.mjs +1792 -254
- package/scripts/stop.mjs +6 -3
- package/scripts/tailscale.mjs +17 -2
- package/scripts/test.mjs +144 -0
- package/scripts/tui.mjs +556 -0
- package/scripts/typecheck.mjs +2 -2
- package/scripts/ui_gateway.mjs +2 -2
- package/scripts/uninstall.mjs +18 -10
- 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} +48 -0
- package/scripts/utils/{wizard.mjs → cli/wizard.mjs} +1 -1
- package/scripts/utils/config.mjs +6 -2
- 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 +60 -11
- package/scripts/utils/env_file.mjs +36 -0
- package/scripts/utils/expo.mjs +4 -2
- package/scripts/utils/handy_master_secret.mjs +94 -0
- package/scripts/utils/happy_server_infra.mjs +100 -46
- 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 +121 -20
- package/scripts/utils/proc.mjs +29 -2
- package/scripts/utils/runtime.mjs +1 -3
- package/scripts/utils/sandbox.mjs +14 -0
- package/scripts/utils/server.mjs +24 -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 +79 -30
- package/scripts/utils/stacks.mjs +38 -0
- package/scripts/utils/watch.mjs +63 -0
- package/scripts/utils/worktrees.mjs +57 -1
- package/scripts/where.mjs +14 -7
- package/scripts/worktrees.mjs +82 -8
- /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
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,31 @@ 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"
|
|
23
|
+
npx happy-stacks setup --profile=selfhost
|
|
33
24
|
```
|
|
34
25
|
|
|
35
26
|
Alternative (global install):
|
|
36
27
|
|
|
37
28
|
```bash
|
|
38
29
|
npm install -g happy-stacks
|
|
39
|
-
happys
|
|
40
|
-
export PATH="$HOME/.happy-stacks/bin:$PATH"
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
(`init` will run `bootstrap` automatically. Use `--no-bootstrap` if you only want to write config and shims.)
|
|
44
|
-
|
|
45
|
-
Developer mode (clone this repo):
|
|
46
|
-
|
|
47
|
-
```bash
|
|
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
|
|
30
|
+
happys setup --profile=selfhost
|
|
54
31
|
```
|
|
55
32
|
|
|
56
|
-
|
|
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
|
-
```
|
|
33
|
+
`setup` can optionally start Happy and guide you through authentication.
|
|
70
34
|
|
|
71
|
-
### Step 2:
|
|
35
|
+
### Step 2: Start Happy
|
|
72
36
|
|
|
73
37
|
Starts the local server, CLI daemon, and serves the pre-built UI.
|
|
74
38
|
|
|
@@ -76,7 +40,7 @@ Starts the local server, CLI daemon, and serves the pre-built UI.
|
|
|
76
40
|
happys start
|
|
77
41
|
```
|
|
78
42
|
|
|
79
|
-
### Step
|
|
43
|
+
### Step 3 (first run only): authenticate
|
|
80
44
|
|
|
81
45
|
On a **fresh machine** (or any new stack), the daemon needs to authenticate once before it can register a “machine”.
|
|
82
46
|
|
|
@@ -84,34 +48,20 @@ On a **fresh machine** (or any new stack), the daemon needs to authenticate once
|
|
|
84
48
|
happys auth login
|
|
85
49
|
```
|
|
86
50
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
If `.../new` shows “no machine” check whether this is **auth** vs a **daemon/runtime** issue:
|
|
51
|
+
If you want a quick diagnosis:
|
|
90
52
|
|
|
91
53
|
```bash
|
|
92
54
|
happys auth status
|
|
93
55
|
```
|
|
94
56
|
|
|
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)
|
|
57
|
+
### Step 4: Enable Tailscale Serve (recommended for mobile/remote)
|
|
108
58
|
|
|
109
59
|
```bash
|
|
110
60
|
happys tailscale enable
|
|
111
61
|
happys tailscale url
|
|
112
62
|
```
|
|
113
63
|
|
|
114
|
-
### Step
|
|
64
|
+
### Step 5: Mobile access
|
|
115
65
|
|
|
116
66
|
Make sure Tailscale is [installed and running]
|
|
117
67
|
([https://tailscale.com/kb/1347/installation](https://tailscale.com/kb/1347/installation)) on your
|
|
@@ -124,9 +74,41 @@ your local server](docs/remote-access.md).
|
|
|
124
74
|
|
|
125
75
|
Details (secure context, phone instructions, automation knobs): `[docs/remote-access.md](docs/remote-access.md)`.
|
|
126
76
|
|
|
127
|
-
##
|
|
77
|
+
## Development (worktrees, stacks, contributor workflows)
|
|
78
|
+
|
|
79
|
+
### Setup (guided)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npx happy-stacks setup --profile=dev
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Developing from a cloned repo
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/leeroybrun/happy-stacks.git
|
|
89
|
+
cd happy-stacks
|
|
90
|
+
|
|
91
|
+
node ./bin/happys.mjs setup --profile=dev
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Notes:
|
|
95
|
+
|
|
96
|
+
- In a cloned repo, `pnpm <script>` still works, but `happys <command>` is the recommended UX (same underlying scripts).
|
|
97
|
+
- To make the installed `~/.happy-stacks/bin/happys` shim (LaunchAgents / SwiftBar) run your local checkout without publishing to npm, set:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
echo 'HAPPY_STACKS_CLI_ROOT_DIR=/path/to/your/happy-stacks-checkout' >> ~/.happy-stacks/.env
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Or (recommended) persist it via init:
|
|
128
104
|
|
|
129
|
-
|
|
105
|
+
```bash
|
|
106
|
+
happys init --cli-root-dir=/path/to/your/happy-stacks-checkout
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Why this exists
|
|
110
|
+
|
|
111
|
+
- **Automated setup**: `happys setup` + `happys start` gets the whole stack up and running.
|
|
130
112
|
- **No hosted dependency**: run the full stack on your own computer.
|
|
131
113
|
- **Lower latency**: localhost/LAN is typically much faster than remote hosted servers.
|
|
132
114
|
- **Custom forks**: easily use forks of the Happy UI + CLI (e.g. `leeroybrun/*`) while still contributing upstream to `slopus/*`.
|
|
@@ -134,7 +116,7 @@ Details (secure context, phone instructions, automation knobs): `[docs/remote-ac
|
|
|
134
116
|
- **Stacks**: run multiple isolated instances in parallel (ports + dirs + component overrides).
|
|
135
117
|
- **Remote access**: `happys tailscale ...` helps you get an HTTPS URL for mobile/remote devices.
|
|
136
118
|
|
|
137
|
-
|
|
119
|
+
### How Happy Stacks wires “local” URLs
|
|
138
120
|
|
|
139
121
|
There are two “URLs” to understand:
|
|
140
122
|
|
|
@@ -170,7 +152,7 @@ Diagram:
|
|
|
170
152
|
|
|
171
153
|
More details + automation: `[docs/remote-access.md](docs/remote-access.md)`.
|
|
172
154
|
|
|
173
|
-
|
|
155
|
+
### How it’s organized
|
|
174
156
|
|
|
175
157
|
- **Scripts**: `scripts/*.mjs` (bootstrap/dev/start/build/stacks/worktrees/service/tailscale/mobile)
|
|
176
158
|
- **Components**: `components/*` (each is its own Git repo)
|
|
@@ -183,9 +165,9 @@ Components:
|
|
|
183
165
|
- `happy-server-light` (light server, can serve built UI)
|
|
184
166
|
- `happy-server` (full server)
|
|
185
167
|
|
|
186
|
-
|
|
168
|
+
### Quickstarts (feature-focused)
|
|
187
169
|
|
|
188
|
-
|
|
170
|
+
#### Remote access (Tailscale Serve)
|
|
189
171
|
|
|
190
172
|
```bash
|
|
191
173
|
happys tailscale enable
|
|
@@ -194,7 +176,7 @@ happys tailscale url
|
|
|
194
176
|
|
|
195
177
|
Details: `[docs/remote-access.md](docs/remote-access.md)`.
|
|
196
178
|
|
|
197
|
-
|
|
179
|
+
#### Worktrees + forks (clean upstream PRs)
|
|
198
180
|
|
|
199
181
|
Create a clean upstream PR worktree:
|
|
200
182
|
|
|
@@ -210,9 +192,40 @@ happys wt pr happy https://github.com/slopus/happy/pull/123 --use
|
|
|
210
192
|
happys wt pr happy 123 --update --stash
|
|
211
193
|
```
|
|
212
194
|
|
|
195
|
+
Create a fully isolated PR stack (creates stack + PR worktrees + optional auth seeding + starts dev):
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
happys stack pr pr123 \
|
|
199
|
+
--happy=https://github.com/slopus/happy/pull/123 \
|
|
200
|
+
--happy-cli=https://github.com/slopus/happy-cli/pull/456 \
|
|
201
|
+
--seed-auth --copy-auth-from=dev-auth --link-auth \
|
|
202
|
+
--dev
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
One-shot “install + run PR stack” (best for maintainers who don’t have Happy Stacks set up yet):
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npx happy-stacks setup pr \
|
|
209
|
+
--happy=https://github.com/slopus/happy/pull/123 \
|
|
210
|
+
--happy-cli=https://github.com/slopus/happy-cli/pull/456
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
You can also run it as:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
npx happy-stacks setup-pr \
|
|
217
|
+
--happy=https://github.com/slopus/happy/pull/123 \
|
|
218
|
+
--happy-cli=https://github.com/slopus/happy-cli/pull/456
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Updating when the PR changes:
|
|
222
|
+
|
|
223
|
+
- Re-run the same command to fast-forward the PR worktrees.
|
|
224
|
+
- If the PR was force-pushed, add `--force`.
|
|
225
|
+
|
|
213
226
|
Details: `[docs/worktrees-and-forks.md](docs/worktrees-and-forks.md)`.
|
|
214
227
|
|
|
215
|
-
|
|
228
|
+
#### Server flavor (server-light vs full server)
|
|
216
229
|
|
|
217
230
|
- Use `happy-server-light` for a light local stack (no Redis, no Postgres, no Docker), and UI serving via server-light.
|
|
218
231
|
- 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 +246,7 @@ happys stack srv exp1 -- use --interactive
|
|
|
233
246
|
|
|
234
247
|
Details: `[docs/server-flavors.md](docs/server-flavors.md)`.
|
|
235
248
|
|
|
236
|
-
|
|
249
|
+
#### Stacks (multiple isolated instances)
|
|
237
250
|
|
|
238
251
|
```bash
|
|
239
252
|
happys stack new exp1 --interactive
|
|
@@ -250,7 +263,15 @@ happys stack dev exp1
|
|
|
250
263
|
|
|
251
264
|
Details: `[docs/stacks.md](docs/stacks.md)`.
|
|
252
265
|
|
|
253
|
-
|
|
266
|
+
#### Dev stacks: browser origin isolation (IMPORTANT)
|
|
267
|
+
|
|
268
|
+
Non-main stacks use a stack-specific localhost hostname (no `/etc/hosts` changes required):
|
|
269
|
+
|
|
270
|
+
- `http://happy-<stack>.localhost:<uiPort>`
|
|
271
|
+
|
|
272
|
+
This avoids browser auth/session collisions between stacks (separate origin per stack).
|
|
273
|
+
|
|
274
|
+
#### Menu bar (SwiftBar)
|
|
254
275
|
|
|
255
276
|
```bash
|
|
256
277
|
happys menubar install
|
|
@@ -259,7 +280,7 @@ happys menubar open
|
|
|
259
280
|
|
|
260
281
|
Details: `[docs/menubar.md](docs/menubar.md)`.
|
|
261
282
|
|
|
262
|
-
|
|
283
|
+
#### Mobile iOS dev (optional)
|
|
263
284
|
|
|
264
285
|
```bash
|
|
265
286
|
happys mobile --help
|
|
@@ -268,7 +289,7 @@ happys mobile --json
|
|
|
268
289
|
|
|
269
290
|
Details: `[docs/mobile-ios.md](docs/mobile-ios.md)`.
|
|
270
291
|
|
|
271
|
-
|
|
292
|
+
#### Tauri desktop app (optional)
|
|
272
293
|
|
|
273
294
|
```bash
|
|
274
295
|
happys build --tauri
|
|
@@ -276,13 +297,12 @@ happys build --tauri
|
|
|
276
297
|
|
|
277
298
|
Details: `[docs/tauri.md](docs/tauri.md)`.
|
|
278
299
|
|
|
279
|
-
|
|
300
|
+
### Commands (high-signal)
|
|
280
301
|
|
|
281
302
|
- **Setup**:
|
|
282
|
-
- `happys
|
|
283
|
-
- `happys
|
|
284
|
-
- `happys bootstrap --
|
|
285
|
-
- `happys bootstrap --server=happy-server|happy-server-light|both`
|
|
303
|
+
- `happys setup` (guided; selfhost or dev)
|
|
304
|
+
- (advanced) `happys init` (plumbing: shims/runtime/pointer env)
|
|
305
|
+
- (advanced) `happys bootstrap --interactive` (component installer wizard)
|
|
286
306
|
- **Run**:
|
|
287
307
|
- `happys start` (production-like; serves built UI via server-light)
|
|
288
308
|
- `happys dev` (dev; Expo web dev server for UI)
|
|
@@ -303,17 +323,18 @@ Details: `[docs/tauri.md](docs/tauri.md)`.
|
|
|
303
323
|
- **Menu bar (SwiftBar)**:
|
|
304
324
|
- `happys menubar install`
|
|
305
325
|
|
|
306
|
-
|
|
326
|
+
### Docs (deep dives)
|
|
307
327
|
|
|
308
328
|
- **Remote access (Tailscale + phone)**: `[docs/remote-access.md](docs/remote-access.md)`
|
|
309
329
|
- **Server flavors (server-light vs server)**: `[docs/server-flavors.md](docs/server-flavors.md)`
|
|
310
330
|
- **Worktrees + forks workflow**: `[docs/worktrees-and-forks.md](docs/worktrees-and-forks.md)`
|
|
311
331
|
- **Stacks (multiple instances)**: `[docs/stacks.md](docs/stacks.md)`
|
|
332
|
+
- **Paths + env precedence (home/workspace/runtime/stacks)**: `[docs/paths-and-env.md](docs/paths-and-env.md)`
|
|
312
333
|
- **Menu bar (SwiftBar)**: `[docs/menubar.md](docs/menubar.md)`
|
|
313
334
|
- **Mobile iOS dev**: `[docs/mobile-ios.md](docs/mobile-ios.md)`
|
|
314
335
|
- **Tauri desktop app**: `[docs/tauri.md](docs/tauri.md)`
|
|
315
336
|
|
|
316
|
-
|
|
337
|
+
### Configuration
|
|
317
338
|
|
|
318
339
|
Where config lives by default:
|
|
319
340
|
|
|
@@ -329,4 +350,21 @@ Notes:
|
|
|
329
350
|
- **Use `.env.example` as the canonical template** (copy it to `.env` if you’re running this repo directly).
|
|
330
351
|
- 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
352
|
|
|
353
|
+
### Sandbox / test installs (fully isolated)
|
|
354
|
+
|
|
355
|
+
If you want to test the full setup flow (including PR stacks) without impacting your “real” install, run with:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
npx happy-stacks --sandbox-dir /tmp/happy-stacks-sandbox setup pr --happy=123 --happy-cli=456
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
To reset completely, just delete the sandbox folder:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
rm -rf /tmp/happy-stacks-sandbox
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Sandbox mode disables global OS side effects (PATH edits, SwiftBar plugin install, LaunchAgents/systemd services) by default.
|
|
368
|
+
To explicitly allow them for testing, set `HAPPY_STACKS_SANDBOX_ALLOW_GLOBAL=1`.
|
|
369
|
+
|
|
332
370
|
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/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/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
|
}
|