@hamedb89/localghost 0.1.9 → 0.1.12

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 CHANGED
@@ -9,46 +9,20 @@ Buh. Friendly local hostnames for app repos.
9
9
  [![CI](https://github.com/hamedb89/localghost/actions/workflows/ci.yml/badge.svg)](https://github.com/hamedb89/localghost/actions/workflows/ci.yml)
10
10
  [![GitHub Pages](https://github.com/hamedb89/localghost/actions/workflows/pages.yml/badge.svg)](https://github.com/hamedb89/localghost/actions/workflows/pages.yml)
11
11
  [![Publish npm](https://github.com/hamedb89/localghost/actions/workflows/publish-npm.yml/badge.svg)](https://github.com/hamedb89/localghost/actions/workflows/publish-npm.yml)
12
- [![npm version](https://img.shields.io/badge/npm-v0.1.9-CB3837?logo=npm)](https://www.npmjs.com/package/@hamedb89/localghost)
12
+ [![npm version](https://img.shields.io/badge/npm-v0.1.12-CB3837?logo=npm)](https://www.npmjs.com/package/@hamedb89/localghost)
13
13
 
14
- Localghost is a tiny Node.js CLI for friendly local domains in app repos. Add it as a dev dependency, run `yarn dev`, and use `http://app.localhost/` instead of remembering which localhost port belongs to which process.
14
+ Localghost is a tiny Node.js CLI for clean local app domains. Add it as a dev dependency, keep running the command your team already knows, and use `http://app.localhost/` instead of remembering which port belongs to which process.
15
15
 
16
- [Website](https://hamedb89.github.io/localghost/) · [npm](https://www.npmjs.com/package/@hamedb89/localghost) · [GitHub](https://github.com/hamedb89/localghost)
16
+ [Website](https://hamedb89.github.io/localghost/) · [Docs](https://hamedb89.github.io/localghost/docs/) · [npm](https://www.npmjs.com/package/@hamedb89/localghost) · [GitHub](https://github.com/hamedb89/localghost)
17
17
 
18
- ## What It Does
18
+ ## Quick Start
19
19
 
20
- - Creates and reads `.localghost` in your app repo.
21
- - Lets repos choose explicit config files or filename patterns when `.localghost` is not enough.
22
- - Updates only a managed Localghost block in `/etc/hosts` during explicit setup.
23
- - Generates `ops/local/Caddyfile` for local reverse proxying. HTTP is the default; HTTPS is explicit with `--https` or `--ssl`.
24
- - Checks whether Caddy is installed, but does not run Homebrew for you.
25
- - Provides a Vite plugin that sets explicit `server.allowedHosts` entries.
26
- - Defaults Vite dev to the configured Localghost domain and no-ops during production/build.
27
- - Prints parsed config and project-local state as JSON for scripts, Codex, agents, and future MCP tools.
28
- - Checks npm for newer Localghost releases at most once per day, with an explicit opt-out.
29
-
30
- ## Trust
31
-
32
- - CI runs typecheck, build, site build, and npm package dry-run on Node 20 and 22.
33
- - GitHub Pages is deployed by Actions from the checked-in `site/` and `assets/` sources.
34
- - Preview the exact Pages artifact locally with `npm run site:serve`, then open `http://127.0.0.1:4173/`.
35
- - npm publish is guarded by `prepublishOnly` and the release workflow publishes with npm provenance.
36
- - Runtime dependencies are intentionally small: `commander` for the CLI and `execa` for process execution. Vite is an optional peer dependency for the Vite plugin.
37
- - No postinstall scripts, hidden Homebrew installs, or broad hosts-file rewrites.
38
- - Update checks are best-effort, cached for 24 hours, and can be disabled with `LOCALGHOST_NO_UPDATE_CHECK=1` or `--no-update-check`.
39
-
40
- <p align="center">
41
- <img src="./assets/localghost-app-icon.png" alt="Localghost app icon" width="180">
42
- </p>
43
-
44
- ## Start Here
20
+ Install it as a dev dependency:
45
21
 
46
22
  ```sh
47
23
  yarn add -D @hamedb89/localghost
48
24
  ```
49
25
 
50
- That is the entrypoint you are optimizing for: install the dev dependency, keep using the dev command your team already knows, and let Localghost handle the local-domain setup around it.
51
-
52
26
  For Vite apps, add the plugin once:
53
27
 
54
28
  ```ts
@@ -60,15 +34,13 @@ export default defineConfig({
60
34
  });
61
35
  ```
62
36
 
63
- Now your daily command is just:
37
+ Then keep using the command your repo already expects:
64
38
 
65
39
  ```sh
66
40
  yarn dev
67
41
  ```
68
42
 
69
- And you are ready.
70
-
71
- On the first interactive `yarn dev`, Localghost can create `.localghost`, explain the `/etc/hosts` change, write the local Caddyfile, and then print the browser-facing URL:
43
+ On the first interactive run, Localghost can create `.localghost`, explain the `/etc/hosts` change, write `ops/local/Caddyfile`, and print the browser-facing URL:
72
44
 
73
45
  ```txt
74
46
  localghost
@@ -77,7 +49,7 @@ also: http://www.app.localhost/
77
49
  target: http://127.0.0.1:5173/
78
50
  ```
79
51
 
80
- If your project does not use Vite, or you want one command that starts Caddy and then your app process, wrap your raw dev script:
52
+ For non-Vite apps, wrap your raw dev command:
81
53
 
82
54
  ```json
83
55
  {
@@ -88,109 +60,139 @@ If your project does not use Vite, or you want one command that starts Caddy and
88
60
  }
89
61
  ```
90
62
 
91
- Then the daily command stays the same:
63
+ ## The Simple Stuff
64
+
65
+ Start the detected development server with Localghost:
92
66
 
93
67
  ```sh
94
- yarn dev
68
+ npm exec localghost
95
69
  ```
96
70
 
97
- ## Configuration
71
+ Localghost detects the package manager, prefers a non-recursive `dev:raw` script, falls back to `dev`, repairs stale setup when needed, then starts Caddy and the server. Preview the decision without starting anything:
72
+
73
+ ```sh
74
+ npm exec localghost -- --dry-run
75
+ ```
98
76
 
99
- Most apps only need a `.localghost` file when they want explicit hostnames or multiple services:
77
+ Create the repo-local hostname contract:
100
78
 
101
- ```txt
102
- # Buh. Friendly names for local services.
103
- # Format: <host> <port>
104
- app.localhost 5173
105
- www.app.localhost 5173
106
- api.app.localhost 8787
79
+ ```sh
80
+ localghost init --write-scripts
107
81
  ```
108
82
 
109
- If `.localghost` is missing, the Vite plugin can prompt to create it during `yarn dev`. You can also create it directly:
83
+ Check whether the machine is ready:
110
84
 
111
85
  ```sh
112
- yarn localghost init
86
+ localghost doctor
113
87
  ```
114
88
 
115
- Check the machine:
89
+ Prepare `/etc/hosts` and the local Caddyfile:
116
90
 
117
91
  ```sh
118
- yarn localghost doctor
92
+ localghost setup
119
93
  ```
120
94
 
121
- If Caddy is missing, Localghost prints:
95
+ Check setup readiness:
122
96
 
123
- ```txt
124
- Caddy: missing
125
- Run: brew install caddy
126
- Localghost will not install it for you. No surprise spells.
97
+ ```sh
98
+ localghost status --ready
127
99
  ```
128
100
 
129
- Prepare or repair the machine setup directly:
101
+ Repair stale hosts, Caddy configuration, or setup state:
130
102
 
131
103
  ```sh
132
- yarn localghost setup
104
+ localghost repair
133
105
  ```
134
106
 
135
- Check that the hosts block and Caddyfile are ready:
107
+ Run only the local proxy:
136
108
 
137
109
  ```sh
138
- yarn localghost status --ready
110
+ localghost dev
139
111
  ```
140
112
 
141
- Run only the proxy when your app server is started separately:
113
+ Wrap an app server:
142
114
 
143
115
  ```sh
144
- yarn localghost dev
116
+ localghost run -- vite
145
117
  ```
146
118
 
147
- Use HTTPS only when you explicitly want Caddy local certificates:
119
+ See the domain layer:
148
120
 
149
121
  ```sh
150
- yarn localghost dev --https
122
+ localghost routes
151
123
  ```
152
124
 
153
- Trust Caddy's local HTTPS CA when you want browsers to stop showing local certificate warnings:
125
+ ```txt
126
+ localghost routes
127
+ http://app.localhost/ -> http://127.0.0.1:5173
128
+ http://api.app.localhost/ -> http://127.0.0.1:8787
129
+ ```
130
+
131
+ See active Localghost sessions:
154
132
 
155
133
  ```sh
156
- yarn localghost trust
134
+ localghost ps
135
+ localghost ps --json
157
136
  ```
158
137
 
159
- Reset generated setup without deleting `.localghost`:
138
+ Check for updates:
160
139
 
161
140
  ```sh
162
- yarn localghost reset
163
- yarn localghost setup
141
+ localghost update
164
142
  ```
165
143
 
166
144
  Prefer `.localhost` names. `.local` is supported, but Localghost warns because `.local` can collide with mDNS/Bonjour.
167
145
 
168
- ## Config Files
146
+ ## What It Changes
169
147
 
170
- By default, Localghost reads `.localghost` from the project root. Repos that need another name can be explicit:
148
+ Localghost is intentionally small and explicit:
171
149
 
172
- ```sh
173
- localghost print --config .localghost.preview
174
- localghost setup --config .localghost.preview
150
+ - Reads `.localghost` from your app repo and turns hostnames into local routes.
151
+ - Updates only a managed Localghost block in `/etc/hosts` during explicit setup.
152
+ - Generates `ops/local/Caddyfile` for local reverse proxying.
153
+ - Records setup state in `ops/local/localghost-state.json`.
154
+ - Keeps HTTP as the default; HTTPS is explicit with `--https`, `--ssl`, or config.
155
+ - Checks whether Caddy is installed, but never installs Homebrew packages for you.
156
+ - Gives Vite explicit `server.allowedHosts` entries without using `allowedHosts: true`.
157
+ - Never opens browser tabs by default.
158
+
159
+ `setup`, `dev`, and `teardown` refuse to run in production-like environments such as `NODE_ENV=production`, `VERCEL_ENV=production`, or `LOCALGHOST_ENV=production`.
160
+
161
+ ## Configuration By Use Case
162
+
163
+ ### One App Domain
164
+
165
+ Use a `.localghost` file when you want one stable local domain:
166
+
167
+ ```txt
168
+ # .localghost
169
+ app.localhost 5173
175
170
  ```
176
171
 
177
- You can pass `--config` more than once. Localghost uses the first file that exists:
172
+ Then run:
178
173
 
179
174
  ```sh
180
- localghost print --config .localghost.private --config .localghost
175
+ localghost setup
176
+ localghost run -- vite
181
177
  ```
182
178
 
183
- You can also search project-root filenames with a regular expression:
179
+ ### Multiple Local Services
184
180
 
185
- ```sh
186
- localghost print --config-pattern '^\.localghost\.(private|preview)$'
181
+ Map each browser-facing host to the upstream port:
182
+
183
+ ```txt
184
+ # .localghost
185
+ app.localhost 5173
186
+ www.app.localhost 5173
187
+ api.app.localhost 8787
188
+ admin.app.localhost 5174
187
189
  ```
188
190
 
189
- The Vite plugin accepts the same shape through `fileName`, `configFiles`, or `configPattern`. If you run `localghost init --config .localghost.preview --write-scripts`, generated package scripts include the matching `--config` flag.
191
+ `localghost routes` prints the same `domain -> upstream` map that `setup` and `dev` use.
190
192
 
191
- ## Package Scripts
193
+ ### Add Package Scripts
192
194
 
193
- `localghost init --write-scripts` adds these scripts when they are missing:
195
+ `localghost init --write-scripts` adds missing scripts without replacing your existing ones:
194
196
 
195
197
  ```json
196
198
  {
@@ -200,6 +202,7 @@ The Vite plugin accepts the same shape through `fileName`, `configFiles`, or `co
200
202
  "localghost:proxy:https": "localghost dev --https",
201
203
  "localghost:run": "localghost run --",
202
204
  "localghost:ready": "localghost status --ready",
205
+ "localghost:repair": "localghost repair",
203
206
  "localghost:trust": "localghost trust",
204
207
  "localghost:ps": "localghost ps",
205
208
  "localghost:print": "localghost print",
@@ -215,7 +218,9 @@ The Vite plugin accepts the same shape through `fileName`, `configFiles`, or `co
215
218
  }
216
219
  ```
217
220
 
218
- A full app might compose them with its own servers:
221
+ ### Keep `yarn dev` As The Daily Command
222
+
223
+ Wrap the raw app server so teammates keep typing the normal command:
219
224
 
220
225
  ```json
221
226
  {
@@ -226,7 +231,7 @@ A full app might compose them with its own servers:
226
231
  }
227
232
  ```
228
233
 
229
- In Turborepo, let Localghost wrap the dev runner and keep dev uncached:
234
+ For Turborepo, wrap the dev runner and keep dev uncached:
230
235
 
231
236
  ```json
232
237
  {
@@ -237,8 +242,6 @@ In Turborepo, let Localghost wrap the dev runner and keep dev uncached:
237
242
  }
238
243
  ```
239
244
 
240
- Then keep persistent dev tasks uncached:
241
-
242
245
  ```json
243
246
  {
244
247
  "tasks": {
@@ -247,263 +250,350 @@ Then keep persistent dev tasks uncached:
247
250
  }
248
251
  ```
249
252
 
250
- `localghost run` resolves one shared Localghost context, starts Caddy, handles the optional HTTPS trust prompt, then starts the child command. That keeps Localghost setup/proxy output before Vite's ready log. It passes the selected port to the child command through `LOCALGHOST_PORT` and `VITE_PORT`, and stops Caddy when the child exits. Dynamic ports are on by default: Localghost starts at the configured port, checks `127.0.0.1:<port>`, and walks upward until it finds a free port. Use `--dynamic-port=no` when you want strict fixed-port behavior.
253
+ `localghost run` starts Caddy, handles optional HTTPS trust, starts the child command, passes `LOCALGHOST_PORT` and `VITE_PORT`, and stops Caddy when the child exits.
251
254
 
252
- Most repos do not need `localghost.config.mjs`. Localghost derives `project` from `package.json`, defaults to port `5173`, keeps HTTPS off by default, enables dynamic ports by default, and adds `www.` aliases by default. Add `localghost.config.mjs` only when you want to override those defaults:
255
+ ### Vite Plugin
253
256
 
254
- ```js
255
- import { defineLocalghostConfig } from "@hamedb89/localghost";
257
+ Use the plugin when you want Vite to bind to `127.0.0.1`, use the selected Localghost port, set strict `allowedHosts`, and print the browser-facing domain:
256
258
 
257
- export default defineLocalghostConfig({
258
- https: true
259
+ ```ts
260
+ import { defineConfig } from "vite";
261
+ import { localGhostPlugin } from "@hamedb89/localghost/vite";
262
+
263
+ export default defineConfig({
264
+ plugins: [
265
+ localGhostPlugin({
266
+ port: 5173,
267
+ configFiles: [".localghost.private", ".localghost"]
268
+ })
269
+ ]
259
270
  });
260
271
  ```
261
272
 
262
- Then the daily script can stay small:
273
+ The plugin runs only during local `vite serve`; production/build mode does not configure Vite dev-server hosting. If `ghostTunnel` is configured, the build hook can still print the production URL shape for visibility.
263
274
 
264
- ```json
265
- {
266
- "scripts": {
267
- "dev": "localghost run -- yarn dev:raw",
268
- "dev:raw": "vite"
269
- }
270
- }
275
+ ### Custom Config Files
276
+
277
+ By default, Localghost reads `.localghost`. Repos that need another file name can be explicit:
278
+
279
+ ```sh
280
+ localghost print --config .localghost.preview
281
+ localghost setup --config .localghost.preview
282
+ ```
283
+
284
+ You can pass `--config` more than once. Localghost uses the first file that exists:
285
+
286
+ ```sh
287
+ localghost print --config .localghost.private --config .localghost
288
+ ```
289
+
290
+ You can also search project-root filenames with a regular expression:
291
+
292
+ ```sh
293
+ localghost print --config-pattern '^\.localghost\.(private|preview)$'
271
294
  ```
272
295
 
273
- `www.` aliases are enabled by default. A `.localghost` entry like `app.localhost 5173` also sets up `www.app.localhost` unless `wwwAlias: false` is set in `localghost.config.mjs`.
296
+ The Vite plugin accepts the same idea through `fileName`, `configFiles`, or `configPattern`.
297
+
298
+ ### Shared Project Defaults
274
299
 
275
- `ghostTunnel` is the production opt-in for a wildcard product entrypoint on top of your deployed Vite app. The default namespace is `<route>-<project>-<owner>.ghost.<domain>`, and omitted domains are shown as `*` in logs:
300
+ Most repos do not need `localghost.config.mjs`. Add it when you want shared defaults for CLI and Vite:
276
301
 
277
302
  ```js
278
303
  import { defineLocalghostConfig } from "@hamedb89/localghost";
279
304
 
280
305
  export default defineLocalghostConfig({
281
- ghostTunnel: {
282
- domains: "socialworkouts.app",
283
- mode: "manual"
284
- }
306
+ project: "app",
307
+ port: 5173,
308
+ dynamicPort: true,
309
+ autoRepair: true,
310
+ command: ["pnpm", "dev"],
311
+ wwwAlias: true
285
312
  });
286
313
  ```
287
314
 
288
- With `ghostTunnel: { domains }`, local route and Vite startup logs use local defaults for `route`, `project`, and `owner`, then fill the configured domain:
315
+ Localghost derives `project` from `package.json`, defaults to port `5173`, keeps HTTP as the default, enables dynamic ports and setup repair by default, and adds `www.` aliases by default. `run`, `dev`, and the Vite plugin perform a read-only readiness check first and repair only when the managed hosts block or setup state is stale.
289
316
 
290
- ```txt
291
- localghost ghost tunnel
292
- mode: manual
293
- expected: https://app-decision-layer-hamed.ghost.socialworkouts.app/
294
- ```
295
-
296
- Without `domains`, the expected URL stays wildcarded:
317
+ With no subcommand, `command` takes precedence. Otherwise Localghost detects npm, pnpm, Yarn, or Bun and runs `dev:raw` or `dev`. Scripts that invoke Localghost are skipped to prevent recursion.
297
318
 
298
- ```txt
299
- localghost ghost tunnel
300
- mode: manual
301
- expected: https://app-decision-layer-hamed.ghost.*/
302
- ```
319
+ ### Multiple Apps
303
320
 
304
- `ghostTunnel: "manual"` and `ghostTunnel: "public"` are shorthand modes. `manual` is the default; use `enabled: false` to keep domains/config in the file without exposing the tunnel surface.
321
+ For a monorepo where one root command already starts every app, keep using `command` and list the routes in `.localghost`.
305
322
 
306
- Use object form to override defaults or provide a concrete preview URL:
323
+ When Localghost should own each process, configure explicit services:
307
324
 
308
325
  ```js
309
326
  export default defineLocalghostConfig({
310
- ghostTunnel: {
311
- domains: "socialworkouts.app",
312
- preview: {
313
- route: "plan",
314
- project: "summer-base",
315
- owner: "hamed"
327
+ services: [
328
+ {
329
+ name: "web",
330
+ cwd: "apps/web",
331
+ host: "xyz.localhost",
332
+ port: 5173,
333
+ command: ["pnpm", "dev"]
334
+ },
335
+ {
336
+ name: "api",
337
+ cwd: "apps/api",
338
+ host: "api.xyz.localhost",
339
+ port: 8787,
340
+ command: ["pnpm", "dev"]
316
341
  }
317
- }
342
+ ]
318
343
  });
319
344
  ```
320
345
 
321
- Production apps can read that flag without requiring the local `.localghost` file, then construct and validate tunnel URLs:
346
+ Then bare `localghost` starts one Caddy instance and both services. Each command runs in its own `cwd` and receives its own `LOCALGHOST_PORT`, `VITE_PORT`, and `LOCALGHOST_SERVICE`. Dynamic-port selection and setup repair apply to every service. When Caddy or any service exits, Localghost stops the remaining processes.
322
347
 
323
- ```ts
324
- import {
325
- assertSecureGhostTunnelRequest,
326
- constructGhostTunnelUrl,
327
- readLocalghostProjectConfig
328
- } from "@hamedb89/localghost";
348
+ Omit a service `command` to detect `dev:raw` or `dev` from that service directory. Service directories must stay inside the project root, and names and hosts must be unique.
329
349
 
330
- const { config } = await readLocalghostProjectConfig();
331
- const url = constructGhostTunnelUrl({
332
- domain: "socialworkouts.app",
333
- route: "plan",
334
- project: "summer-base",
335
- owner: "hamed",
336
- ghostTunnel: config.ghostTunnel
337
- });
350
+ Caddy startup and validation logs are quiet after success; configuration errors remain visible. Set `LOCALGHOST_CADDY_VERBOSE=1` when debugging Caddy itself. Once all service ports are listening, Localghost prints the final hostname-to-upstream map beneath the service startup logs.
338
351
 
339
- const route = assertSecureGhostTunnelRequest({
340
- host: request.headers.get("host") ?? "",
341
- domain: "socialworkouts.app",
342
- protocol: "https",
343
- authenticated: Boolean(session),
344
- ghostTunnel: config.ghostTunnel
345
- });
352
+ Try the runnable Node example in [`examples/multi-service`](./examples/multi-service).
353
+
354
+ Disable automatic repair when you want strict failure behavior:
355
+
356
+ ```sh
357
+ localghost run --auto-repair=no -- vite
346
358
  ```
347
359
 
348
- That constructs `https://plan-summer-base-hamed.ghost.socialworkouts.app/`, validates the same host shape, requires HTTPS by default, and requires the app to confirm auth by default. See [Ghost Tunnel](./docs/ghost-tunnel.md) for the production DNS and routing flow.
360
+ Or set `autoRepair: false` in `localghost.config.mjs`. HTTPS certificate trust remains explicit.
349
361
 
350
- When `ghostTunnel.preview` is configured, local route and Vite startup logs include the concrete URL as `expected: https://plan-summer-base-hamed.ghost.socialworkouts.app/`. In an interactive Vite terminal, press `g` to show the Ghost Tunnel configuration and open a numbered concrete URL. Wildcard `*` URLs are shown for clarity, but the menu only opens configured concrete domains.
362
+ ### Fixed Ports
351
363
 
352
- Relay guardrails are private-by-default: public requests never choose the local target, route registration requires a matching local-agent bearer token, signed route claims are exact/scoped/expiring, and default targets are limited to `localhost`, `127.0.0.1`, and `::1` with dangerous ports blocked. The package exports relay helpers for registration, target validation, header stripping, log redaction, limits, and safe offline responses.
364
+ Dynamic ports are on by default. Localghost starts at the configured port, checks `127.0.0.1:<port>`, and walks upward until it finds a free port.
353
365
 
354
- Local security checks:
366
+ Use strict fixed-port behavior when you want startup to fail instead:
355
367
 
356
368
  ```sh
357
- npm test
358
- npm run test:cli
359
- npm run test:coverage
369
+ localghost run --dynamic-port=no -- vite
360
370
  ```
361
371
 
362
- `localghost dev` and `localghost run` also register their active sessions in a user-local activity file. Use `localghost ps` to see the Localghost apps currently running on the machine:
363
-
364
- ```txt
365
- localghost ps
372
+ Or in config:
366
373
 
367
- app run: vite
368
- cwd: /Users/you/Projects/app
369
- pid: 12345, caddy: 12346, child: 12347
370
- started: 2026-07-05T12:00:00.000Z
371
- app.localhost -> 127.0.0.1:5173 (listening)
374
+ ```js
375
+ export default defineLocalghostConfig({
376
+ dynamicPort: false
377
+ });
372
378
  ```
373
379
 
374
- Pass `--json` when another helper, such as a menu bar app, needs to poll the same state.
380
+ ### Local HTTPS
375
381
 
376
- ## macOS Widget
382
+ HTTP is the default. Use HTTPS only when you explicitly want Caddy local certificates:
377
383
 
378
- Localghost includes a tiny native macOS widget in `apps/macos-widget`. One widget tracks all active Localghost sessions across the machine. It shows `LG n` in the top bar and opens a small floating desktop panel with the Localghost route list, target ports, and listening state.
384
+ ```sh
385
+ localghost setup --https
386
+ localghost dev --https
387
+ ```
379
388
 
380
- Build it from source:
389
+ Trust Caddy's local HTTPS CA when you want browsers to stop showing local certificate warnings:
381
390
 
382
391
  ```sh
383
- npm run build
384
- npm run macos:widget:build
392
+ localghost trust
393
+ localghost run --trust -- vite
385
394
  ```
386
395
 
387
- The app bundle is written to `dist/LocalghostWidget.app`. See [docs/macos-widget.md](./docs/macos-widget.md) for local development notes.
396
+ macOS may ask for your password so Caddy can add its local CA to Keychain. Localghost records the trust result in `ops/local/localghost-state.json`.
388
397
 
389
- ## Vite
398
+ You can also make HTTPS the repo default:
390
399
 
391
- ```ts
392
- import { defineConfig } from "vite";
393
- import { localGhostPlugin } from "@hamedb89/localghost/vite";
400
+ ```js
401
+ export default defineLocalghostConfig({
402
+ https: true
403
+ });
404
+ ```
394
405
 
395
- export default defineConfig({
396
- plugins: [
397
- localGhostPlugin({
398
- port: 5173,
399
- configFiles: [".localghost.private", ".localghost"]
400
- })
401
- ]
406
+ ### Disable `www.` Aliases
407
+
408
+ By default, `app.localhost 5173` also creates `www.app.localhost`.
409
+
410
+ Disable that when the repo wants only exact hosts:
411
+
412
+ ```js
413
+ export default defineLocalghostConfig({
414
+ wwwAlias: false
402
415
  });
403
416
  ```
404
417
 
405
- The plugin binds Vite to `127.0.0.1` by default, prints the selected Localghost domain, generates an explicit `server.allowedHosts` list from the selected config file, and does not set `allowedHosts: true`. It runs only during local `vite serve`; production/build mode no-ops. Dynamic ports are enabled by default, so the plugin uses the configured port when available and otherwise moves to the next free port before Vite starts. Set `dynamicPort: false` when strict fixed-port behavior matters.
418
+ ### Public Ghost Tunnel
419
+
420
+ `ghostTunnel` is an opt-in production URL shape for deployed wildcard endpoints. It does not change local Caddy or `/etc/hosts` setup.
421
+
422
+ Use public mode when the deployed app should react to whatever route arrives:
406
423
 
407
- If `.localghost` is missing and Vite is running in an interactive terminal, the plugin asks whether to create one, prompts for the primary domain and optional extra domains, and then asks whether to run setup. Before touching `/etc/hosts`, it explains why macOS may ask for your password and confirms that only Localghost's managed block is changed.
424
+ ```js
425
+ import { defineLocalghostConfig } from "@hamedb89/localghost";
426
+
427
+ export default defineLocalghostConfig({
428
+ ghostTunnel: {
429
+ mode: "public",
430
+ domains: "copper-comet.example"
431
+ }
432
+ });
433
+ ```
408
434
 
409
- When Vite starts, Localghost prints the browser-facing URLs:
435
+ Build output stays flexible instead of filling slots from the build machine:
410
436
 
411
437
  ```txt
412
- localghost
413
- local: http://app.localhost/
414
- also: http://www.app.localhost/
415
- target: http://127.0.0.1:5173/
438
+ localghost ghost tunnel
439
+ mode: public
440
+ configured: https://<route>-<project>-<owner>.ghost.copper-comet.example/
416
441
  ```
417
442
 
418
- `https: true` means the browser-facing URL is expected to go through Caddy on HTTPS, while Vite still runs behind it on `127.0.0.1:<port>`. The plugin uses that to set Vite websocket/HMR proxy settings and to print `https://...` local host URLs. Localghost never opens browser tabs by default.
443
+ Production code can parse and validate the incoming wildcard host:
419
444
 
420
- Set `log: false` if you want to keep Vite's default terminal output only.
445
+ ```ts
446
+ import {
447
+ assertSecureGhostTunnelRequest,
448
+ readLocalghostProjectConfig
449
+ } from "@hamedb89/localghost";
421
450
 
422
- ## CLI
451
+ const { config } = await readLocalghostProjectConfig();
423
452
 
424
- ```sh
425
- localghost init
426
- localghost init --write-scripts
427
- localghost doctor
428
- localghost setup
429
- localghost setup --project app
430
- localghost setup --config .localghost.preview
431
- localghost setup --https
432
- localghost trust
433
- localghost status
434
- localghost status --ready
435
- localghost ps
436
- localghost ps --json
437
- localghost reset
438
- localghost teardown
439
- localghost teardown --remove-caddyfile
440
- localghost update
441
- localghost --no-update-check doctor
442
- localghost run -- vite
443
- localghost run --trust -- vite
444
- localghost run --dynamic-port=no -- vite
445
- localghost dev --config-pattern '^\.localghost\.'
446
- localghost dev --https
447
- localghost print
453
+ const route = assertSecureGhostTunnelRequest({
454
+ host: request.headers.get("host") ?? "",
455
+ domain: "copper-comet.example",
456
+ protocol: "https",
457
+ authenticated: Boolean(session),
458
+ ghostTunnel: config.ghostTunnel
459
+ });
448
460
  ```
449
461
 
450
- Localghost checks npm for newer releases after successful commands. The check has a short timeout, is cached for 24 hours, and never fails the command. Disable it with `LOCALGHOST_NO_UPDATE_CHECK=1` or `--no-update-check`. Run `localghost update` when you want an explicit update check.
462
+ By default, secure requests require HTTPS and app-authenticated access.
451
463
 
452
- `setup`, `dev`, and `teardown` refuse to run in production-like environments such as `NODE_ENV=production`, `VERCEL_ENV=production`, or `LOCALGHOST_ENV=production`.
464
+ ### Concrete Ghost Tunnel Preview
453
465
 
454
- When HTTPS is enabled, `localghost dev` and `localghost run` ask once whether to trust Caddy's local HTTPS CA. If you accept, macOS may ask for your password so Caddy can add its local CA to Keychain. Localghost records the result in `ops/local/localghost-state.json`; use `localghost trust` or `localghost run --trust -- ...` when you want to rerun the trust step intentionally.
466
+ Use `preview` only when you want one concrete URL in logs or menus:
467
+
468
+ ```js
469
+ export default defineLocalghostConfig({
470
+ ghostTunnel: {
471
+ mode: "public",
472
+ domains: "copper-comet.example",
473
+ preview: {
474
+ route: "decisionlayer",
475
+ project: "decision-layer",
476
+ owner: "hamedbahrami"
477
+ }
478
+ }
479
+ });
480
+ ```
455
481
 
456
- `setup` writes only a managed block in the system hosts file:
482
+ That prints:
457
483
 
458
484
  ```txt
459
- # localghost:start app
460
- 127.0.0.1 app.localhost
461
- # localghost:end app
485
+ localghost ghost tunnel
486
+ mode: public
487
+ configured: https://decisionlayer-decision-layer-hamedbahrami.ghost.copper-comet.example/
488
+ ```
489
+
490
+ ### Manual Ghost Tunnel
491
+
492
+ Manual mode is the default. It is useful for private or operator-mediated sharing flows:
493
+
494
+ ```js
495
+ export default defineLocalghostConfig({
496
+ ghostTunnel: {
497
+ mode: "manual",
498
+ domains: "moonlit-otter.example"
499
+ }
500
+ });
462
501
  ```
463
502
 
464
- Localghost does not rewrite the whole hosts file. It replaces only its own managed block for the selected project.
503
+ Manual relay helpers are private by default: registration requires a local-agent bearer token plus an exact signed route claim, targets must be explicit local host/port objects, dangerous ports are blocked, sensitive headers/logs are redacted, and offline agents get a safe 503 page.
465
504
 
466
- ## Teardown And State
505
+ ### Script And Agent Output
467
506
 
468
- `setup` writes a project-local state file at `ops/local/localghost-state.json`. It records the last Localghost action, selected config path, generated Caddyfile path, hosts file path, proxy mode, and the host entries that were applied. This is durable enough for project tooling and avoids relying on OS temp folders for tracking. Most apps should treat it as generated local state and ignore it in git.
507
+ Use JSON when scripts, menu bar tools, or agents need stable output:
469
508
 
470
509
  ```sh
471
- localghost status
472
- localghost status --ready
510
+ localghost print
473
511
  localghost status --json
512
+ localghost ps --json
513
+ localghost update --json
474
514
  ```
475
515
 
476
- `localghost dev` requires setup to be ready before it starts Caddy. If setup is missing or stale, it prints the exact `localghost setup` command instead of silently running `sudo`. Use `localghost dev --setup` only when you explicitly want the dev command to perform setup first.
516
+ `print` parses the selected config file. `status` reports the project-local setup state. `ps` reports active sessions across projects and whether each upstream port is listening.
477
517
 
478
- When a project no longer needs Localghost, teardown removes only the managed hosts block for the selected project:
518
+ ### Reset Or Remove Localghost
519
+
520
+ Reconcile the managed hosts block, regenerate and validate the Caddyfile, and refresh setup state:
479
521
 
480
522
  ```sh
481
- localghost teardown
523
+ localghost repair
482
524
  ```
483
525
 
484
- When you want to retest setup without deleting `.localghost`, use reset:
526
+ For HTTPS certificate trust problems, explicitly re-run Caddy's trust step:
527
+
528
+ ```sh
529
+ localghost repair --https --trust
530
+ ```
531
+
532
+ If a running Caddy process exits, `localghost run` exits with it; starting the normal development command again launches a fresh Caddy process.
533
+
534
+ Retest setup without deleting `.localghost`:
485
535
 
486
536
  ```sh
487
537
  localghost reset
488
538
  localghost setup
489
539
  ```
490
540
 
491
- The generated Caddyfile is left in place by default. Remove it explicitly when you want a fuller cleanup:
541
+ Remove only the managed hosts block for this project:
542
+
543
+ ```sh
544
+ localghost teardown
545
+ ```
546
+
547
+ Remove the generated Caddyfile too:
492
548
 
493
549
  ```sh
494
550
  localghost teardown --remove-caddyfile
495
551
  ```
496
552
 
497
- Localghost still uses a short-lived OS temp file while copying `/etc/hosts` with `sudo`, but that temp file is not the source of truth.
553
+ ### macOS Widget
554
+
555
+ Localghost includes a tiny native macOS widget under `apps/macos-widget`. It reads the shared activity file and shows known setup/running instances.
556
+
557
+ Build it from source:
558
+
559
+ ```sh
560
+ npm run build
561
+ npm run macos:widget:build
562
+ ```
563
+
564
+ The app bundle is written to `dist/LocalghostWidget.app`.
565
+
566
+ ## CLI Reference
567
+
568
+ ```sh
569
+ localghost [--cwd path] [--dry-run]
570
+ localghost init [--write-scripts] [--config file] [--host host] [--port port]
571
+ localghost doctor
572
+ localghost setup [--project name] [--config file] [--config-pattern regex] [--https|--ssl]
573
+ localghost repair [--project name] [--config file] [--config-pattern regex] [--https|--ssl] [--trust]
574
+ localghost trust [--project name] [--config file] [--config-pattern regex] [--https|--ssl]
575
+ localghost reset [--project name]
576
+ localghost teardown [--project name] [--remove-caddyfile]
577
+ localghost status [--ready] [--json]
578
+ localghost ps [--json]
579
+ localghost update [--json]
580
+ localghost dev [--config file] [--config-pattern regex] [--https|--ssl] [--auto-repair yes|no] [--trust]
581
+ localghost run [--config file] [--config-pattern regex] [--https|--ssl] [--auto-repair yes|no] [--trust] [--dynamic-port] -- command
582
+ localghost routes [--https|--ssl]
583
+ localghost print [--config file] [--config-pattern regex]
584
+ ```
498
585
 
499
586
  ## API
500
587
 
501
588
  ```ts
502
589
  import {
590
+ assertSecureGhostTunnelRequest,
591
+ constructGhostTunnelUrl,
503
592
  getConfigFileCandidates,
504
593
  initLocalghost,
505
594
  readDevHosts,
506
595
  readLocalghostState,
596
+ readLocalghostProjectConfig,
507
597
  removeSystemHosts,
508
598
  renderCaddyfile,
509
599
  renderHostsBlock,
@@ -511,34 +601,30 @@ import {
511
601
  updateSystemHosts
512
602
  } from "@hamedb89/localghost";
513
603
 
514
- readDevHosts({ configFiles: [".localghost.private", ".localghost"] });
515
- readDevHosts({ configPattern: /^\.localghost\.(private|preview)$/ });
604
+ import { localGhostPlugin } from "@hamedb89/localghost/vite";
516
605
  ```
517
606
 
518
- Vite helper:
607
+ `localHostsPlugin` is also exported as a compatibility alias for the Vite helper.
519
608
 
520
- ```ts
521
- import { localGhostPlugin } from "@hamedb89/localghost/vite";
522
- ```
609
+ ## Trust
523
610
 
524
- `localHostsPlugin` is also exported as a compatibility alias.
611
+ - CI runs typecheck, build, site build, and npm package dry-run on Node 20 and 22.
612
+ - GitHub Pages is deployed by Actions from the checked-in `site/`, `docs/`, and `assets/` sources.
613
+ - Preview the exact Pages artifact locally with `npm run site:serve`, then open `http://127.0.0.1:4173/`.
614
+ - npm publish is guarded by `prepublishOnly` and the release workflow publishes with npm provenance.
615
+ - Runtime dependencies are intentionally small: `commander` and `execa`. Vite is an optional peer dependency.
616
+ - No postinstall scripts, hidden Homebrew installs, surprise browser tabs, or broad hosts-file rewrites.
617
+ - Update checks are best-effort, cached for 24 hours, and can be disabled with `LOCALGHOST_NO_UPDATE_CHECK=1` or `--no-update-check`.
525
618
 
526
619
  ## More Docs
527
620
 
528
- Localghost copy can be mysterious, goofy, magical, funny, and a little absurd. The product behavior should stay boring in the best way: explicit commands, exact paths, clear errors, and no hidden installs.
529
-
530
621
  - [Website](https://hamedb89.github.io/localghost/)
531
- - [Brand guidelines](./docs/brand.md)
532
- - [Job-to-be-done flows](./docs/flows.md)
622
+ - [Rendered docs](https://hamedb89.github.io/localghost/docs/)
623
+ - [User flows](./docs/flows.md)
533
624
  - [CLI reference](./docs/localghost.1.md)
534
-
535
- ## Assets
536
-
537
- <p align="center">
538
- <img src="./assets/localghost-mascot.png" alt="Localghost mascot" width="180">
539
- <br>
540
- <img src="./assets/localghost-wordmark.png" alt="Localghost wordmark" width="420">
541
- </p>
625
+ - [Ghost Tunnel guide](./docs/ghost-tunnel.md)
626
+ - [macOS widget notes](./docs/macos-widget.md)
627
+ - [Brand guidelines](./docs/brand.md)
542
628
 
543
629
  ## License
544
630